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

# Build transactions

> Quote and build the unsigned transactions that deposit USDC into a vault or create a redeem request on it.

Two actions cover the vault transactions your users can make through the API:

* [`transaction.quote`](/api-reference/transaction-quote) prices a transaction and says whether the vault takes it now. It builds nothing.
* [`transaction.build`](/api-reference/transaction-build) runs every check, then returns the unsigned transactions and simulates them.

Both take one of two transaction types in the body's `type`:

| `type` | What it does | Vault function |
| - | - | - |
| `DEPOSIT` | Deposits USDC. The vault mints the shares in the same transaction. | `syncDeposit` |
| `REDEEM_REQUEST` | Creates a redeem request. The shares leave the wallet at once, and the vault settles the request later. | `requestRedeem` |

A redeem request is the only way out of a Kamui vault. The API never signs or sends a transaction: your users sign and send the transactions it builds, with your own wallet stack.

<Note>
  Kamui enables transaction building for each API client. Until it is enabled for yours, both actions answer `403 actions_not_enabled`: ask Kamui. Kamui can also switch transaction building off for every client. Both actions then answer `503 actions_disabled`, and the read actions keep answering.
</Note>

## The flow

<Steps>
  <Step title="Quote (optional)">
    Call `transaction.quote` with the vault `slug`, the `type` and the amount. Add the `sender` to see the approval step of a deposit, or the wallet checks of a redeem request.

    ```bash theme={null}
    curl -X POST "https://api.kamui.finance/functions/v1/external-api/transaction.quote" \
      -H "Content-Type: application/json" \
      -H "x-partner-api-key: $KAMUI_API_KEY" \
      -d '{ "type": "DEPOSIT", "slug": "stable-vault", "amount": "1000000000" }'
    ```

    The answer has the expected shares (`expectedShares`) or assets (`expectedAssets`), the fees, the `steps` and `availability`: whether the vault takes the transaction now and, if not, why.
  </Step>

  <Step title="Build">
    Call `transaction.build` with the same fields plus the `sender`, the wallet that will sign and send.

    ```bash theme={null}
    curl -X POST "https://api.kamui.finance/functions/v1/external-api/transaction.build" \
      -H "Content-Type: application/json" \
      -H "x-partner-api-key: $KAMUI_API_KEY" \
      -d '{ "type": "DEPOSIT", "slug": "stable-vault", "amount": "1000000000", "sender": "0x1111111111111111111111111111111111111111" }'
    ```

    The build runs the checks below in order, and the first failure answers. On success, `transactions` holds the unsigned transactions and `simulation` says how they were checked.
  </Step>

  <Step title="Sign and send">
    Send the transactions in `step` order, from `from`, on chain `chainId` (Ethereum mainnet). Wait for each receipt before you send the next.

    * Each transaction is `{ to, data, value }`. `to` is the vault, or the deposit asset for an approval. `value` is always `"0"`.
    * A deposit may need two transactions: an `approve` of exactly the deposit amount (never unlimited), then the `deposit`.
    * A redeem request is one transaction (`redeem_request`) and needs no approval: the sender owns the shares.
  </Step>

  <Step title="Follow up">
    Read the result with [`users.positions`](/api-reference/users-positions) and [`users.transactions`](/api-reference/users-transactions).
  </Step>
</Steps>

Every read behind a quote or a build is pinned to one block (`blockNumber`), and the simulation runs on top of it. Build again after a `409`, or when more than 10 minutes have passed since the build.

## Request fields

| Field | `DEPOSIT` | `REDEEM_REQUEST` | Notes |
| - | - | - | - |
| `type` | required | required | `"DEPOSIT"` or `"REDEEM_REQUEST"`. |
| `slug` | required | required | The vault slug. A legacy slug works; the answer carries the canonical one. |
| `amount` | required | — | USDC to deposit, in base units (6 decimals): `"1000000000"` is 1,000 USDC. |
| `shares` | — | required | Vault shares to put in the redeem request, in base units (18 decimals). |
| `sender` | quote: optional; build: required | quote: optional; build: required | The wallet that signs and sends. Not the zero address. |
| `receiver` | build only, optional | — | Receives the shares. It must equal `sender` (compared case-insensitively) and defaults to it. |
| `referral` | build only, optional | — | The referral address passed to `syncDeposit`. Defaults to the zero address. |
| `chainId` | optional | optional | Must be `1` when sent. |
| `skipSimulation` | build only, optional | build only, optional | `true` skips the simulation. Defaults to `false`. |

Bodies are strict: a field the type does not take (for example `shares` in a `DEPOSIT`), or a field named twice, answers `400 invalid_request`.

Amounts are positive integer strings, without sign, decimals, exponent or leading zeros. A deposit must be at least 1 USDC (`"1000000"`) and at most 1,000,000,000 USDC per request; otherwise it answers `400 amount_below_minimum` or `400 invalid_amount`, with the limit in `details`.

## Deposits (`DEPOSIT`)

A deposit calls `syncDeposit(amount, sender, referral)` on the vault: the vault mints the shares to the sender in the same transaction, at the vault price when the deposit executes. `expectedShares` uses the price at `blockNumber`, after any entry fee (`fees.entryBps`).

Who may deposit: the vault's onchain allow list (`isAllowed`) must accept the sender and the receiver.

`transaction.build` checks, in this order:

| Check | Error |
| - | - |
| The amount is at least the minimum and within the per-request limit. | `400 amount_below_minimum` / `400 invalid_amount` |
| `receiver`, when sent, equals `sender`. | `400 invalid_request` on `body.receiver` |
| The vault exists and is active. | `404 vault_not_found` / `409 vault_not_active` |
| The vault is not paused. | `409 vault_paused` |
| The vault accepts synchronous deposits and its NAV is valid. | `409 deposits_unavailable` |
| The vault's cap has room, counting the deposits waiting for settlement. | `409 deposit_cap_reached` |
| The vault's allow list accepts the sender and the receiver. | `403 wallet_not_allowed` |
| The sender holds the amount of USDC. | `422 insufficient_balance` |
| The transactions pass the simulation. | `422 simulation_failed` |

The transactions are an exact-amount `approve` of USDC when the sender's allowance is short, then the `deposit`.

`transaction.quote` runs no wallet check for a deposit. Its `availability.depositsOpen` says whether the vault takes deposits now; when it is `false`, `availability.reason` is `vault_paused`, `deposits_unavailable` or `deposit_cap_reached`. With a `sender`, `steps` includes `approve` when the allowance is short.

## Redeem requests (`REDEEM_REQUEST`)

A redeem request calls `requestRedeem(shares, sender, sender)` on the vault. The shares leave the wallet at once. The vault settles the request later, usually within `window.maxDays` days, and the assets then become claimable.

* `expectedAssets` is the shares' value at the current price, net of the exit fee (`fees.exitBps`). It is an estimate: the final amount is fixed at the NAV used when the redeem request settles.
* A wallet can have one redeem request waiting for settlement per vault.
* If the sender has a settled redeem request it has not claimed, `requestRedeem` pays those assets to the sender first, in the same transaction. `claimFirst` shows them (`assets` and `shares`); it is `null` otherwise.
* The API builds no claim. Once a redeem request settles, the wallet claims its assets in the Kamui app, or its next redeem request pays them out (`claimFirst`).

`transaction.build` checks, in this order:

| Check | Error |
| - | - |
| The vault exists and is active. | `404 vault_not_found` / `409 vault_not_active` |
| The vault is not paused. | `409 vault_paused` |
| The vault's allow list accepts the sender. | `403 wallet_not_allowed` |
| `shares` is at most the sender's share balance. | `422 insufficient_shares` |
| The sender has no redeem request waiting for settlement in this vault. | `409 redeem_request_already_pending` |
| The transaction passes the simulation. | `422 simulation_failed` |

`transaction.quote` returns `availability.redeemRequestsOpen`. Without a `sender` it checks only that the vault is not paused. With a `sender` it also checks the allow list, the share balance and a pending redeem request; `availability.reason` is then `vault_paused`, `wallet_not_allowed`, `insufficient_shares` or `redeem_request_already_pending`, and `claimFirst` is filled in.

## Simulation

`transaction.build` simulates the transactions at `blockNumber`, from the sender. `simulation.status` is one of:

| `status` | Meaning |
| - | - |
| `passed` | The transactions ran without reverting: the whole bundle with `eth_simulateV1`, or a single transaction with `eth_call` (`simulation.method`). |
| `partial` | The node cannot simulate a bundle, and the deposit needs its approval first, so only the checks ran. |
| `skipped` | You sent `skipSimulation: true`. |

A transaction that reverts answers `422 simulation_failed`; `details` names the transaction and the error. A passed simulation is not a guarantee: the chain can change between the build and the moment your user sends.

## Errors

Besides the checks above, both actions answer the errors every action can answer (`400 invalid_request`, `401 unauthorized`, `5xx`). See [Errors](/api/errors) for the full list.


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