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

# Split, merge and redeem

> On-chain intents: turn pUSD into a full set of outcome shares, merge a set back, or redeem winning shares, all without gas.

> [!WARNING]
>
> **Preview.** On-chain intents run on staging (`https://staging-execution.kairos.trade`). Production answers `503` until release. Fields and behavior may still change.

An on-chain intent runs one operation on the wallet's Polymarket trading wallet:

| `operation` | Does | Trading wallet pays | Trading wallet gets |
| - | - | - | - |
| `split` | Splits pUSD into one share of each outcome | `amountRaw` pUSD | `amountRaw` of **both** outcome tokens |
| `merge` | Merges a full set back into pUSD | `amountRaw` of both outcome tokens | `amountRaw` pUSD |
| `redeem` | Redeems shares of a resolved market | `amountRaw` of the chosen `outcome` | Its payout in pUSD |

Kairos builds the exact calls, the wallet signs twice, and the batch runs with no gas for the wallet. Kairos marks the intent done only once it has checked the final on-chain result itself.

**Supported:** binary Polymarket markets on Polygon, for a wallet [set up on Polymarket](https://docs.kairos.trade/wallets/setup.md). Redeem works on markets on Polymarket's newer exchange (markets whose orders sign with exchange version `3`). Other markets answer `400`.

## With the SDK

```js theme={null}
await kairos.wallet.onchain.split(wallet, 'polymarket:0x…', '1000000') // 1 pUSD → 1 Yes + 1 No
await kairos.wallet.onchain.merge(wallet, 'polymarket:0x…', '1000000')
await kairos.wallet.onchain.redeem(wallet, 'polymarket:0x…', '4500000', { outcome: 0 })
```

Each call prepares the intent, asks the wallet for its two signatures, executes it, and waits until it's confirmed. To find what you can redeem, read [positions](https://docs.kairos.trade/wallets/portfolio.md): `resolved` rows with `redeemable: true`.

## Over HTTP

### 1. Prepare

```http theme={null}
POST /v3/onchain-intents
X-Kairos-Partner-Id: <partner id>
X-Kairos-Wallet-Api: 2
Authorization: SIWE <token>
Idempotency-Key: split-0x4a62-1042
Content-Type: application/json
```

```json theme={null}
{
  "wallet": "0x4a62316623ad457f02cdc5d997ded67a383ec569",
  "account": "0x769cdb173cfb9cf845445d31aa923a02f9200891",
  "market": "polymarket:0x<64-hex condition id>",
  "operation": "split",
  "amountRaw": "1000000",
  "submissionOwner": "kairos",
  "executeBefore": "2026-10-28T12:04:00Z"
}
```

| Field | Rules |
| - | - |
| `wallet` | The signing address, **in lowercase** |
| `account` | The wallet's Polymarket trading wallet, in lowercase: `venues.polymarket.tradingWallet` from [wallet status](https://docs.kairos.trade/wallets/setup.md#2-fund-the-wallet). It must already be deployed |
| `market` | `polymarket:0x…` or a bare condition id (`0x` + 64 hex) |
| `operation` | `split`, `merge` or `redeem` |
| `amountRaw` | A whole number of base units as a string, more than 0, no leading zeros. pUSD and outcome tokens both have 6 decimals, so `"1000000"` is 1 pUSD or 1 share |
| `outcome` | `0` or `1`. Required for `redeem`; not allowed otherwise |
| `submissionOwner` | Required. `kairos`: Kairos submits the signed batch. `client`: you submit it yourself (see [below](#submitting-it-yourself)) |
| `executeBefore` | RFC 3339 UTC, whole seconds, in the future and at most **5 minutes** out. It is also the batch's on-chain deadline |
| `dependencies` | Optional. Up to 16 ids of earlier intents on the same trading wallet that must confirm first, for example a split before a merge |
| `Idempotency-Key` | Required header. 1–128 printable ASCII characters, no spaces. The same key and body return the same intent; a different body answers `409` |

All field names are camelCase. Unknown fields are refused.

### 2. Check and sign

The response holds the intent and two signing requests:

```json theme={null}
{
  "intent": {
    "terms": {
      "id": "…", "signer": "0x4a62…", "account": "0x769c…", "chainId": "137",
      "market": "polymarket:0x…", "operation": "split",
      "effects": [
        { "token": { "kind": "erc20", "contract": "0xc011a7e12a19f7b1f670d46f03b03f3342e82dfb" }, "direction": "debit", "amountRaw": "1000000" },
        { "token": { "kind": "erc1155", "contract": "0x…", "tokenId": "…" }, "direction": "credit", "amountRaw": "1000000" },
        { "token": { "kind": "erc1155", "contract": "0x…", "tokenId": "…" }, "direction": "credit", "amountRaw": "1000000" }
      ],
      "maxWalletGasCostRaw": "0",
      "executeBefore": "2026-10-28T12:04:00Z",
      "…": "…"
    },
    "intentHash": "0x…",
    "status": "needs_signatures"
  },
  "consentRequest": { "method": "eth_signTypedData_v4", "params": ["0x4a62…", "{…OnchainIntent…}"] },
  "nativeRequest":  { "method": "eth_signTypedData_v4", "params": ["0x4a62…", "{…Batch…}"] },
  "submission": { "…": "…" }
}
```

* `terms.effects` is the **exact** set of token movements the batch makes on the trading wallet. Show them to the user, and refuse to sign if they're not what you asked for.
* `consentRequest` is the wallet's consent to these terms: domain `KairosOnchainIntent` v1 on chain 137, `primaryType: "OnchainIntent"`, message `{ signer, partnerId, audience, intentId, intentHash, executeBefore }`.
* `nativeRequest` is the trading wallet's own `Batch` (domain `DepositWallet` v1, chain 137, `verifyingContract` = the trading wallet). It holds two calls: one approval, then the operation.
* Both must be signed by the wallet's own key: 65 bytes, low `s`, no smart-account signatures. The wallet must be on Polygon (`0x89`). The documents carry `chainId` as the string `"137"`.

> [!NOTE]
>
> The approval in the batch lets Polymarket's split, merge and redeem contract act on the trading wallet: a pUSD allowance of exactly `amountRaw` for a split, or a standing `setApprovalForAll` on the outcome tokens for merge and redeem.

### 3. Execute

```http theme={null}
POST /v3/onchain-intents/{id}/execute
```

```json theme={null}
{ "intentHash": "0x…", "consentSignature": "0x…", "nativeSignature": "0x…" }
```

Execute checks both signatures, then the trading wallet's balance at the latest final block. It reserves the debited tokens and commits. Kairos then submits the batch once. With `submissionOwner: "client"`, send only `consentSignature`; sending `nativeSignature` too answers `400`.

The response is the same shape as prepare. Executing again with the same signatures returns the intent unchanged.

### 4. Follow it

`GET /v3/onchain-intents/{id}` every few seconds until `status` is `confirmed`. The response then carries `proof`: the transaction hash, block, and the effects found on chain.

| `status` | Means |
| - | - |
| `needs_signatures` | Waiting for execute |
| `ready` | Committed; Kairos is about to submit |
| `awaiting_client` | Committed; waiting for you to submit (`client` only) |
| `reconciling` | Submitted; waiting for the final block |
| `confirmed` | **Final.** Done, with `proof` |

> [!WARNING]
>
> **Never broadcast the batch from the wallet itself.** The batch must be submitted gaslessly as given. A copy sent from the owner's address never confirms, and the intent's reserved balance stays reserved.

## Submitting it yourself

With `submissionOwner: "client"`, the prepare response's `submission` describes the request to make: `endpoint`, `method` and a `body` without a signature. Add `"signature": "<native signature>"` to `body` and send it. Then, optionally, report the transaction hash:

```json theme={null}
POST /v3/onchain-intents/{id}/reports
{ "intentHash": "0x…", "transactionHash": "0x…" }
```

A report is only a hint. Kairos finds and checks the transaction itself, with or without it. Unless you already run your own Polymarket relayer integration, use `submissionOwner: "kairos"`.

## Chaining intents

To merge shares a split is about to create, prepare the split, then prepare the merge with `"dependencies": ["<split id>"]`. Execute the merge only after the split is `confirmed`: until then, execute answers `409`.

## Errors

Errors are `{ "error": "message" }`.

| Status | Means |
| - | - |
| `400` | Bad input (`invalid on-chain intent request`), `a valid Idempotency-Key is required`, a non-Polymarket or non-binary market, `account must be the registered signer's Polymarket deposit wallet`, `deposit wallet is not deployed at finality`, redeeming a losing outcome (`redemption must produce a positive bounded payout`), redeem on an older market, or a `nativeSignature` that doesn't match `submissionOwner` |
| `401` | `signature does not authorize this plan request`: a signature that doesn't recover to `wallet`. Also a missing, expired or foreign sign-in token |
| `403` | Missing scope (`trade:execute` to prepare, execute and report; `trade:read` to read), or `wallet partner is disabled` |
| `404` | `on-chain intent not found`, `wallet not found` |
| `409` | Wallet not set up, `Idempotency-Key already names another intent`, a dependency not confirmed yet (`the plan is missing required signatures`), a stale `intentHash` or a report on a `kairos` intent (`the plan no longer accepts this operation`), `Polymarket V2 trading is disabled`, or `trade plan changed; read it before retrying` (the balance changed or is too low: read the intent and try again) |
| `410` | `plan authorization has expired`: past `executeBefore`. Prepare a new intent |
| `422` | Unknown, missing or mistyped JSON fields (a plain-text body, not JSON) |
| `503` | `trade plans are disabled` (production, until release), `this node is not the intent coordinator` (use the base URL Kairos gave you), `canonical Polygon state is unavailable`, or `execution is disabled for this operation`. On `redeem` it can also mean **the market hasn't resolved on-chain yet** |


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