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

# Create a deposit

> Opens a perp margin account (subaccount_id 0) and funds it, or tops one up, with one signed DepositModule action. The venue verifies the action, reads the nonce, ownership and the owner's USDC balance and allowance from the chain, simulates, and submits it through Matching.verifyAndMatch, paying the gas.

Opens a perp margin account and funds it, or tops up one you already have, with one signed `Action` for the
`DepositModule`. The venue verifies it and submits it through `Matching.verifyAndMatch`, paying the gas; the module
pulls your USDC, deposits it into the perp's cash, and Matching records you as the account's owner.

<Note>
  Either approve USDC to the `DepositModule` once, on chain, or [sign a permit](#or-sign-a-permit-instead) with the
  deposit and never send a transaction yourself. Either way every deposit is this endpoint, and the venue pays its gas.
</Note>

<Warning>
  **Wallets with an EIP-7702 delegation cannot sign for Matching with a plain signature.** Matching verifies
  signatures with OpenZeppelin's `SignatureChecker`, which treats any address that has code — including an EOA with
  a 7702 delegation — as a contract wallet and asks it via ERC-1271 instead of recovering the signature. The deposit
  is refused with `OV_InvalidSignature` unless the delegate contract implements `isValidSignature`. Use an
  undelegated EOA, or a wallet that supports ERC-1271. The same applies to orders and withdrawals.
</Warning>

## Before your first deposit

Approve the `DepositModule` to move at least the amount you will deposit, from the wallet that will own the account:

```text theme={null}
USDC.approve(0x6540f8d9Eb599b045C05E45cb6a5B1730a806658, amount)
```

USDC on Base is `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`.

### Or sign a permit instead

USDC on Base supports EIP-2612 permits, so you can skip the on-chain approve entirely: sign a `Permit` with USDC's
domain (`name` `USD Coin`, `version` `2`, chain `8453`) for `spender` = the `DepositModule`, a `value` of at least the
deposit amount, and your current USDC `nonce`. Send it alongside the action:

```json theme={null}
"permit": { "value": "1000000000", "deadline": "1789400600", "signature": "0x…" }
```

The venue submits the permit, then the deposit, and pays the gas for both. If your allowance already covers the
deposit — you approved earlier, or someone else submitted your permit first — the permit is skipped, not failed. A
permit USDC refuses (expired, already used, or signed for another spender) comes back as a **400**.

## Signing the action

Sign an [`Action`](/signing) against the same `Matching` domain as an order, with:

| Field | Value |
| - | - |
| `subaccountId` | `0` to open a new perp account; your perp account's id to top it up |
| `nonce` | any unused `uint256`; nonces are per owner and per module |
| `module` | the DepositModule: `0x6540f8d9Eb599b045C05E45cb6a5B1730a806658` on Base |
| `data` | `abi.encode(uint256 amount, address asset, address managerForNewAccount)` |
| `expiry` | unix seconds, in the future and at most one hour ahead |
| `owner` | your address; the USDC is pulled from here, and you own the account |
| `signer` | the same as `owner` (session keys are not supported for deposits) |

In `data`:

* `amount` is **USDC in 6-decimal base units**: `1000000000` is 1,000 USDC. It must be explicit — the "whole
  balance" sentinel `type(uint256).max` is refused — and at least the venue's minimum (10 USDC).
* `asset` is the perp's cash asset, `0xA74E49b4Ed7cb176bc02ef4D8a1A3240C9aD4272` — the `quote_asset_address` that
  `GET /v1/markets` reports for `cNGN-PERP`. No other asset is accepted.
* `managerForNewAccount` is the perp risk manager, `0xDE0423D0a1E15536265C9513d2e0c10DAb5835D4`. For a top-up it is
  ignored; send the same address or zero.

Your account's cash is credited at 18 decimals, so 1,000 USDC appears on chain as `1000000000000000000000`. The
response states the amount in all three forms.

Send the action's seven fields as strings (numbers in base 10, `data` as hex) with the signature:

```json theme={null}
{
  "action": {
    "subaccount_id": "0",
    "nonce": "7",
    "module": "0x6540f8d9Eb599b045C05E45cb6a5B1730a806658",
    "data": "0x000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000a74e49b4ed7cb176bc02ef4d8a1a3240c9ad4272000000000000000000000000de0423d0a1e15536265c9513d2e0c10dab5835d4",
    "expiry": "1789400600",
    "owner": "0xYourAddress",
    "signer": "0xYourAddress"
  },
  "signature": "0x…"
}
```

## What is checked

In order, and every check fails closed:

1. The request: module, signer equals owner, expiry, `data` exactly three words, the perp cash asset, an explicit
   amount at or above the minimum, and the perp risk manager for a new account. Refused with **400**.
2. At most one deposit in progress per owner, three requests a minute, and six submitted deposits an hour. Refused
   with **429**; the hourly limit sends `Retry-After`. A request refused for any other reason does not count toward
   the hour.
3. The signature authorizes the action. Refused with **401**.
4. Read from the chain before anything is submitted, each refused with **400** and what to do: the nonce is unused;
   for a top-up, you own the account and it is a perp account; your wallet holds the USDC and has approved the
   `DepositModule` for it.
5. The executor's simulation. The reverts above, if they still happen (a race), come back as **400** with `revert`
   naming them; any other revert is **422**.

**503** means deposits are off or paused: the venue's gas wallet is below its reserve, or the hour's budget of
sponsored gas is spent. `error` says which; retry later.

## The outcome

A **200** carries the transaction and what it credited:

```json theme={null}
{
  "action_hash": "0x1a818d05…",
  "status": "confirmed",
  "accepted": true,
  "tx_hash": "0x…",
  "receipt_status": "success",
  "block_number": "52386004",
  "subaccount_id": "27",
  "amount_usdc": "1000.000000",
  "amount_units": "1000000000",
  "credited_cash_e18": "1000000000000000000000"
}
```

With a permit that was needed, the response also carries `permit_tx_hash`.

`subaccount_id` is your new account when you sent `0`. Use it as `subaccount_id` on your orders.

`action_hash` identifies the deposit: it is the EIP-712 hash of your signed action (`Matching.getActionHash`).
`status` is one of:

| `status` | Meaning |
| - | - |
| `pending` | Being checked and submitted. Answered with **202**. |
| `submitted` | Broadcast, but the receipt was not known yet (`receipt_status: "timeout"`). |
| `confirmed` | Mined successfully. |
| `reverted` | Mined and reverted. |
| `rejected` | Refused before anything was sent. You may resubmit the same request. |
| `unknown` | The venue could not confirm whether it was submitted. |

## Checking a deposit later

```text theme={null}
GET /v1/deposits/{action_hash}
```

Returns the deposit's record. A `submitted` deposit is re-read from the chain first, so a receipt that timed out
resolves to `confirmed` (with `subaccount_id`) or `reverted` on the next read.

Sending the **same signed request** again is also safe at any point: the venue answers it from its record and
never submits it twice — after a timeout, a dropped connection or a restart. A **502** means the venue could not
confirm whether the deposit was submitted: look it up by `action_hash`, or resend the same request, before signing a
new one. A second signed deposit is a second deposit.


## OpenAPI

````yaml POST /v1/deposits
openapi: 3.1.0
info:
  title: Numo orderbook API
  description: >-
    REST endpoints exposed by Numo's markets-service and execution-service for
    dollar stablecoins. Real-time book, trades, and order streams are served
    over the WebSocket endpoint documented in the WebSocket streams reference.
  version: 1.0.0
servers:
  - url: https://api.numofx.com
    description: markets-service
  - url: https://executor.numofx.com
    description: execution-service
security: []
tags:
  - name: Markets service
  - name: Execution service
paths:
  /v1/deposits:
    post:
      tags:
        - Markets service
      summary: Create a deposit
      description: >-
        Opens a perp margin account (subaccount_id 0) and funds it, or tops one
        up, with one signed DepositModule action. The venue verifies the action,
        reads the nonce, ownership and the owner's USDC balance and allowance
        from the chain, simulates, and submits it through
        Matching.verifyAndMatch, paying the gas.
      operationId: createDeposit
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/DepositRequest'
      responses:
        '200':
          description: >-
            Submitted. receipt_status timeout means broadcast but not yet
            confirmed; resubmit the same request for the outcome.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DepositReceipt'
        '202':
          description: >-
            The same signed deposit is already being submitted; status is
            pending. Read it later with GET /v1/deposits/{action_hash}.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DepositRecord'
        '400':
          description: >-
            Malformed or off-policy request, or a chain pre-check failed: a
            spent nonce, not your account, not a perp account, or too little
            USDC balance or allowance. error says what to do; revert names the
            on-chain revert when there was one.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WithdrawalRejection'
        '401':
          description: The signature does not authorize the action
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: >-
            The simulation reverted for a reason not listed under 400, and
            revert names it
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/WithdrawalRejection'
        '429':
          description: >-
            A deposit for this owner is in progress, more than three were
            requested in a minute, or six were submitted in the last hour
            (Retry-After)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '502':
          description: Could not confirm whether the deposit was submitted
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: >-
            Deposits are not enabled, or paused: the venue's gas wallet is below
            its reserve, or the hour's sponsored gas budget is spent
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
components:
  schemas:
    DepositRequest:
      type: object
      required:
        - action
        - signature
      properties:
        action:
          type: object
          description: >-
            A Matching Action for the DepositModule, signed with EIP-712 against
            the Matching domain.
          required:
            - subaccount_id
            - nonce
            - module
            - data
            - expiry
            - owner
            - signer
          properties:
            subaccount_id:
              type: string
              description: >-
                Base-10. 0 opens a new perp account; otherwise your perp account
                to top up.
            nonce:
              type: string
              description: Base-10 uint256, unused by this owner in the DepositModule.
            module:
              type: string
              description: The DepositModule address.
            data:
              type: string
              description: >-
                abi.encode(uint256 amount, address asset, address
                managerForNewAccount). amount is USDC in 6-decimal base units
                (1000000000 = 1,000 USDC), explicit and at least 10 USDC; asset
                is the perp cash asset; managerForNewAccount is the perp risk
                manager, or zero for a top-up.
            expiry:
              type: string
              description: Unix seconds; in the future and at most one hour ahead.
            owner:
              type: string
              description: >-
                Your address: the USDC is pulled from here, and you own the
                account.
            signer:
              type: string
              description: Must equal owner.
        signature:
          type: string
          description: >-
            EIP-712 signature by signer: 65 bytes, or longer for an ERC-1271
            wallet.
        permit:
          type: object
          description: >-
            Optional EIP-2612 permit on Base USDC from owner to the
            DepositModule, instead of a prior approve. The venue submits it with
            the DepositModule as spender, and skips it when the allowance
            already covers the deposit.
          required:
            - value
            - deadline
            - signature
          properties:
            value:
              type: string
              description: Base-10, 6-decimal USDC base units; at least the deposit amount.
            deadline:
              type: string
              description: Unix seconds; not yet passed.
            signature:
              type: string
              description: 65-byte EIP-712 signature over USDC's Permit, by owner.
    DepositReceipt:
      type: object
      required:
        - accepted
        - tx_hash
        - amount_usdc
        - amount_units
        - credited_cash_e18
      properties:
        action_hash:
          type: string
          description: >-
            EIP-712 hash of the signed action (Matching.getActionHash); the key
            for GET /v1/deposits/{action_hash}.
        status:
          type: string
          enum:
            - pending
            - submitted
            - confirmed
            - reverted
            - rejected
            - unknown
        accepted:
          type: boolean
          description: True once mined successfully.
        tx_hash:
          type: string
        receipt_status:
          type: string
          enum:
            - success
            - reverted
            - timeout
          description: 'timeout: broadcast, not yet confirmed.'
        block_number:
          type: string
        subaccount_id:
          type: string
          description: >-
            The account credited: your new account when subaccount_id was 0.
            Absent until the receipt is known.
        amount_usdc:
          type: string
          description: The deposit in USDC, with 6 decimal places.
        amount_units:
          type: string
          description: The same amount in 6-decimal USDC base units, as signed.
        credited_cash_e18:
          type: string
          description: What the account's cash rises by, at the ledger's 18 decimals.
        permit_tx_hash:
          type: string
          description: >-
            The permit's transaction, when the deposit carried a permit and it
            was needed.
    DepositRecord:
      type: object
      required:
        - action_hash
        - status
        - owner
        - amount_usdc
        - amount_units
        - credited_cash_e18
      properties:
        action_hash:
          type: string
        status:
          type: string
          enum:
            - pending
            - submitted
            - confirmed
            - reverted
            - rejected
            - unknown
          description: >-
            rejected: refused before anything was sent; the same request may be
            resubmitted.
        owner:
          type: string
        nonce:
          type: string
        subaccount_id_requested:
          type: string
          description: 0 for a new account.
        subaccount_id:
          type: string
          description: The account credited, once known.
        tx_hash:
          type: string
        permit_tx_hash:
          type: string
          description: >-
            The permit's transaction, when the deposit carried a permit and it
            was needed.
        block_number:
          type: string
        amount_usdc:
          type: string
        amount_units:
          type: string
        credited_cash_e18:
          type: string
        error:
          type: string
        revert:
          type: string
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time
    WithdrawalRejection:
      type: object
      required:
        - error
      properties:
        error:
          type: string
        revert:
          type: string
          description: The revert the simulation hit, e.g. WERC_CannotBeNegative.
    ErrorResponse:
      type: object
      properties:
        error:
          type: string

````

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