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

# Escrow wallets API

> Create escrow wallets, propose transactions and swaps from them, and poll the result, over HTTP with an API key.

An escrow wallet moves funds only through a proposal: the proposal is simulated, checked against the wallet's limits, and judged against its mandate. The concepts are on the [Escrow wallets](/escrow) page. This page is the HTTP reference. Every route takes an API key, and every `{id}` below accepts a wallet's id or its name.

Not available over HTTP: changing a wallet's rules, x402 payments, withdrawals and confirming a held proposal. Use the dashboard or the [MCP tools](/api-reference/mcp-tools) for rules and x402. Only the owner, signed in to the dashboard, can confirm or withdraw.

## List wallets

`GET /api/v1/wallets`. Free. Archived wallets are left out unless you add `?includeArchived=true`.

Returns `{ "wallets": [...] }`. Each wallet:

| Field | |
| - | - |
| `id`, `name` | |
| `chain` | `base`, `solana`, `starknet`, or `ethereum` for wallets created before Base. |
| `mode` | `autonomous` or `safe`. |
| `custody` | `server` (a custodian holds the key) or `embedded` (the owner's own wallet signs). |
| `address` | Where to send funds. |
| `mandate` | The rules the judge checks, or `null`. |
| `balanceUsd` | Priced total, or `null`. |
| `tokenCount` | Number of token balances. |
| `pendingCount` | Proposals waiting for the owner. |
| `archivedAt`, `createdAt` | ISO timestamps. |

## Create a wallet

`POST /api/v1/wallets`. Free. Returns `201` with the wallet row above.

```json theme={null}
{
  "name": "trading",
  "chain": "base",
  "mode": "autonomous",
  "mandate": "Swaps between USDC and WETH only. Never send funds to a new address.",
  "limits": { "maxPerTxUsd": 200, "dailyCapUsd": 500 }
}
```

| Field | |
| - | - |
| `name` | Required. Unique on the account. |
| `chain` | `base`, `solana` or `starknet`. Or send `chainType`: `ethereum` (creates a Base wallet), `solana` or `starknet`. |
| `mode` | Required. `autonomous` executes an ALLOW whose simulation is clean. `safe` holds every proposal for the owner. Safe mode is available on Base and Starknet. |
| `mandate` | Plain-language rules. |
| `limits` | Optional: `maxPerTxUsd`, `dailyCapUsd` (rolling 24 hours), `tokenAllowlist` (token contract addresses), `recipientAllowlist` (addresses). Checked against the simulated effects before the judge runs. |

Chain and mode cannot be changed later. An account can have 25 wallets.

| Status | `error` | When |
| - | - | - |
| `400` | `invalid JSON body`, `name is required`, a chain or mode message | Invalid body. |
| `400` | `create_failed` | The wallet could not be created. `message` says why. |
| `503` | `escrow_unconfigured` | Escrow wallets are not configured on this deployment. |

## Balances

`GET /api/v1/wallets/{id}/balances`. Free.

```json theme={null}
{
  "walletId": "…",
  "name": "trading",
  "chain": "base",
  "address": "0x…",
  "balanceUsd": 125.4,
  "balances": [
    {
      "symbol": "USDC",
      "name": "USD Coin",
      "logoUrl": null,
      "tokenAddress": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
      "amount": "125400000",
      "amountFormatted": "125.4",
      "decimals": 6,
      "usdValue": 125.4,
      "caip2": "eip155:8453"
    }
  ]
}
```

`amount` is in base units. Use `amountFormatted`.

## Propose a transaction

`POST /api/v1/wallets/{id}/propose` with `{ "input": {...}, "note"?: "..." }`.

`note` is context for the judge. It is treated as untrusted narration that cannot grant permission, and it is cut at 2,000 characters.

Every `input` has a `kind` and a chain: either `caip2` (`eip155:8453`, `eip155:1`, `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp`, `starknet:SN_MAIN`) or `chain` (`base`, `ethereum`, `solana`, `starknet`). It must be the wallet's chain.

<AccordionGroup>
  <Accordion title="transfer">
    A named asset, in whole units, sent as-is with no conversion. On EVM and Starknet a known asset becomes the plain transfer call. On Starknet the asset must be STRK, ETH, USDC or USDT. On Solana a named transfer always waits for the owner, because only the owner can sign it.

    ```json theme={null}
    {
      "input": {
        "kind": "transfer",
        "chain": "base",
        "transfer": { "asset": "USDC", "amount": "25", "to": "0x1111111111111111111111111111111111111111" }
      },
      "note": "Monthly payment to the design contractor."
    }
    ```

    `amount` must be a plain decimal string greater than zero: `"1.5"`, not `"1e3"`.
  </Accordion>

  <Accordion title="evm_transaction">
    One transaction. `value` may be decimal or hex. `from`, if sent, must be the wallet. Fee fields above 1 ETH are refused.

    ```json theme={null}
    {
      "input": {
        "kind": "evm_transaction",
        "caip2": "eip155:8453",
        "transaction": {
          "to": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
          "value": "0",
          "data": "0xa9059cbb00000000000000000000000011111111111111111111111111111111111111110000000000000000000000000000000000000000000000000000000001312d00"
        }
      }
    }
    ```
  </Accordion>

  <Accordion title="evm_calls">
    A batch executed together, for example an approval and the call that uses it.

    ```json theme={null}
    {
      "input": {
        "kind": "evm_calls",
        "chain": "base",
        "calls": [
          { "to": "0x…", "data": "0x…" },
          { "to": "0x…", "data": "0x…", "value": "0" }
        ]
      }
    }
    ```
  </Accordion>

  <Accordion title="solana_transaction">
    A base64 serialized transaction.

    ```json theme={null}
    { "input": { "kind": "solana_transaction", "chain": "solana", "transaction": "AQAAAA…" } }
    ```
  </Accordion>

  <Accordion title="starknet_calls">
    A multicall executed as one transaction. `calldata` holds felts as hex or decimal strings.

    ```json theme={null}
    {
      "input": {
        "kind": "starknet_calls",
        "chain": "starknet",
        "calls": [
          { "contractAddress": "0x…", "entrypoint": "transfer", "calldata": ["0x…", "1000000", "0"] }
        ]
      }
    }
    ```

    For a STRK20 private action, add `"proof": { "facts": [...], "data": "..." }`: 1 to 64 hex felts and a base64 proof under 4 MB. The proof is not stored, so a proposal that waits for the owner needs a fresh one.
  </Accordion>
</AccordionGroup>

`x402_payment` is refused here. Chance builds x402 payments itself from terms it fetches; use the `escrow_pay_x402` MCP tool.

### Result

Status `200` with a proposal result:

| Field | |
| - | - |
| `txId` | Proposal id. Poll it below. |
| `status` | `blocked`, `awaiting_user`, `executing`, `executed` or `failed`. |
| `verdict` | `ALLOW`, `BLOCK`, `ESCALATE` or `null`. |
| `reasoning` | The judge's reason, or the limit that blocked it. |
| `requestHash` | Hash of the canonical payload the wallet will sign. Execution recomputes it and refuses on a mismatch. |
| `simulation` | The simulation report: `status` (`clean`, `reverted`, `unavailable`), asset `changes` with USD values, `warnings`. |
| `approvalUrl` | Set when the proposal waits for the owner. Send it to them. |
| `txHash` | Set when it executed. |
| `scanId` | Verification id, for `GET /api/v1/intent/{id}`. `null` when no judge ran. |
| `creditsRemaining` | `null` when no credit was charged. |
| `proof` | The receipt, or `null` when no judge ran. See [Receipts](/concepts/proofs). |

How a proposal is routed:

* A wallet limit fails: `blocked`, `verdict: "BLOCK"`, no judge, no credit.
* The judge returns `BLOCK`: `blocked`. The escrow judge is told to block a transaction whose simulation reverts.
* `ALLOW` on an autonomous wallet, with a clean simulation that the wallet's limits can be checked against, for an action Chance can sign: executes now. `status` is `executed` with a `txHash`, `executing` while no transaction hash is known yet, or `failed`.
* Anything else, including every proposal on a safe wallet and every `ESCALATE`: `awaiting_user` with an `approvalUrl`. The link expires after the approval window, 60 minutes by default.

An API key cannot confirm a held proposal. Only the owner can, signed in.

## Propose a swap

`POST /api/v1/wallets/{id}/swap`. Base and Ethereum wallets only. Chance quotes the swap on the Uniswap Trading API, builds the batch (an approval for exactly the sell amount when one is needed, then the swap) and proposes it as above.

```json theme={null}
{ "sell": "USDC", "buy": "WETH", "amount": "25", "chain": "base", "slippageBps": 50 }
```

| Field | |
| - | - |
| `sell` | A 0x token address, `ETH`, `WETH`, `USDC`, or a symbol the wallet holds on this chain. |
| `buy` | A 0x token address, `ETH`, `WETH` or `USDC`. |
| `amount` | How much to sell, in the sell token's units. |
| `chain` | `base` or `ethereum`. Must be the wallet's chain. |
| `slippageBps` | 1 to 1,000. Default 50. It sets the minimum output written into the signed swap. |
| `note` | Context for the judge, untrusted. |

Returns the proposal result plus `swap`: `routing`, `sell` and `buy` (`address`, `decimals`, `symbol`), `amountIn`, `amountOut`, `minimumOut` in base units and their `…Human` versions, `priceImpactPct`, `slippagePct`, `gasFeeUSD`, `routeString`, `approvalNeeded`, `approvalSpender`.

## Proposal status

`GET /api/v1/escrow-transactions/{txId}`. Free.

```json theme={null}
{
  "id": "…",
  "status": "awaiting_user",
  "verdict": "ESCALATE",
  "txHash": null,
  "approvalUrl": "https://harness.chance.cc/approve/…",
  "explorerUrl": null
}
```

`status` is one of `verifying`, `blocked`, `awaiting_user`, `executing`, `executed`, `failed`, `rejected`, `expired`. A held proposal past its window reads `expired`. `approvalUrl` is set only while `awaiting_user`.

## Errors

| Status | `error` | When |
| - | - | - |
| `400` | `invalid JSON body`, `input is required` | Bad body. |
| `400` | `invalid_input` | `input` failed validation. `message` says which field. |
| `400` | `propose_failed` | The wallet cannot take this action: wrong chain, an unsupported asset or kind, a `from` that is not the wallet. No credit charged. |
| `400` | `swap_failed` | Swap: unknown token, bad amount or slippage, wallet on another chain, or a quote the service refused. |
| `401` | | Missing, unknown or revoked API key. |
| `402` | `propose_failed` | Out of credits. The proposal is recorded as blocked. |
| `404` | `not_found` | No such wallet or proposal on this account. |
| `409` | `propose_failed` | The wallet is archived, or it is a safe wallet from before self-custodial safe mode and can no longer transact. |
| `500` | `propose_failed`, `swap_failed` | Unexpected failure. |
| `502` | `propose_failed` | The judge run failed. The credit is refunded and the proposal is blocked. |
| `502` | `swap_failed` | The quote service could not be reached. |
| `503` | `swap_failed` | Swaps are not configured on this deployment. |
