Docs · Tools
Every token, one confirmation.
Batch Send moves every ERC-20 you select to one destination address in a single EIP-5792 wallet_sendCalls batch. It is for the moment you are leaving a wallet — a compromised key, a migration to a hardware wallet, closing down a burner — when the alternative is approving twenty transfers by hand and hoping none of them jams.
At a glance#
| Batch Send | |
|---|---|
| Chains | Base, Ethereum, Optimism, Arbitrum, Robinhood. Base is the default; ?chain=<id> makes the choice linkable. |
| Assets | ERC-20 only. There is no native-ETH send path — your ETH stays where it is and pays the gas. |
| Amounts | Whole balances. You pick tokens, not amounts. |
| Destination | One address for the whole batch, typed as a hex address or an ENS name. |
| Per batch | Up to 50 tokens. |
| Confirmations | One, on a wallet that implements EIP-5792 batching. Otherwise one per token, plus one for the fee. |
| Cost | A flat 0.0002 ETH in native ETH per batch, plus gas — see the fee. |
| Wallets | Whatever is connected: the Farcaster host wallet inside a mini app, the wallet connected in the SwapsPro header, or an injected browser wallet connected on the page. |
Using it, step by step#
- Connect. Inside a Farcaster mini app the host wallet connects itself. Elsewhere, use the page's connect button, the header, or the wallet picker.
- Pick the chain. Five logos, one per supported chain. Switching chains clears your selection, because a token list from another chain means nothing here.
- Tick tokens. Your holdings are already listed — priced tokens first, and a collapsed Hidden / unpriced group underneath for everything the explorer flagged or could not price. Select all works on either group separately.
- Type the destination. A hex address, or an ENS name resolved on Ethereum mainnet. It is remembered in your browser for next time, and editing the field invalidates the previous resolution immediately — a stale address can never survive into a send.
- Send. The button opens a confirmation panel that names the token count, the chain and the full destination address and calls the send irreversible. Confirming that hands the batch to your wallet.
The rail beside the list keeps the arithmetic visible the whole time: how many priced tokens this chain holds and what they are worth, how many you have ticked, the fee, your ETH on the chain, and the resolved destination.
What you actually sign#
One batch. Every call in it is a plain ERC-20 transfer of your whole balance to the destination, followed by the flat fee as a bare value transfer with no calldata.
the batch
// One EIP-5792 wallet_sendCalls batch, in submission order:
transfer(<destination>, <your whole balance>) // token 1
transfer(<destination>, <your whole balance>) // token 2
… // up to 50
{ to: <fee recipient>, value: 200000000000000n } // 0.0002 ETH, last call
// Notes on the shape:
// - every transfer sends the FULL balance; there is no partial-amount field
// - no approve() anywhere — a transfer needs none
// - atomicRequired: false, so a wallet that executes the calls one after
// another (rather than atomically) is still allowed to take the batch
// - the fee call is omitted entirely when the fee is zeroBatching is negotiated, not assumed. Before building anything the tool asks the wallet, with wallet_getCapabilities, whether it supports atomic batching on this chain — and anything that cannot be read as an explicit yes counts as a no. The probe is given five seconds; a wallet that never answers is treated as not supporting it. Submission itself uses the final 2.0.0 wire format and retries once with the draft 1.0 shape if the host rejects the request's shape (and only then — a user rejection or an unsupported method is never retried).
If your wallet is on the wrong network, the send switches it first — offering to add the chain when the wallet does not know it, which is common for Robinhood Chain — then re-reads the chain id and refuses to continue unless it matches. An unknown chain counts as the wrong chain: a sequential send executes raw calldata on whatever network the wallet is really on, so that has to be proven rather than assumed.
The preflight, and why it exists#
Every selected transfer is simulated against a public RPC before anything reaches your wallet. Tokens whose transfer would revert are dropped from the batch and named back to you: “Skipped N tokens that refuse to be transferred (…) — likely scam tokens with locked transfers.”
This is not a nicety. Some wallets validate every call in a batch and reject the whole batch when any single call would fail — so one hostile airdrop sitting in your wallet would otherwise brick the entire send. Non-compliant tokens that return false from transfer instead of reverting are dropped on the same basis.
The classification is typed rather than string-matched, because substring matching misfiled real cases:
| Simulation result | Verdict | What happens |
|---|---|---|
| Returns no data (USDT-style tokens whose transfer returns nothing) | Fine | Kept. The call succeeded; only the decode failed. |
| Reverts, or returns false | Un-executable | Dropped from the batch and named in the notice. |
| HTTP error, timeout, or anything unrecognised | Unavailable | Kept. A transport failure is never allowed to accuse a token. |
When every simulation fails for transport reasons, nothing is filtered at all — the tool does not quietly shrink your batch because an RPC was unreachable. And when every simulation genuinely fails on the token's own terms, the send stops before your wallet is ever opened: “Every selected token refuses to be transferred (transfer-locked scam tokens). Nothing to send.”
The preflight is best-effort, and says so
When batching is not available#
- A wallet with no
wallet_sendCallsfalls back automatically to one transaction per token, in order, with the fee last. - In that mode, rejecting one token skips that token and moves on; two rejections in a row read as “stop everything” and end the run.
- The fee is sent last and only if at least one token actually landed. If the fee transaction itself fails or is rejected, it is swallowed — your tokens have already moved, and that is what matters.
- Any transfer that reverts on-chain is counted and named in the result: “N transfers did not go through (…). They are still in your wallet.” A partial failure is still a failure you have to see.
Once a batch is submitted, nothing is ever re-sent
Out of gas on the target chain#
A batch needs the flat fee plus a little gas headroom on the chain you are sending on — 0.0022 ETH on Ethereum, where gas is dearer, and 0.0003 ETH on the L2s. Below that, the send button is disabled and a top-up panel takes its place: “No gas on {chain}? Top up from another chain”.
- It reads your native balance on the other batch chains straight from their public RPCs — no wallet round-trips — and offers the ones holding enough, cheap L2s before mainnet and deepest balance first. A dead RPC costs you one missing source, never the whole scan.
- It sells half again what the target chain needs, so bridge fees and slippage cannot leave you short, and it refuses the route outright if the quoted output would still land under the requirement rather than bridging you into the same problem.
- It is one transaction on the source chain, quoted through the same routing the swap page uses. Arrival is watched by polling the target chain's balance, and the send unlocks by itself when the ETH lands: “Gas arrived on {chain} — you're ready to send.”
- Gas headroom is 0.002 ETH on Ethereum and 0.0001 ETH elsewhere — the flat fee sits on top of that.
Robinhood Chain is excluded as both a source and a target for the top-up: no cross-chain swap route reaches it, only the official bridge. That leaves 4 chains in the top-up graph. If nowhere has enough ETH, the panel collapses back to the plain requirement — “You need at least 0.0003 ETH on {chain} for the fee + gas” — and you fund the wallet yourself.
The fee#
A flat 0.0002 ETH per batch, in native ETH, appended as the last call. Not a percentage, not scaled by how many tokens you are moving, and never taken out of the tokens themselves. It is waived entirely for a qualifying Pro Pass holder — the pass has to be held by the wallet that would pay, not by anything else that happens to be connected — and when it is waived the fee call is simply left out of the batch.
A batch that sends nothing does not pay it: the fee call rides in the batch, and in the one-by-one path it is only sent after at least one transfer has landed. Gas is separate, quoted by your own wallet, and goes to validators rather than to us. Full context on the fees page.
Limits, and what happens at them#
| Limit | Value | What you see |
|---|---|---|
| Tokens per batch | 50 | Over the cap the send button is disabled: “Max 50 tokens per batch — unselect a few and send in rounds.” |
| Destination | One per batch | Sending to yourself is refused — the resolved address is compared to the connected wallet and the button stays disabled with “that’s this same wallet”. |
| Wallet response | 90 seconds | A submission the wallet never answers is reported, never auto-retried — a retry would stack a second confirmation sheet on you. |
| Batch confirmation | 5 minutes | Polled every 2 seconds. A timeout reports the batch as possibly in flight; it never re-sends. |
Where the token list comes from#
There is nothing to look up and no contract addresses to paste. Balances come from the chain's Blockscout index, with an Alchemy fallback where one is configured and, on Base, Zora coin enrichment — Zora creator and content coins are ordinary ERC-20s that trade in Zora's own pools, so they arrive from the explorer with no price and would otherwise be buried in the hidden group.
- Only ERC-20 contracts with a non-zero balance are listed.
- A token is hidden when the explorer's reputation flag is anything but
ok, or when nothing prices it. Hidden tokens are still sendable — they are one click away behind the disclosure, with a warning that some scam tokens revert on transfer. - The list is re-fetched several times after a send, cache-busted, because explorer indexing lags a block or two and a stale list is how a double-send happens.
What it deliberately will not do#
- Send native ETH. ERC-20 transfers only. Emptying the ETH balance would leave nothing to pay gas with.
- Send part of a balance. Every transfer is the whole balance.
- Send to more than one destination. One address per batch. Splitting a payment across recipients is a different tool.
- Approve anything. There is no
approvein the batch. Nothing is ever granted standing permission to move your tokens. - Re-send after a submitted batch. Ever. See above.
Related#
- Batch Swap + Burn — the same batching machinery, pointed at selling dust instead of moving it.
- DAO and treasury tooling — Batch Send from inside a Safe.
- Fees — one table, every path.
