> ## 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.

# Execute

> Use private funds in an external EVM call.

Use `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 as `amount` funds 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 for `erc20Abi` and `vaultAbi`. This snippet
uses viem for calldata encoding. Use ethers or your own encoder if that is
already your app stack.

```ts theme={null}
import { account, createUnlinkClient, evm } from "@unlink-xyz/sdk/browser";
import { encodeFunctionData } from "viem";

const client = createUnlinkClient({
  environment: "base-sepolia",
  account: account.fromMnemonic({ mnemonic }),
  evm: evm.fromEip1193({ provider: window.ethereum }),
});

const token = "0xTokenAddress";
const vault = "0xVaultAddress";
const amount = 1_000_000_000_000_000_000n;

const approveCall = {
  target: token,
  value: "0",
  data: encodeFunctionData({
    abi: erc20Abi,
    functionName: "approve",
    args: [vault, amount],
  }),
  label: "approve",
};

const supplyCall = {
  target: vault,
  value: "0",
  data: encodeFunctionData({
    abi: vaultAbi,
    functionName: "supply",
    args: [token, amount],
  }),
  label: "supply",
};

const result = await client.execute({
  token,
  amount: amount.toString(),
  calls: [approveCall, supplyCall],
});

console.log(result.status, result.executionId);
```

The target contract sees the ExecutionAccount as `msg.sender`. Set each call
value to `"0"`. Use WETH or an ERC-20 flow for actions that need native ETH.

## Deposit back privately

Use `depositBack` 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.

```ts theme={null}
const depositBackAmount = 900_000_000_000_000_000n;

const approvePermit2Call = {
  target: token,
  value: "0",
  data: encodeFunctionData({
    abi: erc20Abi,
    functionName: "approve",
    args: [permit2Address, depositBackAmount],
  }),
  label: "approve-permit2",
};

const result = await client.execute({
  token,
  amount: amount.toString(),
  calls: [...calls, approvePermit2Call],
  depositBack: {
    token,
    amount: depositBackAmount.toString(),
    nonce: crypto.getRandomValues(new BigUint64Array(1))[0].toString(),
    deadline: Math.floor(Date.now() / 1000) + 3600,
  },
});
```

Choose a fresh `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](/execute-advanced).

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:

```bash theme={null}
npm install @unlink-xyz/sdk@canary
```

Use `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`.

```ts theme={null}
const first = await client.execute({
  token,
  amount: amount.toString(),
  calls: [approveCall, supplyCall],
});

if (first.status !== "completed") {
  throw new Error(`execute ended with status ${first.status}`);
}

const followUp = await client.executeAccountCall({
  accountIndex: first.execution.account_index,
  calls: [
    {
      target: vault,
      value: "0",
      data: encodeFunctionData({
        abi: vaultAbi,
        functionName: "claimRewards",
        args: [],
      }),
    },
  ],
});

console.log(followUp.status, followUp.executionId);
```

For a public fallback that transfers ERC-20 tokens from the ExecutionAccount to
the connected EVM wallet and then uses `depositWithApproval()` to deposit back
into the pool, see [Advanced execute](/execute-advanced#public-eoa-deposit-fallback).

## 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.

Optional fields:

* `depositBack`: private re-deposit for ERC-20 tokens left in the ExecutionAccount.
* `accountPolicy`: account selection policy. Defaults to `"fresh"`.
  Use `"fresh"`, `"reuseLatest"`, or `{ slotIndex }`.

The `execute()` method requires a seed-backed account. `fromKeys` can transfer
and withdraw but cannot execute. See
[Account constructors](/accounts-and-keys#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.

```ts theme={null}
// Paginated list of the caller's accounts (stable keyset cursor).
const page = await client.executionAccounts.list({
  environment, // optional filter
  limit, // 1–100, default 50
  cursor, // page.next_cursor from a previous call
});

// One account by id.
const account = await client.executionAccounts.get(accountId);

// One account by its on-chain address (or null), scoped to the caller.
const account = await client.executionAccounts.getByAddress({
  environment,
  address,
});

// Reserve (or reuse) backend-authoritative account indices.
const reserved = await client.executionAccounts.reserve({
  policy: "fresh", // "fresh" | "reuseLatest" | { slotIndex }
});
```

Each `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.
