Skip to main content
This page covers the edge cases around execute(): what the ExecutionAccount can do, how to return assets to the private pool, and what the advanced SDK exposes for follow-up calls.

ExecutionAccount boundary

An execute() session has three parts:
  • A private withdrawal from the user’s Unlink balance into an ExecutionAccount.
  • A sponsored ERC-4337 UserOperation whose calldata is ExecutionAccount.executeBatch(Call[]).
  • An optional depositBack that returns ERC-20 tokens from the ExecutionAccount to the user’s private Unlink balance.
The ExecutionAccount is a smart account, not an EOA. The owner key signs an ExecutionIntent for ERC-4337 validation. It does not send normal EVM transactions directly from the ExecutionAccount.

Advanced SDK surface

The @unlink-xyz/sdk/advanced subpath exports lower-level helpers for custom orchestration:
prepareExecute / submitExecute are the private-withdrawal execute-session helpers. Direct prepareExecute callers pass an account candidate (accountPolicy plus executionAccount.slotIndex, address, owner, and initCode); /execute/prepare is the atomic bind-or-conflict boundary. execute() handles the normal discovery, local derivation, and retry loop for you. prepareExecuteAccountCall / submitExecuteAccountCall prepare and submit a sponsored UserOperation from an ExecutionAccount without preparing a withdrawal. The prepare request may include factory initCode for the account’s first bind/deploy; the durable account binding is completed on submit after the backend verifies the owner’s ExecutionIntent signature. Direct low-level callers that use first-bind evidence in prepare must send the same factory initCode to submitExecuteAccountCall. executeAccountCall is the high-level helper that performs the same account-call flow for you. reserveExecutionAccount is still exported for explicit discovery/reservation tools and legacy custom flows. The high-level private-withdrawal execute() path does not call it; it lists accounts for an environment, derives the candidate account from the returned allocation hint, then prepares that candidate.
The withdrawless account-call flow does not create a pool withdrawal, and it is not a withdraw(0) workaround. Cold first use is supported only for the verified ExecutionAccount factory initCode derived by the SDK.
Pass an evm provider when constructing the user client to enable the SDK’s client-side deployed-code check for warm account calls. The backend always performs the authoritative factory/initCode derivation checks before preparing a withdrawless account-call session, and requires deployed code only when the request is reusing an account without first-bind initCode evidence.

Nonce helper

Permit2 nonces must be fresh. A compact browser helper for a random 128-bit nonce:

Return assets with depositBack

Use depositBack in the same execute() call when the batch leaves ERC-20 tokens in the ExecutionAccount and those tokens should go back into the user’s private balance. The deposit-back submit uses Permit2. Your batch must leave the requested token and amount in the ExecutionAccount, and Permit2 must be able to pull it. The usual pattern is to include an ERC-20 approval for Permit2 as one of the calls.
If the batch swaps into a different ERC-20, set depositBack.token to the token that remains in the ExecutionAccount and approve Permit2 for that token.

Follow-up calls without withdrawal

If a previous execute session completed without depositBack, those assets can remain in the ExecutionAccount. Use client.executeAccountCall to run another call from the same account index without moving funds through the pool first. The same recovery path applies when a known terminal failure such as deposit_back_failed or user_op_reverted leaves tokens in the ExecutionAccount. This still uses the ExecutionAccount owner signature and Unlink’s sponsored ERC-4337 path. The session has funding mode existing_execution_account, withdrawal_tx_id: null, token: null, and amount: null.
You can also use it for a non-deposit follow-up call, as long as the target calldata can execute from the ExecutionAccount and does not need a new private withdrawal.

Public EOA deposit fallback

depositWithApproval() deposits from the connected EVM wallet, not from the ExecutionAccount. If depositBack is not suitable for a flow, transfer the tokens from the ExecutionAccount to the user’s EOA in a follow-up executeAccountCall(), then call depositWithApproval() from that EOA.
This fallback is public. The ERC-20 transfer from the ExecutionAccount to the EOA and the later deposit source wallet are visible on-chain.
The pool, circuits, and contracts still require positive note amounts for real withdrawals and outputs. A zero-amount withdrawal is not the supported escape hatch.