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

# Funded trade plans

> One buy order signed up front with a hard cost ceiling, and optionally funded from USDC on another chain first.

> [!WARNING]
>
> **Preview.** Trade plans aren't available in production yet (it answers `503`), and they're still being validated on staging. Fields and behavior may change before release. Ask Kairos before you build on them.

A trade plan is **one buy order** that the wallet authorizes in a single batch of signatures. Kairos then runs it in a fixed order:

1. **Fund it, if needed.** When the venue account is short, Kairos bridges in exactly the shortfall from the same wallet's USDC on Polygon or Arbitrum, using gasless permits. Nothing is sent from the wallet.
2. **Trade.** Only once the funds have arrived and their block is final does Kairos submit the order the wallet signed.

Every plan pins a **cost ceiling** (`tradeBudget.maxDebitRaw`) and the [fee terms](https://docs.kairos.trade/wallets/fees.md), and the wallet signs them.

| Use a trade plan when | Use an [order action](https://docs.kairos.trade/wallets/trade.md#place-an-order) when |
| - | - |
| The wallet's collateral is on another chain and you want one flow that funds and trades | The collateral is already on the venue |
| You charge a [partner fee](https://docs.kairos.trade/wallets/fees.md) | You need sells, cancels, setup or sign-in |
| You want every signature up front, with explicit fee and cost ceilings | You want the step-by-step action flow |

The wallet must be [set up](https://docs.kairos.trade/wallets/setup.md) on the venue first. Plans are buy-only, and there's no way to cancel a plan once it's executed.

## With the SDK

```js theme={null}
const plan = await kairos.wallet.tradePlans.run({
  wallet,
  order: {
    market: 'polymarket:0x…',
    outcome: 'Yes',
    side: 'buy',
    price: '0.05',
    size: '10',
    timeInForce: 'GTC',
    postOnly: true,
    clientOrderId: 'order-1042',
  },
  // Only when the venue account may be short:
  funding: {
    sourceChainId: '42161',
    sourceToken: '0xaf88d065e77c8cc2239327c5edb3a432268e5831', // USDC on Arbitrum
    maxAmountRaw: '750000',
    maxGasCostRaw: '0',
    slippageBps: 50,
  },
}, { maxDebitRaw: '600000' }) // refuse to sign if the plan could cost more

plan.status // final: 'completed' means the venue accepted the order. Check it
```

`run` prepares the plan, checks it against what you asked for, asks the wallet for each signature (switching chains as needed), executes it, and waits until it ends.

## Over HTTP

### 1. Prepare

```http theme={null}
POST /v3/trade-plans
X-Kairos-Partner-Id: <partner id>
X-Kairos-Wallet-Api: 2
Authorization: SIWE <token>
Idempotency-Key: 7f0c2d0e8a7b4c35a1b0f3f1c9e2d4a6
Content-Type: application/json
```

```json theme={null}
{
  "wallet": "0x4a62316623ad457f02cdc5d997ded67a383ec569",
  "interactionMode": "single_batch",
  "order": {
    "market": "polymarket:0x<64-hex condition id>",
    "outcome": "Yes",
    "side": "buy",
    "price": "0.05",
    "size": "10",
    "timeInForce": "GTC",
    "postOnly": true,
    "clientOrderId": "order-1042"
  },
  "funding": {
    "sourceChainId": "42161",
    "sourceToken": "0xaf88d065e77c8cc2239327c5edb3a432268e5831",
    "maxAmountRaw": "750000",
    "maxGasCostRaw": "0",
    "slippageBps": 50
  },
  "tradeSubmitBefore": "2026-10-28T12:04:00Z",
  "submission": { "funding": "kairos", "trade": "kairos" }
}
```

| Field | Rules |
| - | - |
| `wallet` | The signing address |
| `ctfDependencies` | Optional, Polymarket only. Up to 16 [on-chain intent](https://docs.kairos.trade/wallets/onchain.md) ids (merge or redeem) whose pUSD this buy may count on before it lands |
| `interactionMode` | Required. `single_batch` |
| `order.market` | `venue:market_id`: `polymarket:0x…` (64 hex), `predictfun:<digits>` or `hyperliquid:<digits>` |
| `order.side` | `buy` only |
| `order.price`, `order.size` | **Canonical** decimal strings: `"0.5"`, not `"0.50"`; `"2"`, not `"2.0"`. Price strictly between 0 and 1. Size must fit the venue's share lot |
| `order.timeInForce` | `GTC`, `GTD`, `FOK`, `FAK`, `IOC`. Hyperliquid: `GTC`, `FAK`, `IOC`, whole shares, up to 5 price decimals, `clientOrderId` of `0x` + 32 lowercase hex |
| `order.postOnly` | Required boolean. `true` only with `GTC` or `GTD` |
| `order.clientOrderId` | Required. 1–128 printable ASCII characters, no spaces, unique per wallet and venue |
| `order.outcome` | The outcome name, up to 64 characters |
| `order.expiresAt` | RFC 3339, whole seconds. Required for `GTD`, refused otherwise. Later than `tradeSubmitBefore` |
| `funding` | Omit or `null` when the venue account already holds enough. See [funding routes](#funding-routes) |
| `funding.sourceChainId`, `funding.sourceToken` | `"137"` or `"42161"`, and that chain's native USDC. See [funding routes](#funding-routes) |
| `funding.maxAmountRaw` | The most the source chain may be debited, bridge fees included |
| `funding.maxGasCostRaw` | Required integer string. Use `"0"`: funding is gasless |
| `funding.slippageBps` | 0–10000 |
| `tradeSubmitBefore` | RFC 3339 UTC, whole seconds, at most **5 minutes** out. The last moment Kairos may submit the trade |
| `submission.funding` | `kairos` (default): Kairos submits the funding permits. `client`: you submit them to Relay yourself |
| `Idempotency-Key` | Required. The same key and body return the same plan; the same key with a different body answers `409 Idempotency-Key already names a different request` |

All field names are camelCase. Unknown fields are refused.

### 2. Check the plan

The response is the plan, with `status: "needs_signatures"`. **Check it before the wallet signs anything:**

* `order` is the order Kairos will place. It can differ from your request in two ways only: `outcome` comes back in the venue's canonical spelling (on Hyperliquid, its `#<digits>` coin), and on Predict.fun a `GTC` order becomes a `GTD` (or a `GTD`'s `expiresAt` moves earlier) so it ends with the wallet's Predict.fun session. Refuse anything else.
* `tradeBudget.maxDebitRaw` is the most the trade can take from the venue account. Show it, and the fees, to the user.
* `funding.amountInRaw` is what the source chain will be debited. It is at most your `maxAmountRaw`.
* `executeBefore` is the deadline to sign and execute: at most 2 minutes out when funded (the bridge quote's life), otherwise `tradeSubmitBefore`.

```json theme={null}
{
  "contractVersion": 1,
  "id": "8d0e…",
  "planHash": "0xb3d5…6953",
  "status": "needs_signatures",
  "venue": "polymarket",
  "wallet": "0x4a62…c569",
  "tradingWallet": "0x…",
  "order": { "market": "polymarket:0x…", "outcome": "Yes", "side": "buy", "price": "0.05", "size": "10", "timeInForce": "GTC", "postOnly": true, "clientOrderId": "order-1042" },
  "tradeBudget": { "notionalRaw": "500000", "maxPlatformFeeBps": 75, "maxCollateralFeeRaw": "16250", "maxDebitRaw": "516250", "existingSpendableRaw": "0", "…": "…" },
  "funding": { "sourceChainId": "42161", "amountInRaw": "521800", "requiredArrivalRaw": "516250", "minimumArrivalRaw": "516250", "fundingSubmitBefore": "2026-10-28T12:02:00Z", "…": "…" },
  "executeBefore": "2026-10-28T12:02:00Z",
  "tradeSubmitBefore": "2026-10-28T12:04:00Z",
  "requests": [ { "id": "plan", "…": "…" }, { "id": "funding-1", "…": "…" }, { "id": "funding-2", "…": "…" }, { "id": "trade", "…": "…" } ],
  "execution": { "committed": false, "phase": "collecting", "fundingStatus": "not_started", "tradeStatus": "not_started" },
  "error": null
}
```

### 3. Sign each request

`requests` lists every signature the plan needs, each with its own `chainId`:

| `id` | Signs | Chain |
| - | - | - |
| `plan` | The plan itself: `KairosTradePlan` / `TradePlan(wallet, partnerId, audience, planId, planHash, executeBefore, tradeSubmitBefore)` | The venue's chain (`0x89`, `0x38`, or Hyperliquid's signing chain) |
| `funding-1` | Relay `MulticallAuthorization` (funded plans only) | The source chain (`0x89` or `0xa4b1`) |
| `funding-2` | USDC `ReceiveWithAuthorization` for exactly `funding.amountInRaw` (funded plans only) | The source chain |
| `trade` | The venue order, the same document an [order action](https://docs.kairos.trade/wallets/signing.md) asks for | The venue's chain |

Every request is `eth_signTypedData_v4`. Switch the wallet to `chainId`, pass `request` to the wallet, and keep each signature. Signatures must be 65 bytes from the wallet's own key, with `v` of 0, 1, 27 or 28 and a low `s` (standard wallets do this). An unfunded plan needs 2 signatures; a funded one needs 4.

### 4. Execute

Send every signature in one call. (Or send some first with `POST /v3/trade-plans/{id}/answers`, which takes the same body.)

```http theme={null}
POST /v3/trade-plans/{id}/execute
```

```json theme={null}
{
  "planHash": "0xb3d5…6953",
  "answers": [
    { "kind": "signature", "requestId": "plan",      "signature": "0x…" },
    { "kind": "signature", "requestId": "funding-1", "signature": "0x…" },
    { "kind": "signature", "requestId": "funding-2", "signature": "0x…" },
    { "kind": "signature", "requestId": "trade",     "signature": "0x…" }
  ]
}
```

Execute checks the balances, reserves the plan's amounts so another plan can't spend them, and commits. From then on Kairos runs the plan even if your client goes away. Calling execute again returns the plan as it is.

### 5. Follow it

`GET /v3/trade-plans/{id}` every couple of seconds, with the same headers, until `status` is final.

## States

| `status` | Means |
| - | - |
| `needs_signatures` | Waiting for signatures |
| `ready_to_execute` | Every signature is in. Call execute |
| `funding_pending` | Committed; waiting for the funds to arrive |
| `reconciling` | Funding or the trade is in flight. `error.code` is `OUTCOME_UNKNOWN` with `nextAction: "reconcile"`: that's normal, keep polling |
| `trade_pending` | Funds arrived; the order is being submitted |
| `completed` | **Final.** The venue accepted the order. `execution.orderId` is the Kairos order id; follow fills in [orders](https://docs.kairos.trade/wallets/portfolio.md). Accepted doesn't mean filled |
| `expired` | **Final.** Not executed by `executeBefore`, or not submitted by `tradeSubmitBefore` |
| `failed` | **Final.** The trade failed before any funding moved. `error.code` says why |
| `funded_trade_failed` | **Final.** The funds arrived but the trade failed |
| `funded_trade_expired` | **Final.** The funds arrived after `tradeSubmitBefore` |

`execution` adds detail: `phase` (`collecting`, `funding`, `trade`, `finished`), `fundingStatus`, `tradeStatus`, `confirmedArrivalRaw`, `orderId`, `venueOrderId`, `venueOrderStatus`.

> [!WARNING]
>
> **Funding and trade settle separately.** If the funds arrive and the trade then fails or expires, the funds stay in the venue trading account (`tradingWallet`). Nothing is bridged back. The user can trade with them, or withdraw them from the venue.

### `error.code` on a plan

| `code` | Means |
| - | - |
| `OUTCOME_UNKNOWN` | Still in flight. Keep polling |
| `PLAN_EXPIRED` | Not executed in time |
| `TRADE_EXPIRED` | The trade wasn't submitted by `tradeSubmitBefore`, or the funds arrived late |
| `FUNDING_SHORTFALL` | Less arrived than the trade needs |
| `COLLATERAL_CHANGED` | The venue balance fell below `maxDebitRaw` before submission |
| `VENUE_REJECTED` | The venue refused the signed order |
| `PREFLIGHT_REFUSED` | A pre-trade check refused the order |
| `NATIVE_NOT_DISPATCHED` | The order's authorization expired before it could be submitted |

## Funding routes

| From | To Predict.fun (BNB Chain USDT) | To Polymarket (Polygon pUSD) | To Hyperliquid (spot USDC) |
| - | - | - | - |
| USDC on Polygon (`137`, `0x3c499c542cef5e3811e1192ce70d8cc03d5c3359`) | Yes | No | Yes |
| USDC on Arbitrum (`42161`, `0xaf88d065e77c8cc2239327c5edb3a432268e5831`) | Yes | Yes | Yes |

Kairos asks for exactly the shortfall: `maxDebitRaw` minus what the venue account already holds. If no route is available, prepare answers `503 no verified gasless funding route is available`. A short account with no `funding` answers `409 collateral is short; supply an explicit funding source`.

With `submission.funding: "client"`, Kairos keeps no funding signatures. Execute with the `plan` and `trade` signatures only. Each funding request carries `externalSubmission` (Relay's endpoint, `kind`, `api` and `requestId`): after execute, you post the signed permits to Relay yourself, `funding-1` first. Then tell Kairos with `POST /v3/trade-plans/{id}/reports`, using each request's `requestHash` and `externalSubmission.requestId`:

```json theme={null}
{ "planHash": "0x…", "reports": [ { "requestId": "funding-1", "requestHash": "0x…", "providerRequestId": "0x…" } ] }
```

A report is only a hint to check sooner. Kairos confirms funding from the destination chain itself.

Hyperliquid funding goes to mainnet only. If the bridge never delivers, the plan stays `reconciling`: there is no failed-funding state. Relay refunds a failed bridge to `funding.refundRecipient`, the wallet. Contact Kairos if a plan stays there.

## Errors

Errors are `{ "error": "message" }`. The one exception is unpaid fees, which also carries `code: "FEE_SETTLEMENT_REQUIRED"` ([fees](https://docs.kairos.trade/wallets/fees.md#how-fees-are-collected)).

| Status | Means |
| - | - |
| `400` | Bad input (`invalid trade plan: …`), `Idempotency-Key is required`, `invalid Idempotency-Key`, a size that doesn't fit the share lot, or a changed answer (`a previously accepted answer cannot change`) |
| `401` | A signature that doesn't recover to `wallet` (`signature does not authorize this plan request`), or an auth failure |
| `403` | Missing scope, or the venue isn't enabled for this account |
| `404` | `trade plan not found` |
| `409` | Wallet not set up (`set up the venue wallet before preparing a trade plan`, `renew the Predict.fun wallet session`: run Predict.fun setup again), `Idempotency-Key already names a different request`, `clientOrderId already names another trade plan`, `collateral is short; supply an explicit funding source`, `FEE_SETTLEMENT_REQUIRED`, `the plan is missing required signatures`, `the plan no longer accepts this operation`, or `trade plan changed; read it before retrying` |
| `410` | `plan authorization has expired`: past `executeBefore`. Prepare a new plan |
| `422` | Unknown, missing or mistyped JSON fields (a plain-text body, not JSON), or Kairos's fee plus yours over 10% |
| `503` | Not available on this deployment (`trade plans are disabled`), no funding route, or a venue or price feed briefly unavailable |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.