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

# Credit a bridge deposit to NEAR Intents

> Credits a HOT bridge deposit to its NEAR Intents account: what the old backend's `process_bridge_deposit` did.

Give the deposit either **by its source tx** (`tx`, `chain` optional) or **by its fields** (`chain_id`, `nonce`, `amount`, `token_id`, `receiver_id`), plus the `intent_account` to credit. When `tx` is present the fields are ignored.

- By tx, the service reads the deposit on chain: the bridge's `NewTransfer` log on EVM, its `wasm` deposit event on CosmWasm (Gonka), the treasury's deposit message in the tonapi trace on TON (any hash of the trace), the bridge's `deposit` call in the envelope on Stellar (Soroban RPC keeps transactions for about a week; send an older one by its fields). Without `chain`, a `0x` hash is looked for on every EVM chain and a bare hash on Gonka, TON and Stellar.
- By fields, nothing is trusted: the deposit is checked on its chain through omni's AuthCall before it is signed.

The deposit's receiver must be `intent_account`'s NEAR Intents receiver (`sha256` of `{"account_id":"intents.near","msg":"{\"receiver_id\":\"<account>\"}"}`, see `POST /bridge/register`); a checksummed EVM address is also tried lowercased. Then the deposit wallet signs it and a relay account sends `mt_deposit_call` to `v2_1.omni.hot.tg`.

Idempotent: a deposit already credited answers `already_credited`, and a call repeated within 24 hours answers the first NEAR tx. With `dry_run: true` the deposit is read and signed, and nothing is sent (`status: signed`).



## OpenAPI

````yaml https://mpc.hex-api.app/openapi.json post /relay/deposit
openapi: 3.1.0
info:
  title: HEX MPC API
  version: 1.0.0
  summary: HOT bridge signatures and relay, the bridge registry and monitor.
  description: >
    The public API of `hex-mpc`, a Cloudflare Worker in front of the HOT MPC
    nodes. It replaces the old HOT backend's bridge endpoints

    (`api0.herewallet.app`: `process_bridge_deposit`, `auto_deposit_register`,
    ...) and HOR (`rpc1.hotdao.ai`, `rpc2.hotdao.ai`).


    ## Which routes to use


    - **Credit a deposit to NEAR Intents**: `POST /relay/deposit` with the
    source tx (or the deposit's fields). The service reads the deposit on its
    chain, signs it and sends `mt_deposit_call` on NEAR, paying the gas. This is
    what `process_bridge_deposit` did.

    - **Pay out withdrawals**: `POST /relay/withdraw` with the chain and the
    receiver; poll `GET /relay/jobs/{chain}/{id}`.

    - **Clear a paid withdrawal**: `POST /relay/clear`.

    - **Submit transactions yourself**: the `Signatures` routes return the MPC
    signatures only.

    - **Record what your users do** (optional, for support and recovery): `POST
    /bridge/register`, `/bridge/deposits`, `/bridge/withdrawals`.


    Every relay call sends only what holds on chain (a deposit that happened,
    the payout of a withdrawal omni holds, the clear of a paid one), and each at
    most once, so retrying a call is safe.


    ## Conventions


    - **Bodies** are JSON objects, at most 32 KiB (`/intents/solver`: 128 KiB).
    A body that is not a JSON object is a `422`, a larger one a `413`.

    - **Big integers.** Nonces and amounts are answered as decimal strings. In
    requests they may be decimal strings or JSON integers: integers above 2^53
    are read exactly (the bridge's nonces are about 1.8e21), but a decimal
    string is the safer choice for clients whose JSON encoder uses doubles.

    - **Nonces** are omni's: `unix seconds * 10^12 + n`.

    - **Encodings.** omni's token ids, receivers and the bridge signatures are
    base58; NEAR tx hashes are base58.

    - **Chain ids.** Chain ids are omni's (`v2_1.omni.hot.tg`), not always the
    chain's own: EVM chains use their EIP-155 id (Ethereum 1, Optimism 10, BNB
    56, Polygon 137, Monad 143, X Layer 196, Base 8453, Arbitrum 42161,
    Avalanche 43114, ...); the others have omni's numbers: Solana 1001, NEAR
    1010, Stellar 1100, TON 1117 (TON v1, 1111, is dead), Juno 4444118, Gonka
    4444119.

    - **Errors** are `{ "detail": "..." }` with an HTTP status (FastAPI's
    format, kept from HOR), the router's own `404`, `405` and `429` included.
    Clients should branch on the status and show or log the `detail`. A `5xx`
    (and a `502`/`503` in particular) is worth retrying with backoff; a `4xx`
    will answer the same until something changes on chain.

    - **CORS.** Any origin; the request's `Origin` is echoed with
    `access-control-allow-credentials: true`, and preflights allow `GET, POST,
    OPTIONS` with any header.

    - **Times** in answers are milliseconds since the epoch unless named `*_s`
    (seconds).


    ## Rate limits


    Per client IP (an IPv6 client by its /64), over 60 seconds, counted per
    Cloudflare location:


    | Class | Limit | Routes |

    | --- | --- | --- |

    | `bridge` | 300 / 60 s | `/withdraw/sign`, `/deposit/sign`, `/clear/sign`,
    `/relay/*`, `/bridge/*` |

    | `solver` | 120 / 60 s | `/intents/solver` |


    `/v1/health`, `/v1/time` and `/openapi.json` are not limited. Over the limit
    the answer is `429` `{ "detail": "Too many requests" }` with `Retry-After:
    60`.


    ## Load protection


    The MPC nodes are a fixed quorum: every signature is a round on each of
    them. So on top of the per-IP limits, every call that reaches the nodes (the
    `Signatures` routes, and the relay's own signing) is admitted globally,
    whoever asks:


    - **Rate per kind of signing**, by default 300 a minute each for
    withdrawals, deposits and clears: over it, `429` with `Retry-After`.

    - **Signings at once**, 60 in all, each kind its own share: over it, `503`
    `MPC nodes are busy, try again shortly` with `Retry-After`. Also `503` with
    `Retry-After` when the nodes answer `429` or too few of them are up.

    - **One signing per message.** A signature is kept for a day and answered
    again (a retry, another client, the relay), and while it is being made a
    second request waits for it (or gets `503` `This signature is being made,
    try again shortly` after 45 s). A refusal of the bridge (`400`) is answered
    again for 5 seconds.

    - **Refusals before the nodes**: a deposit omni already credited (`400`
    `Deposit … is already credited`), a withdrawal no payout can land (`400`
    `Withdrawal … : it can only be refunded`: it pays the token contract itself,
    the zero address, or a Stellar nonce past its 60 days).


    Retry a `429` or `503` after its `Retry-After`.
servers:
  - url: https://mpc.hex-api.app
    description: Production
security: []
tags:
  - name: Relay
    description: >-
      The recommended integration: the service sends the bridge's transactions
      and pays for them. Deposits are credited to NEAR Intents; withdrawals are
      paid out on their chain and cleared on omni.
  - name: Signatures
    description: >-
      The bridge's MPC signatures, for callers that submit the transactions
      themselves (HOR-compatible: same bodies, answers and errors). Each
      signature needs omni's word, and a deposit or clear also its AuthCall on
      chain, so a caller cannot forge or redirect one.
  - name: Registry
    description: >-
      The bridge registry: what users do on the bridge, as clients report it and
      as the relay sees it on chain. A post only records a claim: nothing is
      signed or sent for it, and what the relay reads on chain overrides it.
      Lookups answer 503 while the registry's database is not configured.
  - name: Monitor
    description: Health of every bridge HEX moves funds through, over 24 hours.
  - name: Intents
    description: >-
      NEAR Intents solver relay passthrough, used by HEX/HOT kit to publish
      signed intents.
  - name: Service
    description: Health, time and this document.
paths:
  /relay/deposit:
    post:
      tags:
        - Relay
      summary: Credit a bridge deposit to NEAR Intents
      description: >-
        Credits a HOT bridge deposit to its NEAR Intents account: what the old
        backend's `process_bridge_deposit` did.


        Give the deposit either **by its source tx** (`tx`, `chain` optional) or
        **by its fields** (`chain_id`, `nonce`, `amount`, `token_id`,
        `receiver_id`), plus the `intent_account` to credit. When `tx` is
        present the fields are ignored.


        - By tx, the service reads the deposit on chain: the bridge's
        `NewTransfer` log on EVM, its `wasm` deposit event on CosmWasm (Gonka),
        the treasury's deposit message in the tonapi trace on TON (any hash of
        the trace), the bridge's `deposit` call in the envelope on Stellar
        (Soroban RPC keeps transactions for about a week; send an older one by
        its fields). Without `chain`, a `0x` hash is looked for on every EVM
        chain and a bare hash on Gonka, TON and Stellar.

        - By fields, nothing is trusted: the deposit is checked on its chain
        through omni's AuthCall before it is signed.


        The deposit's receiver must be `intent_account`'s NEAR Intents receiver
        (`sha256` of
        `{"account_id":"intents.near","msg":"{\"receiver_id\":\"<account>\"}"}`,
        see `POST /bridge/register`); a checksummed EVM address is also tried
        lowercased. Then the deposit wallet signs it and a relay account sends
        `mt_deposit_call` to `v2_1.omni.hot.tg`.


        Idempotent: a deposit already credited answers `already_credited`, and a
        call repeated within 24 hours answers the first NEAR tx. With `dry_run:
        true` the deposit is read and signed, and nothing is sent (`status:
        signed`).
      operationId: relayDeposit
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DepositRelayRequest'
            examples:
              stellarByTx:
                summary: A Stellar deposit by its tx
                value:
                  tx: >-
                    3b1f6c0e9d2a47c58e6f1a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f
                  chain: 1100
                  intent_account: alice.near
              evmByTx:
                summary: An EVM deposit by its tx (every EVM chain is tried)
                value:
                  tx: >-
                    0xdfd90cb908815115b944a21f71a015cccfe668ff8752e49d440beb9da7769df4
                  intent_account: '0xe4df1d74122c2a7b14481cbdde704fbd0a4f11a2'
              byFields:
                summary: A deposit by its fields (e.g. TON, or what the client parsed)
                value:
                  chain_id: 137
                  nonce: '1792107257000001836156'
                  amount: '2500000'
                  token_id: qiStmoQJDQPTebaPjgx5VBxZv6L
                  receiver_id: 6QPkTPp7EKZytMtEuvRmqGBBMMuwf4rrX4JjAoiuCYnT
                  intent_account: alice.near
                  source_tx: >-
                    0xdfd90cb908815115b944a21f71a015cccfe668ff8752e49d440beb9da7769df4
              dryRun:
                summary: Read and sign only
                value:
                  tx: >-
                    3b1f6c0e9d2a47c58e6f1a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f
                  chain: 1100
                  intent_account: alice.near
                  dry_run: true
      responses:
        '200':
          description: The deposit is credited (now or before), or signed on a dry run.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DepositRelayAnswer'
              examples:
                credited:
                  summary: Credited now
                  value:
                    status: credited
                    chain_id: 1100
                    nonce: '1792944123000000004211'
                    amount: '25000000'
                    token_id: 111bzQBB65GxAPAVoxqmMcgYo5oS3txhqs1Uh1cgahKQUeTUq1TJu
                    receiver: 6QPkTPp7EKZytMtEuvRmqGBBMMuwf4rrX4JjAoiuCYnT
                    intent_account: alice.near
                    signature: >-
                      4sJhsuGTu8f9XoiAkZj3Si9GSjUT714yzQWdo5wZHkWpAKQHd69dfiT3RycMnUqhrT5FA4wokk6RbrFEFKkmPnW7
                    near_tx: 2rQmS4n4TxT7GJ26XLDDZEHEmsizLb1ezGC81zr8dpkR
                alreadyCredited:
                  summary: Credited before
                  value:
                    status: already_credited
                    chain_id: 1100
                    nonce: '1792944123000000004211'
                    amount: '25000000'
                    token_id: 111bzQBB65GxAPAVoxqmMcgYo5oS3txhqs1Uh1cgahKQUeTUq1TJu
                    receiver: 6QPkTPp7EKZytMtEuvRmqGBBMMuwf4rrX4JjAoiuCYnT
                    intent_account: alice.near
                signed:
                  summary: Dry run
                  value:
                    status: signed
                    chain_id: 1100
                    nonce: '1792944123000000004211'
                    amount: '25000000'
                    token_id: 111bzQBB65GxAPAVoxqmMcgYo5oS3txhqs1Uh1cgahKQUeTUq1TJu
                    receiver: 6QPkTPp7EKZytMtEuvRmqGBBMMuwf4rrX4JjAoiuCYnT
                    intent_account: alice.near
                    signature: >-
                      4sJhsuGTu8f9XoiAkZj3Si9GSjUT714yzQWdo5wZHkWpAKQHd69dfiT3RycMnUqhrT5FA4wokk6RbrFEFKkmPnW7
        '400':
          description: >-
            The deposit cannot be credited: its receiver is not the account's,
            its tx failed or made no bridge deposit, the chain is not supported,
            omni refused it, or `mt_deposit_call` failed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                wrongReceiver:
                  value:
                    detail: >-
                      The deposit's receiver
                      6QPkTPp7EKZytMtEuvRmqGBBMMuwf4rrX4JjAoiuCYnT is not
                      bob.near's NEAR Intents receiver
                noDeposit:
                  value:
                    detail: >-
                      tx
                      3b1f6c0e9d2a47c58e6f1a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f
                      made no bridge deposit on chain 1100
                refused:
                  value:
                    detail: Cant validate deposit data on dry run
                sendFailed:
                  value:
                    detail: 'mt_deposit_call failed: ...'
        '403':
          description: The MPC nodes refused or could not sign. Retry later.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                signing:
                  value:
                    detail: HOT RPC signing failed
        '404':
          description: The tx was not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                notFound:
                  value:
                    detail: >-
                      Tx
                      3b1f6c0e9d2a47c58e6f1a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f
                      is not found on chain 1100
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '422':
          description: A malformed field.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                account:
                  value:
                    detail: intent_account is required
                tx:
                  value:
                    detail: tx must be a 32-byte hex hash
                chain:
                  value:
                    detail: chain must be a chain id
                receiver:
                  value:
                    detail: receiver_id must be 32 bytes
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          $ref: '#/components/responses/InternalError'
        '502':
          $ref: '#/components/responses/BadGateway'
        '503':
          $ref: '#/components/responses/SigningUnavailable'
components:
  schemas:
    DepositRelayRequest:
      description: >-
        A deposit by its source tx, or by its fields. When `tx` is present the
        fields are ignored.
      anyOf:
        - $ref: '#/components/schemas/DepositRelayByTx'
        - $ref: '#/components/schemas/DepositRelayByFields'
    DepositRelayAnswer:
      type: object
      required:
        - status
        - chain_id
        - nonce
        - amount
        - token_id
        - receiver
        - intent_account
      properties:
        status:
          type: string
          enum:
            - credited
            - already_credited
            - signed
          description: >-
            `credited`: `mt_deposit_call` went through now. `already_credited`:
            omni had executed it before (nothing sent, no signature). `signed`:
            a dry run.
        chain_id:
          $ref: '#/components/schemas/ChainId'
        nonce:
          $ref: '#/components/schemas/NonceString'
        amount:
          $ref: '#/components/schemas/DecimalString'
        token_id:
          $ref: '#/components/schemas/Base58'
          description: omni's token id.
        receiver:
          $ref: '#/components/schemas/Base58'
          description: The deposit's receiver.
        intent_account:
          type: string
          description: >-
            The account credited, in the form whose receiver matched (a
            checksummed EVM address may come lowercased).
        signature:
          $ref: '#/components/schemas/Base58'
          description: The deposit signature (64 bytes). Absent with `already_credited`.
        near_tx:
          $ref: '#/components/schemas/Base58'
          description: The NEAR tx of `mt_deposit_call`. With `credited` only.
        near_sender:
          type: string
          description: >-
            The relay account that sent `near_tx`: with it, the NEAR tx can be
            looked up (absent when nothing was sent).
          examples:
            - dhjdisksj.tg
    Error:
      type: object
      description: 'Every error: FastAPI''s `{ detail }` with the HTTP status.'
      required:
        - detail
      properties:
        detail:
          type: string
          description: What went wrong, readable.
      examples:
        - detail: Nonce was not used on target chain
    DepositRelayByTx:
      type: object
      required:
        - tx
        - intent_account
      properties:
        tx:
          type: string
          pattern: ^(0x)?[0-9a-fA-F]{64}$
          description: >-
            The source tx: `0x` + 64 hex on EVM; 64 hex on Stellar and CosmWasm
            (Gonka).
          examples:
            - 3b1f6c0e9d2a47c58e6f1a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f
            - '0xdfd90cb908815115b944a21f71a015cccfe668ff8752e49d440beb9da7769df4'
        chain:
          $ref: '#/components/schemas/ChainId'
          description: >-
            The source chain. Unset, a `0x` hash is looked for on every EVM
            chain, a bare hash on Gonka, TON and Stellar.
        intent_account:
          $ref: '#/components/schemas/IntentAccount'
        dry_run:
          type: boolean
          default: false
          description: 'Read and sign only; send nothing (`status: signed`).'
    DepositRelayByFields:
      type: object
      required:
        - chain_id
        - nonce
        - amount
        - token_id
        - receiver_id
        - intent_account
      properties:
        chain_id:
          $ref: '#/components/schemas/ChainId'
        nonce:
          $ref: '#/components/schemas/NonceInput'
        amount:
          $ref: '#/components/schemas/AmountInput'
        token_id:
          $ref: '#/components/schemas/Base58'
          description: omni's token id.
        receiver_id:
          $ref: '#/components/schemas/Base58'
          description: >-
            The deposit's on-chain receiver, base58 of 32 bytes (the account's
            `deposit_hash`).
        intent_account:
          $ref: '#/components/schemas/IntentAccount'
        source_tx:
          type: string
          description: >-
            The deposit's source tx, a label for the registry only: the row
            reported for it (`POST /bridge/deposits`) is the one marked
            credited, when it is the same account's. Not checked on chain.
        dry_run:
          type: boolean
          default: false
          description: 'Sign only; send nothing (`status: signed`).'
    ChainId:
      type: integer
      minimum: 1
      description: >-
        Chain ids are omni's (`v2_1.omni.hot.tg`), not always the chain's own:
        EVM chains use their EIP-155 id (Ethereum 1, Optimism 10, BNB 56,
        Polygon 137, Monad 143, X Layer 196, Base 8453, Arbitrum 42161,
        Avalanche 43114, ...); the others have omni's numbers: Solana 1001, NEAR
        1010, Stellar 1100, TON 1117 (TON v1, 1111, is dead), Juno 4444118,
        Gonka 4444119.
      examples:
        - 1100
        - 1117
        - 137
        - 4444119
    NonceString:
      type: string
      pattern: ^\d{1,39}$
      description: omni's nonce (`unix seconds * 10^12 + n`), decimal.
      examples:
        - '1792944123000000004211'
    DecimalString:
      type: string
      pattern: ^\d+$
      description: A non-negative integer as a decimal string.
    Base58:
      type: string
      pattern: ^[1-9A-HJ-NP-Za-km-z]+$
      description: Base58 (Bitcoin alphabet).
    IntentAccount:
      type: string
      description: >-
        A NEAR Intents account: a NEAR account (`alice.near`, a 64-hex implicit
        account), an EVM address, or a base58 32-byte key (a Solana address,
        taken as its hex implicit account).
      examples:
        - alice.near
        - '0xe4df1d74122c2a7b14481cbdde704fbd0a4f11a2'
    NonceInput:
      description: >-
        omni's nonce: a decimal string (preferred) or a JSON integer, read
        exactly even above 2^53. Hex, floats and negatives are refused.
      oneOf:
        - $ref: '#/components/schemas/NonceString'
        - type: integer
          minimum: 0
    AmountInput:
      description: >-
        A positive amount in the token's smallest unit: a decimal string
        (preferred) or a JSON integer.
      oneOf:
        - type: string
          pattern: ^\d{1,39}$
        - type: integer
          minimum: 1
  responses:
    PayloadTooLarge:
      description: 'The body exceeds 32 KiB (`/intents/solver`: 128 KiB).'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            tooLarge:
              value:
                detail: Body exceeds 32768 bytes
    TooManyRequests:
      description: >-
        Over the route's per-IP rate limit (60 s), or, on a route that signs,
        over the MPC nodes' signing rate (see Load protection). Wait
        `Retry-After` seconds.
      headers:
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            limited:
              value:
                detail: Too many requests
            signing:
              value:
                detail: Too many bridge_deposit signings, try again in 1 s
    InternalError:
      description: An unexpected error. Its message is not exposed.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            internal:
              value:
                detail: Internal Server Error
    BadGateway:
      description: >-
        A chain or NEAR RPC is unavailable, or an answer could not be read.
        Retry with backoff.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            near:
              value:
                detail: NEAR RPC is unavailable, try again
            chain:
              value:
                detail: Stellar RPC of chain 1100 is unavailable, try again
    SigningUnavailable:
      description: >-
        The MPC nodes cannot take the signing now (busy, rate limited by the
        nodes, too few of them up, or the same signature is being made), with
        `Retry-After`; or something the route needs is not configured.
      headers:
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            busy:
              value:
                detail: MPC nodes are busy, try again shortly
            down:
              value:
                detail: MPC nodes are unavailable, try again shortly
            inFlight:
              value:
                detail: This signature is being made, try again shortly
            nodes:
              value:
                detail: MPC nodes are not configured
            relay:
              value:
                detail: The relay's NEAR keys are not configured
  headers:
    RetryAfter:
      description: Seconds to wait.
      schema:
        type: integer
      example: 60

````

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