Picnic agents is in early access — read-only access only for now.
EN
Docs
Tools
transfer

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:

  • picnic source — executes in ~10s via the 4337 bundler. Used when topping up the card (recipient = gnosisPay safe address) or sending to any other address.
  • gnosisPay source (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 to broadcast once enqueued and to executed once 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:

NameTypeRequiredDescription
source_smart_account_addressstringyesSource 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_idnumberyesChain ID of the source smart account. Must equal the chainId encoded in asset_id — mismatch is rejected upfront.
asset_idstringyesAsset 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.
recipientstringyesDestination — 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_amountstringyesDecimal 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:

FieldTypeDescription
action_idstringUUID for the pending action. Pass to get_action_status as a polling fallback.
statusstringLifecycle status — always "proposed" immediately after this tool returns.
summarystringHuman-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_urlstringURL the user opens to authorize with their passkey.
resource_uristringpicnic://action/{action_id}. Subscribe via resources/subscribe to receive notifications/resources/updated as the action progresses.
simulation_urlstringTenderly 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_atdateISO timestamp when this proposal expires if not signed. Currently 10 minutes after creation.
external_recipientbooleantrue 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.
warningsstring[]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):

proposedsignedsubmittedbroadcastexecuted (success path) — same shape for both source types. What each step refers to differs:

  • picnic source: broadcast = bundler accepted the 4337 userOp; executed = userOp mined on-chain.
  • gnosisPay source: 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 in get_transactions then).

Other terminal states: failed (on-chain reverted), expired (not signed before expires_at), rejected (user denied at the sign page).

Errors:

  • Unknown asset_id: … — the asset_id isn'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)