# Deposit (v1)

> POST /v1/deposit/build and PUT /v1/deposit/submit. Build the deposit plan, then register the deposit to create the order.

> API v1 is deprecated. It stays supported, but new integrations should use the [Khalani API](/). See [Migrate to v2](/v1/migrate/).

## Build

```text
POST /v1/deposit/build
```

Returns what the user must do to fund the chosen route: wallet actions to run (`CONTRACT_CALL`) or a deposit address to transfer to (`TRANSFER`). Nothing happens on-chain until the user acts.

### Request body

| Field | Type | Required | Description |
|---|---|---|---|
| `quoteId` | string | Yes | From `POST /v1/quotes`. |
| `routeId` | string | Yes | The chosen route's `routeId`. |
| `from` | string | Usually | The wallet that sends the deposit. Required when the quote had a `fromAddress` (and it must match), and for every method except `TRANSFER` and `TRANSFER_WITH_TX_HASH_SUBMIT`. |
| `depositMethod` | string | No | `CONTRACT_CALL`, `TRANSFER` or `TRANSFER_WITH_TX_HASH_SUBMIT`, and one the route lists in `depositMethods`. Left out, the build uses the route's only method, or `CONTRACT_CALL` when it has several. |
| `recipient` | string | Sometimes | Receives the output. Required for a `TRANSFER` build without `from`. |
| `refundTo` | string | Sometimes | Receives a refund. Required for a `TRANSFER` build without `from`. |

A build can be repeated for the same quote, for example after a wallet error. `from`, `recipient` and `refundTo` must then match the first build, or it returns `400`.

`PERMIT2` passes validation, but no route offers it: asking for it returns `400 NotSupportedDepositMethodException`.

### Example

```bash
curl -X POST https://api.hyperstream.dev/v1/deposit/build \
  -H "Content-Type: application/json" \
  -d '{
    "from": "0x5b38da6a701c568545dcfcb03fcb875f56beddc4",
    "quoteId": "3f6c2a1e-8b4d-4e7a-9c15-2d0b7e9a4f61",
    "routeId": "Hyperstream"
  }'
```

### Response: `CONTRACT_CALL`

An ordered list of wallet actions. This native-route plan switches to Arbitrum, approves USDC, then deposits:

```json
{
  "kind": "CONTRACT_CALL",
  "approvals": [
    {
      "type": "eip1193_request",
      "request": { "method": "wallet_switchEthereumChain", "params": [{ "chainId": "0xa4b1" }] }
    },
    {
      "type": "eip1193_request",
      "waitForReceipt": true,
      "request": {
        "method": "eth_sendTransaction",
        "params": [{ "from": "0x5b38da6a701c568545dcfcb03fcb875f56beddc4", "to": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", "data": "0x095ea7b3…" }]
      }
    },
    {
      "type": "eip1193_request",
      "deposit": true,
      "request": {
        "method": "eth_sendTransaction",
        "params": [{ "from": "0x5b38da6a701c568545dcfcb03fcb875f56beddc4", "to": "0xb280C373F57c9169e009a66496EE556084afAd24", "data": "0x…", "value": "0x1c6bf52634000", "gas": "0x4c4b4" }]
      }
    }
  ]
}
```

| Field | Type | Description |
|---|---|---|
| `kind` | string | `CONTRACT_CALL`. |
| `approvals[]` | array | Wallet actions. Run them in order. |
| `approvals[].type` | string | `eip1193_request` (EVM) or `solana_sendTransaction` (Solana). |
| `approvals[].request` | object | EVM only. An EIP-1193 request: `wallet_switchEthereumChain` with `[{ chainId }]`, or `eth_sendTransaction` with `[{ from, to, data?, value?, gas?, gasPrice?, maxFeePerGas?, maxPriorityFeePerGas?, nonce? }]`. Forward it unchanged. |
| `approvals[].transaction` | string | Solana only. A base64 transaction to sign and send. |
| `approvals[].waitForReceipt` | boolean | Optional, EVM only. Wait for the receipt before the next action. |
| `approvals[].deposit` | boolean | Optional. Marks the deposit. Submit its hash. |
| `expiresAt` | integer | Optional. Unix seconds after which the plan is no longer valid. |
| `buildId` | string | Optional. Returned when the quote had no `fromAddress`. The submit must send it back. |

A chain-switch action has no `waitForReceipt` or `deposit` field. Read a missing flag as `false`. For Solana, the API refreshes each transaction's blockhash during the build, so have the user sign right away.

### Response: `TRANSFER`

A deposit address and an exact amount. No wallet actions:

```json
{
  "kind": "TRANSFER",
  "orderId": "9f2c4e1a7b3d5f60812a4c6e8b0d2f41",
  "depositAddress": "0x7d2c…e41a",
  "amount": "10000000",
  "token": "0xaf88d065e77c8cc2239327c5edb3a432268e5831",
  "chainId": 42161,
  "expiresAt": 1792443600
}
```

| Field | Type | Description |
|---|---|---|
| `kind` | string | `TRANSFER`. Also returned for `TRANSFER_WITH_TX_HASH_SUBMIT`. |
| `depositAddress` | string | Where to send the transfer. |
| `amount` | string | The exact amount to send, in base units. |
| `token` | string | The token to send: its address, or `BTC` on Bitcoin. |
| `chainId` | integer | The chain to send it on. |
| `expiresAt` | integer | Optional. Unix seconds. Send before it. |
| `memo` | string | Optional. A memo the transfer must carry, when the chain needs one. |
| `orderId` | string | Optional. A 32-character hex id for a native-route deposit-address order. Track it with `GET /v1/orders/{refundTo}?orderIds={orderId}`, not with `by-id`. |
| `submitTxHashRequested` | boolean | Optional. `true` on TRON and Bitcoin sources: after the transfer, submit its hash. Without it, do not submit. |

See [Wallet execution](/v1/wallet-execution/#deposit-addresses) for how to run each kind.

### Errors

| Status | `name` | When |
|---|---|---|
| `400` | `ValidationException` | A field is missing or malformed. |
| `400` | `BadRequestException` | The quote expired (`Quote has expired. Please request a new quote.`), `from` is missing or differs from the quote's `fromAddress`, the quote is bound to another wallet, or `recipient` or `refundTo` differs from an earlier build. Also `Malformed JSON body`. |
| `400` | `NotSupportedDepositMethodException` | The route does not offer `depositMethod`. `details` holds `depositMethod`, `fillerId` and `supportedMethods`. |
| `400` | `CannotFillException` | The route cannot build this deposit. `details` is hidden. |
| `400` | `BuildDepositParsingException` | The route returned a plan the API could not validate. |
| `404` | `QuoteNotFoundException` | Unknown `quoteId` and `routeId` pair, or the quote's storage window has passed. |
| `404` | `NotSupportedTokenException`, `NotSupportedChainException`, `IntentNotFoundException` | The token or chain is not supported, or the stored quote has no intent. |
| `429` | `RATE_LIMITED` (`errorCode`) | Too many builds. See [Limits and reliability](/v1/limits/). |
| `503` | `MaintenanceException` | Maintenance. Retry after `Retry-After: 300`. |

## Submit

```text
PUT /v1/deposit/submit
```

Registers the deposit and creates the order. Send either the hash of a transaction the user broadcast, or a signed transaction for the API to broadcast. The submit is not rate limited and keeps working during maintenance.

### Request body

| Field | Type | Required | Description |
|---|---|---|---|
| `quoteId` | string | Yes | From the quote. |
| `routeId` | string | Yes | The route you built. |
| `buildId` | string | When the build returned one | Send back the build's `buildId`. A missing or stale one returns `400`. |
| `txHash` | string | One of the two | The deposit's hash. EVM: `0x` and 64 hex characters. Solana: the base58 signature, 87 or 88 characters. TRON and Bitcoin on `HyperstreamUTXO`: 64 hex characters without `0x`. |
| `signedTransaction` | string | One of the two | A signed transaction for the API to broadcast. EVM: `0x`-prefixed hex. Solana: base64. |

Send exactly one of `txHash` and `signedTransaction`.

### Example

```bash
curl -X PUT https://api.hyperstream.dev/v1/deposit/submit \
  -H "Content-Type: application/json" \
  -d '{
    "txHash": "0x4e9a2c71d0b35f8e6a1c9d27b4f08e35a6c1d92e7b04f3a85c6d1e29f7b30a4c",
    "quoteId": "3f6c2a1e-8b4d-4e7a-9c15-2d0b7e9a4f61",
    "routeId": "Hyperstream"
  }'
```

### Response

```json
{
  "orderId": "cm2x8k3n70001qz5v9w4r6t2y",
  "txHash": "0x4e9a2c71d0b35f8e6a1c9d27b4f08e35a6c1d92e7b04f3a85c6d1e29f7b30a4c"
}
```

| Field | Type | Description |
|---|---|---|
| `orderId` | string | The order's id: `c` and 24 lowercase letters or digits. Track it with `GET /v1/orders/by-id/{orderId}`. |
| `txHash` | string | The deposit's hash: the one you sent, or the one the API broadcast. |

The submit consumes the quote. Submit each deposit once.

### Errors

| Status | `name` | When |
|---|---|---|
| `400` | `ValidationException` | Neither field is valid, or a hash has the wrong format. |
| `400` | `BadRequestException` | The hash or transaction format does not match the source chain, the `buildId` is missing or stale, or the quote is funded through a deposit address (`/submit is not used for TRANSFER quotes`). |
| `400` | `BroadcastException` | The API's broadcast failed: balance, nonce, or an expired Solana blockhash. `details` is hidden. |
| `404` | `QuoteNotFoundException` | Unknown `quoteId` and `routeId` pair, a quote past its storage window, or a quote already used by a successful submit. |
| `404` | `NotSupportedChainException` | The API cannot broadcast on the source chain. Broadcast it yourself and submit the hash. |
| `500` | `InternalErrorException` | A message starting `Deposit was submitted, but` means the deposit exists and only the API's record failed. Do not deposit again. |

A repeated submit after a success returns `404`, not `409`. Before you retry a submit whose response you did not see, look the order up with `GET /v1/orders/{address}`.
