execute() when a user wants to take tokens from their private Unlink
balance, run one or more EVM calls from an ExecutionAccount, and optionally
return tokens privately.
A single call is a one-item calls array. For a DeFi action, put each step in
order, such as approve then supply. The batch is atomic: if one call reverts,
the whole batch reverts.
Gas is sponsored
execute() runs through an ExecutionAccount, an ERC-4337 smart account that
belongs to the user. The SDK sends the call batch and locally derived account
candidate to Unlink; the backend builds the UserOperation and Unlink’s paymaster
sponsors it. The operation is submitted through a bundler and the gas is paid by
Unlink, not by the user.
- The user does not need to hold native gas tokens to call
execute(). The private balance you pass asamountfunds the calls; Unlink sponsors the gas. - You do not set gas fields. The backend builds the operation, the user signs the prepared ExecutionIntent, and Unlink sponsors and submits it.
- Sponsored execution is subject to per-call gas limits. A batch that exceeds the sponsorship caps is rejected before it is submitted.
Basic DeFi call
Use your app’s contract definitions forerc20Abi and vaultAbi. This snippet
uses viem for calldata encoding. Use ethers or your own encoder if that is
already your app stack.
msg.sender. Set each call
value to "0". Use WETH or an ERC-20 flow for actions that need native ETH.
Deposit back privately
UsedepositBack when the action leaves ERC-20 tokens in the ExecutionAccount
and you want to return them to the user’s private Unlink balance.
execute({ depositBack }) still starts with a private withdrawal, then runs the
call batch, then deposits back after the ExecutionAccount call is confirmed.
The call batch must leave depositBack.amount of depositBack.token in the
ExecutionAccount, and Permit2 must be able to pull that token. Include an
approval for Permit2 unless the ExecutionAccount already has enough allowance.
nonce for each deposit-back. The deadline is a Unix timestamp
in seconds and should leave enough time for the execution to settle.
To deposit back from an already funded ExecutionAccount without another private
withdrawal, use executeAccountCall({ depositBack }) with the same Permit2
approval requirement. See
Advanced execute.
If an execution ends as deposit_back_failed or user_op_reverted, tokens can
remain in the ExecutionAccount. Submit a follow-up executeAccountCall from the
same account index with corrected calls, including the Permit2 approval when
using depositBack.
Follow-up calls from the same account
Before using this flow, update to the latest canary SDK:executeAccountCall() when a user already has assets or state in an
ExecutionAccount and you want to run more calldata from that same account
without another private withdrawal.
This is not withdraw(0). It prepares a sponsored ERC-4337 UserOperation from
an ExecutionAccount without a new private withdrawal. If the reserved account is
cold, the SDK supplies verified factory initCode so the first account-call can
deploy and bind it during submit. The target contract still sees the
ExecutionAccount as msg.sender.
depositWithApproval() to deposit back
into the pool, see Advanced execute.
Parameters
Required fields:token: ERC-20 token withdrawn privately into the ExecutionAccount.amount: amount in token base units.calls: ordered batch of one to sixteen EVM calls.
depositBack: private re-deposit for ERC-20 tokens left in the ExecutionAccount.accountPolicy: account selection policy. Defaults to"fresh". Use"fresh","reuseLatest", or{ slotIndex }.
execute() method requires a seed-backed account. fromKeys can transfer
and withdraw but cannot execute. See
Account constructors.
Account discovery
client.executionAccounts exposes per-user discovery and reservation of
ExecutionAccounts. It surfaces identity and lifecycle metadata only — never
balances, positions, or note metadata. Scoping is enforced for capability
tokens; tenant API keys select the user (the SDK forwards the bound address).
When you list accounts for a specific environment, the response also includes
an allocation hint with tenant_index, chain_index, next_slot_index, and an
optional latest_slot_index when a reusable bound account exists. The high-level
execute() helper uses that hint to derive the candidate account locally, then
/execute/prepare atomically binds it.
ExecutionAccount carries account_id, tenant_index, chain_index, account_index, environment, account_address, owner_address, status (reserved | active | retired), deployed_at, created_at, updated_at, and last_execution_at (the created_at of the account’s most recent execution session, or null).
The reservation policy maps onto the backend allocation policy: "fresh" →
first_unused, "reuseLatest" → most_recent_active, and { slotIndex } →
by_index. client.reserveExecutionAccount(...) remains as a deprecated alias.