@gasfree-kit/intent
v0.2.0
Published
Chain-abstracted stablecoin payment intents — powered by gasfree-kit
Maintainers
Readme
@gasfree-kit/intent
Chain-abstracted stablecoin payment intents for EVM chains.
Declare what you want ("send 50 USDT to this address") and the SDK handles multi-chain balance discovery, route planning, cross-chain bridging, and gasless execution automatically.
How It Works
┌──────────────────────────────────────────────────────────┐
│ Your app │
└────────────────────────────┬─────────────────────────────┘
│
v
┌──────────────────────────────────────────────────────────┐
│ @gasfree-kit/intent │
│ │
│ discoverBalances ─> planIntent ─> executeIntent │
│ │
│ Routes: direct | bridge | aggregate │
└────────┬──────────────────┬──────────────────┬───────────┘
│ │ │
v v v
┌────────────────┐ ┌────────────────┐ ┌────────────────────┐
│ @gasfree-kit/ │ │ WDK USDT0 │ │ Multi-chain RPC │
│ evm-4337 │ │ bridge │ │ (balance queries) │
│ │ │ (LayerZero) │ │ │
│ ERC-4337 │ │ │ │ eth_call per chain │
│ UserOps │ │ Cross-chain │ │ │
└────────────────┘ └────────────────┘ └────────────────────┘Supported Chains
| Chain | Chain ID | Direct transfer | Bridge source | | -------- | -------- | --------------- | ------------- | | Ethereum | 1 | Yes | Yes | | Base | 8453 | Yes | Yes | | Arbitrum | 42161 | Yes | Yes | | Optimism | 10 | Yes | Yes | | Polygon | 137 | Yes | Yes | | Celo | 42220 | Yes | Yes | | Plasma | 9745 | Yes | Yes |
Prerequisites
This package depends on:
@gasfree-kit/corefor chain metadata, types, validation, and errors@gasfree-kit/evm-4337for ERC-4337 gasless transfers and wallet setup
Both are installed automatically as dependencies.
Installation
npm install @gasfree-kit/intentRequired peer dependency:
npm install @tetherto/wdk-wallet-evm-erc-4337Optional peer dependency (for cross-chain bridging):
npm install @tetherto/wdk-protocol-bridge-usdt0-evmQuick Start
5-line integration
import { GasfreeIntent } from '@gasfree-kit/intent';
const intent = GasfreeIntent.create({
chains: ['base', 'arbitrum', 'optimism'],
signer: { seedPhrase },
bundlerUrl: 'https://your-bundler.example.com',
paymasterUrl: 'https://your-paymaster.example.com',
isSponsored: true,
sponsorshipPolicyId: 'your-policy-id',
});
const receipt = await intent.sendIntent({
from: senderAddress,
to: merchantAddress,
amount: '50.00',
});
console.log(receipt.status); // "completed"Step by step
1. Create a config
import type { IntentConfig } from '@gasfree-kit/intent';
const config: IntentConfig = {
chains: ['base', 'arbitrum', 'optimism', 'polygon', 'celo'],
signer: { seedPhrase: 'your twelve word seed phrase ...' },
bundlerUrl: 'https://your-bundler.example.com',
paymasterUrl: 'https://your-paymaster.example.com',
isSponsored: true,
sponsorshipPolicyId: 'your-policy-id',
// Enable cross-chain bridging (requires wdk-protocol-bridge-usdt0-evm):
// bridgeConfig: { enabled: true },
};2. Discover balances
import { GasfreeIntent } from '@gasfree-kit/intent';
const intent = GasfreeIntent.create(config);
const balances = await intent.discoverBalances(senderAddress);
console.log(balances.total); // "83.50"
console.log(balances.chains); // [{ chain: 'base', balance: '50.00', ... }, ...]3. Plan the intent
const plan = await intent.planIntent({
from: senderAddress,
to: merchantAddress,
amount: '50.00',
});
console.log(plan.route); // "direct" | "bridge" | "aggregate"
console.log(plan.steps); // ordered execution steps
console.log(plan.estimatedFee); // "0" (sponsored) or "0.15" etc.4. Execute the plan
const receipt = await intent.executeIntent(plan);
console.log(receipt.status); // "completed" | "partial" | "failed"
console.log(receipt.totalFee); // actual fee paid
console.log(receipt.executionTime); // msRoute Types
The planner selects the simplest feasible route:
| Route | When | Steps | | --------- | ------------------------------------------ | ---------------------- | | Direct | Sender has enough on the destination chain | 1 transfer | | Bridge | One other chain has enough | 1 bridge + 1 transfer | | Aggregate | Balance split across multiple chains | N bridges + 1 transfer |
Bridge and aggregate routes require bridgeConfig: { enabled: true } in the config. Without it, only direct routes are planned.
Events
Subscribe to lifecycle events for real-time UI feedback:
intent.on('balances:discovered', (event) => {
console.log('Balances:', event.data);
});
intent.on('step:started', (event) => {
console.log('Starting step:', event.data);
});
intent.on('step:completed', (event) => {
console.log('Step done:', event.data);
});
intent.on('intent:completed', (event) => {
console.log('Payment complete:', event.data);
});
intent.on('recovery:needed', (event) => {
console.log('Recovery info:', event.data);
});All event types: balances:discovered, plan:created, step:started, step:completed, step:failed, intent:completed, intent:failed, recovery:needed.
Event handler exceptions are swallowed and never affect execution.
Configuration Reference
| Parameter | Type | Default | Description |
| --------------------- | -------------------------------------- | -------- | --------------------------------------------- |
| chains | EvmChain[] | Required | EVM chains to discover balances on |
| signer | IntentSigner | Required | { seedPhrase, accountIndex? } |
| bundlerUrl | string | Required | Candide ERC-4337 bundler endpoint |
| paymasterUrl | string | Required | Candide paymaster endpoint |
| isSponsored | boolean | Required | Whether gas is sponsored |
| sponsorshipPolicyId | string | - | Required when isSponsored is true |
| paymasterAddress | string | - | Paymaster contract (non-sponsored mode) |
| chainOverrides | Partial<Record<EvmChain, overrides>> | - | Per-chain RPC, bundler, paymaster overrides |
| bridgeConfig | { enabled: boolean, ... } | disabled | Must be { enabled: true } for bridge routes |
| balanceCacheTTL | number | 15000 | Balance cache TTL in ms |
| bridgeTimeout | number | 300000 | Bridge step timeout in ms (5 min) |
| transferTimeout | number | 120000 | Transfer step timeout in ms (2 min) |
| maxRetries | number | 3 | Auto-retries per step on transient failure |
| rpcFallbacks | Partial<Record<EvmChain, string[]>> | - | Fallback RPC URLs per chain |
| onStatusChange | (event: IntentEvent) => void | - | Shorthand callback for all lifecycle events |
Error Handling
All errors extend GasfreeError from @gasfree-kit/core:
import {
IntentValidationError,
RoutePlanningError,
InsufficientCrossChainBalanceError,
BridgeError,
IntentTimeoutError,
IntentExecutionError,
} from '@gasfree-kit/intent';
try {
await intent.sendIntent({ from, to, amount: '50.00' });
} catch (error) {
if (error instanceof InsufficientCrossChainBalanceError) {
console.log('Not enough USDT across all chains');
} else if (error instanceof IntentTimeoutError) {
console.log('Step timed out:', error.message);
} else if (error instanceof RoutePlanningError) {
console.log('No viable route:', error.message);
}
}Recovery
When a step fails, the receipt includes a recovery object explaining where funds are and what to do:
const receipt = await intent.executeIntent(plan);
if (receipt.recovery) {
console.log(receipt.recovery.type); // "bridge_pending" | "transfer_failed" | "timeout"
console.log(receipt.recovery.stuckChain); // which chain funds are on
console.log(receipt.recovery.stuckAmount); // how much
console.log(receipt.recovery.recoveryAction); // human-readable next step
console.log(receipt.recovery.retryable); // can the SDK retry automatically?
}Funds are never custodied by the SDK. If a bridge succeeds but the transfer fails, funds sit in the sender's own address on the destination chain.
Fund Safety
- The SDK never holds, pools, or controls user funds
- Every on-chain action requires the user's wallet signature
- Bridge uses Tether's USDT0 protocol (LayerZero) which guarantees delivery or revert
- If any step fails, funds remain in the sender's address on whichever chain they're on
- All failures are reflected in the
IntentReceiptwith recovery guidance
Main Exports
| Export | What it does |
| ----------------------- | ------------------------------------------------------- |
| GasfreeIntent | Main class: discover, plan, execute, one-shot send |
| discoverBalances | Standalone multi-chain balance discovery |
| planIntent | Standalone route planning from a balance map |
| validateIntentRequest | Validate an intent request before planning |
| buildEvm4337Config | Build per-chain EVM4337 config from IntentConfig |
| IntentEventEmitter | Typed event emitter for lifecycle events |
| clearBalanceCache | Clear the in-memory balance cache |
| Error classes | IntentValidationError, RoutePlanningError, etc. |
| Types | IntentRequest, ExecutionPlan, IntentReceipt, etc. |
License
MIT
