Appearance
Gasless Execution
Crypto has an onboarding tax most people never signed up for: to move a token, you first have to hold a different token to pay the network fee — ETH on Ethereum, BNB on BNB Chain, SOL on Solana. pocket agent's answer is to sponsor the gas so you (and your agent) never have to think about it. You hold what you're sending; the fee is paid for you.
This page is the engineering detail behind that promise: how Solana sends are sponsored, how EVM sends attempt sponsorship and when they fall back, how the ERC-4337 smart-account path batches an approval and an action into a single sponsored user operation, and — most importantly — the phantom-hash failure mode that the whole design is built to avoid.

Why this matters for correctness, not just UX
Gasless isn't only a convenience. The alternative — an agent wallet that has to keep a native-gas balance topped up on every chain — is both a worse experience and a correctness hazard: a wallet with no gas can get a transaction hash back for a transaction that never mines. Sponsoring the fee removes that failure class on the happy path, and the code is written to refuse to lie when it can't confirm a send. That honesty is the real feature.
The three execution paths
Three mechanisms sit behind every on-chain send, chosen by chain and configuration:
| Path | Where | Gas paid by | Flag |
|---|---|---|---|
| Solana sponsor | src/provider/solana.ts | Privy (sponsor) | always on |
| EVM sponsored send | src/provider/wallets.ts | Privy (sponsor) | GASLESS_EVM (default true) |
| EVM wallet-gas | src/provider/wallets.ts | the wallet's own native token | GASLESS_EVM=false |
| ERC-4337 smart account | src/provider/defi/{execute,smartaccount}.ts | paymaster (sponsor) | SMART_ACCOUNTS + BUNDLER_RPC_URL |
Solana: always sponsored
Solana is the cleanest case. Privy's server SDK natively sponsors the network fee, so a wallet needs no SOL for gas — it only needs to hold what it's sending. solanaGaslessTransfer builds a versioned transaction (a SystemProgram.transfer for SOL, or an SPL transferChecked for USDC / USDT / EURC) and submits it with sponsor: true.
One honest caveat: SPL account rent
The network fee is sponsored. But sending an SPL token (USDC/USDT/EURC) to a brand-new recipient that has no token account yet still costs the one-time account rent (~0.002 SOL), paid by the sender in the create-ATA instruction. SOL transfers and SPL transfers to an existing token account are fully gasless. Full rent abstraction arrives with the treasury/account-abstraction payer step — noted plainly in the code.
EVM: sponsored-first, and authoritative
On EVM, the behavior turns on GASLESS_EVM (default true). This is the subtle part, because getting it wrong risks a double-send.
When sponsorship is on, the sponsored send is the only broadcast path — the code deliberately never falls back to a wallet-gas send after a sponsored attempt. Here's why: the agent/server wallets hold no native token, so a wallet-gas send would fail anyway; and if the sponsored transaction already went out, a second (fallback) broadcast would send the funds twice. So on success the code returns the hash; and if the sponsored call resolves without a hash or throws, it surfaces an honest "may still be processing" error — never a phantom success, never a re-broadcast — and lets the reconciler resolve anything that actually landed.
When GASLESS_EVM is off (no sponsorship configured at all), the wallet pays its own gas. A native ETH/BNB/POL send is fine — the fee comes out of what's being sent. But an ERC-20 transfer (USDC/USDT/…) needs native gas the wallet usually lacks, and without it Privy can hand back a hash for a transaction that never mines. The code checks the wallet's native balance first and fails cleanly before broadcasting rather than returning a hash it can't stand behind.
EVM token addresses and chain CAIP-2 ids are hardcoded in wallets.ts (EVM_TOKENS, EVM_CAIP2) for Ethereum, Base, BNB, and Polygon.
ERC-4337: smart accounts + paymaster
The cleanest gasless experience — and the one used for Agentic DeFi — is an ERC-4337 smart account. Instead of sending raw transactions from an EOA, the agent sends a user operation through a bundler, and a paymaster sponsors the gas. Crucially, a swap's approve and the action itself are batched into one atomic userOp: both succeed or neither does.
The stack (src/provider/defi/smartaccount.ts):
- Kernel v3.1 smart account, EntryPoint 0.7
- owner / signer = the agent's Privy server EOA, adapted into a viem signer via
createViemAccount - Pimlico bundler + paymaster, driven through
permissionless.js - heavy dependencies are lazy-imported, so a missing or incompatible package can never crash boot — it only surfaces if the smart-account path is actually used
Fund the smart-account address, not the EOA
A smart account is a different address than the EOA — it's counterfactual (computed deterministically at index 0n) and only deployed on its first operation. So the agent's DeFi funds must sit at the smart-account address when SMART_ACCOUNTS is on; Enso is told fromAddress = SA. The address is computable from Privy + a public RPC alone (no bundler needed), so you can fund it before the flag is flipped. Ask the agent with defi_smart_account(chain).
Smart accounts are supported on Ethereum (1), Base (8453), and BNB (56). The bundler URL supports ${chainId} templating so one configured endpoint serves every chain.
How the DeFi executor chooses
executeCalls (src/provider/defi/execute.ts) is the router that turns an approved DeFi plan into on-chain calls. It picks a path and, like every send, is gated by ENABLE_WITHDRAWALS:
The EOA path sends calls in order (e.g. approve, then the action). It isn't atomic like the 4337 batch, but each step is broadcast only after the previous one returns a hash, and a throw happens before a broadcast — so there's no double-send. Any call that returns no hash aborts the whole sequence rather than leave an unknown on-chain state.
The 4337 path is skipped in embedded mode — on purpose
When EMBEDDED_WALLETS is on, executeCalls deliberately avoids the smart-account path, because today's Kernel account is owned by the server EOA (custodial). Embedded mode instead routes through the delegated signer (agentSendEvm), where the user-owned wallet signs within its enclave-enforced mandate. The non-custodial target (Track C of REFACTOR_PLAN.md) is to flip the smart account's owner to the user's embedded key and add a session-key module — that work is not started yet. See The agent runtime → Mandates.
The phantom-hash caveat
A "phantom hash" is the single most important failure mode in this whole subsystem, so it's worth stating on its own. On EVM, a wallet that lacks native gas (or has no active sponsorship) can be handed back a transaction hash for a transaction that will never mine. If the code treated that hash as success, you'd see "Sent" for a transfer that never happened — and worse, a naive retry could double-send once the funds are available.
The runtime defends against this at every layer:
The rules, concretely:
- No hash → no success. Every send path throws rather than report a send that returned an empty hash. The sponsored EVM path throws a "may still be processing" error instead of guessing.
- Guard before broadcast. On the wallet-gas path, an ERC-20 send with no native gas fails before broadcasting — the phantom hash is never even created.
- Confirm, don't assume.
settleSendreports an honestconfirmed/reverted/pending. A tx that hasn't settled in the ~25-second window stayspending— it is not declared failed synchronously, because a false "failed" would invite a manual re-send → double-send. - Reconcile after a grace window. A still-
pendingsend is only declared dropped by the leader-locked reconciler, and only after a 15-minute grace (long enough that a real tx has propagated to our RPCs and passed Solana's blockhash-validity window). Anything stuck in a hashlesssendingstate is never auto-resolved — it's surfaced via an alert for manual reconciliation, because its outcome is genuinely unknown.
This is why gasless is framed as a correctness feature. By sponsoring the fee, the common cause of a phantom hash disappears on the happy path; and where it can still occur, the code is written to say so honestly rather than fake a receipt. For the full set of money-safety invariants this plugs into, see Security.
Configuration reference
| Flag / key | Default | What it controls |
|---|---|---|
GASLESS_EVM | true | Attempt Privy-sponsored EVM sends; when off, the wallet pays gas (with the ERC-20 guard) |
SMART_ACCOUNTS | false | Master switch for the ERC-4337 batched, paymaster-sponsored path |
BUNDLER_RPC_URL | (unset) | Pimlico/ZeroDev/Alchemy bundler; supports ${chainId} templating. smartAccountsConfigured = SMART_ACCOUNTS && this is set |
PAYMASTER_RPC_URL | (unset) | Sponsorship endpoint (often the same as the bundler for Pimlico) |
PAYMASTER_POLICY_ID | (unset) | Provider sponsorship policy id, passed as paymasterContext when present |
ENABLE_WITHDRAWALS | false | The hard gate on all on-chain execution — nothing broadcasts without it |
EMBEDDED_WALLETS + PRIVY_AUTHORIZATION_KEY | off | Routes EVM execution through the mandate-bounded delegated signer instead of the 4337 path |
ALCHEMY_API_KEY | (unset) | Keyed RPC for Ethereum/Base (public RPCs otherwise) |
Where gasless sits by plan
Agentic DeFi swaps are a Plus-and-up feature; smart accounts (the gasless 4337 path) are a Pro feature. Solana and EVM sponsored sends on the ordinary wallet paths apply wherever ENABLE_WITHDRAWALS is on. See Pricing.
Related: The agent runtime · Agentic DeFi · Wallet · Security