> ## Documentation Index
> Fetch the complete documentation index at: https://docs.keystoneos.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Action Delegates

> Route deposit and approval actions through your custody provider.

## What Are Action Delegates?

Action delegates are callback functions you pass to `KeystoneProvider` that handle on-chain operations. When the widget needs to deposit tokens or approve a token transfer, it calls your delegate instead of interacting with the blockchain directly.

This is how Tier 2 (Interactive + Custody) integration works - your frontend collects the user's intent, and your backend executes it through Fireblocks, BitGo, or any signing infrastructure.

## The Deposit Flow

When a user clicks "Deposit" in the widget:

```mermaid theme={null}
sequenceDiagram
    participant U as User
    participant W as Widget
    participant PB as Your Backend
    participant KS as KeyStone API
    participant FB as Fireblocks

    U->>W: Clicks "Deposit"
    W->>KS: Fetch deposit calldata
    KS-->>W: calldata, escrow address, chain ID
    W->>PB: onDepositRequired(leg, depositInfo)
    PB->>FB: Submit transaction
    FB-->>PB: txHash
    PB-->>W: { txHash }
    W->>W: Show "Deposit pending"
    KS-->>W: SSE: leg_deposited
    W->>W: Show "Deposit confirmed"
```

## Setting Up Delegates

```tsx theme={null}
<KeystoneProvider
  sessionToken={token}
  actionDelegates={{
    onDepositRequired: async (leg, depositInfo) => {
      // leg: the settlement leg being deposited
      // depositInfo: pre-encoded calldata and metadata
      const result = await yourBackend.submitDeposit(depositInfo);
      return { txHash: result.txHash };
    },
    onApprovalRequired: async (leg, approvalInfo) => {
      // ERC-20 approve() before deposit
      const result = await yourBackend.submitApproval(approvalInfo);
      return { txHash: result.txHash };
    },
  }}
>
```

## Deposit Info Object

The `depositInfo` parameter passed to `onDepositRequired` contains everything needed to execute the deposit:

| Field              | Type     | Description                                            |
| ------------------ | -------- | ------------------------------------------------------ |
| `calldata`         | `string` | ABI-encoded `depositLeg()` calldata (0x-prefixed)      |
| `escrowAddress`    | `string` | The escrow contract address to send the transaction to |
| `chainId`          | `number` | The blockchain network (e.g., 11155111 for Sepolia)    |
| `tokenAddress`     | `string` | The ERC-20 token contract address                      |
| `depositAmount`    | `string` | Amount in token smallest units                         |
| `approvalCalldata` | `string` | ABI-encoded `approve()` calldata                       |

Your backend can submit the `calldata` directly to `escrowAddress` on the specified `chainId`. No ABI knowledge needed.

## Approval Info Object

The `approvalInfo` parameter passed to `onApprovalRequired`:

| Field              | Type     | Description                      |
| ------------------ | -------- | -------------------------------- |
| `tokenAddress`     | `string` | The ERC-20 token to approve      |
| `escrowAddress`    | `string` | The spender (escrow contract)    |
| `amount`           | `string` | Amount to approve                |
| `approvalCalldata` | `string` | Pre-encoded `approve()` calldata |

## Backend Implementation

Your backend receives the deposit info and routes it to your custody provider:

```typescript theme={null}
// Express example
app.post('/api/deposit', async (req, res) => {
  const { calldata, escrowAddress, chainId } = req.body;

  // Option 1: Fireblocks
  const tx = await fireblocks.createTransaction({
    assetId: chainIdToFireblocksAsset(chainId),
    destination: { type: 'ONE_TIME_ADDRESS', oneTimeAddress: { address: escrowAddress } },
    extraParameters: { contractCallData: calldata },
  });

  // Option 2: BitGo
  const tx = await bitgo.sendTransaction({
    address: escrowAddress,
    data: calldata,
    chain: chainId,
  });

  // Option 3: Any signing infrastructure
  const tx = await yourSigner.sendTransaction({
    to: escrowAddress,
    data: calldata,
    chainId,
  });

  res.json({ txHash: tx.hash });
});
```

## Without Delegates (Tier 3)

If no delegates are provided and a wagmi provider is detected, the widget uses the connected wallet directly. No backend needed.

```tsx theme={null}
<WagmiProvider config={wagmiConfig}>
  <KeystoneProvider sessionToken={token}>
    {/* useDeposit will use the connected wallet */}
  </KeystoneProvider>
</WagmiProvider>
```

## Without Delegates and No Wallet (Tier 1)

If no delegates and no wallet context, the deposit button is hidden. The settlement displays as view-only, and your backend handles deposits entirely through the SDK.
