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.