Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.unlink.xyz/llms.txt

Use this file to discover all available pages before exploring further.

Execution sessions move ERC-20 tokens from private pool notes into an ERC-4337 ExecutionAccount, run an ordered batch of one or more sponsored account calls atomically via Solady’s executeBatch, and optionally deposit resulting tokens back into the private pool. A “single call” is just a length-1 array. The SDK exposes session.execute(...) as the high-level path. It reserves an ExecutionAccount, prepares an ExecutionIntent, collects the required signatures, submits once, and returns once the session reaches a terminal status. Provide an EVM provider with eth_getCode support. The SDK accepts an empty prepared initCodeHash only when eth_getCode confirms bytecode at the resolved ExecutionAccount address; an active backend account alone does not prove deployment.
import { account, createTenant, evm } from "@unlink-xyz/sdk";

const provider = account.fromMnemonic({ mnemonic });

const tenant = createTenant({ engineUrl, apiKey });
const session = tenant.forUser({
  account: provider,
  evm: evm.fromViem({ walletClient, publicClient }),
});

const executeResult = await session.execute({
  token: "0xTokenAddress",
  amount: "1000000000000000000",
  // Generic batch primitive. `calls` is an ordered array of up to 16 calls run
  // atomically as one sponsored UserOp via Solady's `executeBatch`. A "single
  // call" is just a length-1 array. Each call's value must be "0" (native value
  // is unsupported on execution sessions). Optional `label` is client-side only.
  calls: [
    {
      target: "0xTokenAddress",
      value: "0",
      data: "0x...approveCalldata",
      label: "approve",
    },
    {
      target: "0xExternalContract",
      value: "0",
      data: "0x...supplyCalldata",
      label: "supply",
    },
  ],
  // signSigningRequest + signingHooks auto-derive from the seed-backed
  // account plus the cached /info/environment payload. Override either
  // explicitly only for Tenant↔Client splits.
  depositBack: {
    token: "0xTokenAddress",
    amount: "900000000000000000",
    nonce: "42",
    deadline: Math.floor(Date.now() / 1000) + 3600,
  },
});

Flow

  1. Reserve or reuse an ExecutionAccount.
  2. Prepare a private withdrawal and backend-built ExecutionIntent.
  3. Sign the private withdrawal request.
  4. Verify and sign the prepared ExecutionIntent.
  5. Submit the withdrawal signature, ExecutionIntent signature, and optional deposit-back payload in one request.
  6. Backend recovery waits for the withdrawal, sponsors and relays the UserOperation, and submits deposit-back when requested.
The backend persists each stage and the reconciler can continue recoverable work after client retries or process restarts. Client-side signing recovery after a process restart still requires the caller to recreate the same signing hooks and withdrawal signer. After single submit is accepted, funds may become public in the ExecutionAccount, so failures surface recovery metadata instead of pretending the flow is cancelable.

Recovery

session.execute(...) throws ExecuteRecoveryError when prepare, signing, submit, poll, or deposit-back pre-signing fails after the session exists. The error includes the execution id, withdrawal transaction id, optional deposit-back transaction id, failed stage, last known execution session when available, prepared ExecutionIntent, collected ExecutionIntent signature when available, and deposit-back payload when available. Use the low-level helpers for custom orchestration:
const session = await unlink.getExecuteSession(executionId);
const action = getExecuteNextAction(session);
When execution_submit_accepted is true, no signer-side action remains, even if the session status is still prepared.

Parameters

ParameterTypeRequiredDescription
tokenstringYesERC-20 token withdrawn privately
amountstringYesAmount in token base units
callsExecuteCall[]YesOrdered batch of 1..16 calls run atomically. Each call: { target, value, data, label? }. A “single call” is a length-1 array.
signSigningRequestSignSigningRequestFnNoOverride the withdrawal EdDSA signer. Auto-derived from a seed account.
signingHooksExecutionSigningHooksNoOverride the ExecutionIntent signer. Auto-derived from a seed account.
depositBackDepositBackParamsNoPrivate re-deposit for leftover/output ERC-20s
Each call’s value must be "0" until native ETH execution funding is supported (use WETH for flows that need ETH). For compound DeFi actions, list each step in calls (approve + supply, repay + withdraw, swap + deposit) — the wrapper runs them atomically as one sponsored UserOp via Solady’s executeBatch, and any inner revert reverts the whole batch. The optional label on each call is client-side only — the SDK strips it before sending over the wire. Use it to tag calls in your own logging or devtools. Per-call error surfacing (which call in the batch failed) is tracked separately as ENG-566 and is not implemented yet.