transfer
Initiate a token transfer from one of the user's controlled smart accounts to a recipient. Both picnic and gnosisPay source accounts are supported — discover available source addresses via get_smart_accounts. Returns a sign_url and a resource_uri for live status updates. Execution starts as soon as the user signs at sign_url.
Source account types:
picnicsource — executes in ~10s via the 4337 bundler. Used when topping up the card (recipient= gnosisPay safe address) or sending to any other address.gnosisPaysource (Gnosis chain only) — used to withdraw from the card account. Enqueues through the gnosisPay delay module and dispatches ~3 minutes after signing. The action transitions tobroadcastonce enqueued and toexecutedonce the delayed dispatch lands on-chain.
Deposit-to-card constraint: when the recipient is the user's own gnosisPay safe and the source is a picnic account, asset_id MUST match the card's denominated currency (EUR / USD / GBP, per the card's fiatSymbol). Otherwise the tool rejects up front — depositing the wrong token would land as a non-spendable balance on the card safe. Withdrawals FROM the card have no such restriction (the safe may legitimately hold mistakenly-deposited assets that the user needs to recover).
Required scopes: propose:transfer, execute:transfer
Parameters:
| Name | Type | Required | Description |
|---|---|---|---|
source_smart_account_address | string | yes | Source smart account holding the asset. Must be one of the passkey's controlled accounts on source_chain_id. Use get_smart_accounts to discover available addresses. |
source_chain_id | number | yes | Chain ID of the source smart account. Must equal the chainId encoded in asset_id — mismatch is rejected upfront. |
asset_id | string | yes | Asset id — eip155:{chainId}/erc20:{address} for ERC-20s, eip155:{chainId}/bep20:{address} on chain 56 (BSC), or eip155:{chainId}/slip44:60 for the chain's native coin. |
recipient | string | yes | Destination — either a 0x EVM address or an ENS name (e.g. vitalik.eth). ENS resolution always runs on Ethereum mainnet regardless of the destination chain; the resolved address is used as-is. |
formatted_amount | string | yes | Decimal amount in the asset's display units, e.g. "1.5" for 1.5 USDC or "0.001" for 0.001 ETH. The server resolves decimals from asset_id. |
Returns:
| Field | Type | Description |
|---|---|---|
action_id | string | UUID for the pending action. Pass to get_action_status as a polling fallback. |
status | string | Lifecycle status — always "proposed" immediately after this tool returns. |
summary | string | Human-readable summary the user sees on the sign page. Derived server-side from asset_id + parsed formatted_amount + resolved recipient — not from caller input. |
sign_url | string | URL the user opens to authorize with their passkey. |
resource_uri | string | picnic://action/{action_id}. Subscribe via resources/subscribe to receive notifications/resources/updated as the action progresses. |
simulation_url | string | Tenderly preview URL for picnic-source transfers; null for gnosisPay-source (the delay-module enqueue isn't a userOp, so a Tenderly preview is meaningless). |
expires_at | date | ISO timestamp when this proposal expires if not signed. Currently 10 minutes after creation. |
external_recipient | boolean | true when the destination is not one of the user's controlled smart accounts on this chain. Surface this warning verbally to the user before opening sign_url — on-chain transfers are irreversible. |
warnings | string[] | Human-readable warning lines aimed at the calling agent (currently: the external-recipient line when external_recipient = true). Empty array when there's nothing to flag. |
Action lifecycle (status enum):
proposed → signed → submitted → broadcast → executed (success path) — same shape for both source types. What each step refers to differs:
picnicsource:broadcast= bundler accepted the 4337 userOp;executed= userOp mined on-chain.gnosisPaysource:broadcast= the delay-module enqueue tx is in the mempool;executed= the enqueue tx mined on-chain. The actual on-chain dispatch of the inner transfer lands ~3 minutes later via the scheduled-transaction job (and shows up inget_transactionsthen).
Other terminal states: failed (on-chain reverted), expired (not signed before expires_at), rejected (user denied at the sign page).
Errors:
Unknown asset_id: …— theasset_idisn't registered in our asset registry.source_chain_id (X) does not match the chain encoded in asset_id (Y)— args don't agree; fix one of them.Source smart account 0x… is not controlled by this passkey on chain X— the address isn't in the passkey's controlled set.gnosisPay-source transfers are only supported on chain 100 (Gnosis)— gnosisPay safes only exist on Gnosis.Invalid recipient: … — expected a 0x address or ENS name— recipient is neither a valid address nor a dotted ENS-style name.Invalid ENS name: …/ENS name … does not resolve to an address/Failed to resolve ENS name …— ENS errors.Invalid formatted_amount: …— the value can't be parsed as a decimal at the asset's precision.Amount must be > 0— zero or negative amount.
Example prompts:
"Top up my card with 5 USDC from my Picnic account on Base." (picnic source → gnosisPay safe address)
"Withdraw 10 EURE from my card to my Picnic account." (gnosisPay source → picnic address, ~3 min)
"Send 100 USDC to vitalik.eth on Base." (picnic source → external)