Skip to content

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.

Wallet

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:

PathWhereGas paid byFlag
Solana sponsorsrc/provider/solana.tsPrivy (sponsor)always on
EVM sponsored sendsrc/provider/wallets.tsPrivy (sponsor)GASLESS_EVM (default true)
EVM wallet-gassrc/provider/wallets.tsthe wallet's own native tokenGASLESS_EVM=false
ERC-4337 smart accountsrc/provider/defi/{execute,smartaccount}.tspaymaster (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:

  1. 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.
  2. 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.
  3. Confirm, don't assume. settleSend reports an honest confirmed / reverted / pending. A tx that hasn't settled in the ~25-second window stays pending — it is not declared failed synchronously, because a false "failed" would invite a manual re-send → double-send.
  4. Reconcile after a grace window. A still-pending send 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 hashless sending state 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 / keyDefaultWhat it controls
GASLESS_EVMtrueAttempt Privy-sponsored EVM sends; when off, the wallet pays gas (with the ERC-20 guard)
SMART_ACCOUNTSfalseMaster 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_WITHDRAWALSfalseThe hard gate on all on-chain execution — nothing broadcasts without it
EMBEDDED_WALLETS + PRIVY_AUTHORIZATION_KEYoffRoutes 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

be everywhere — live here.