# Static Wallets

> Permanent deposit addresses tied to a specific order or user, perfect for recurring and long-term payments.

Static wallets are permanent addresses for receiving cryptocurrency payments. They are linked to a specific `order_id` and are unique by the combination of `project_id + order_id + currency + network`.

Use static wallets for:

- Recurring deposits from the same user
- Long-term payment addresses displayed on a user profile
- High-volume deposit flows where you want a stable address per user

## Create static wallet

`POST /v1/static-wallet`

### Request parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `currency` | string | yes | Cryptocurrency (USDT, BTC, ETH, etc.) |
| `network` | string | yes | Network code |
| `order_id` | string | yes | Your order/user ID (up to 255 chars) |
| `label` | string | no | Wallet label (up to 255 chars) |
| `url_callback` | string | yes | URL for webhook notifications |
| `invite_code` | string | no | Referrer code |

### Request example

```json
{
  "currency": "USDT",
  "network": "TRX-TRC20",
  "order_id": "USER-123",
  "label": "User deposit #123",
  "url_callback": "https://your-site.com/webhook/static"
}
```

### Response example

```json
{
  "state": 0,
  "result": {
    "uuid": "019b2265-34d8-7001-a230-8f97de90d481",
    "address": "TXYZabc123...",
    "currency": "USDT",
    "network": "TRX-TRC20",
    "label": "User deposit #123",
    "order_id": "USER-123",
    "status": "active",
    "url": "https://go.2328.io/static/019b2265-34d8-7001-a230-8f97de90d481",
    "created_at": "2026-01-20T12:00:00Z",
    "qr": "data:image/png;base64,iVBORw0..."
  }
}
```

## Wallet info

Get static wallet information by `uuid` or `address`.

`POST /v1/static-wallet/info`

### Request parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `uuid` | string | yes* | Static wallet UUID |
| `address` | string | yes* | Blockchain wallet address |

> **INFO:** At least one of `uuid` or `address` is required.

### Response example

```json
{
  "state": 0,
  "result": {
    "uuid": "019b2265-34d8-7001-a230-8f97de90d481",
    "address": "TXYZabc123...",
    "currency": "USDT",
    "network": "TRX-TRC20",
    "status": "active",
    "total_received": "1250.50",
    "transactions_count": 3,
    "created_at": "2026-01-20T12:00:00Z",
    "qr": "data:image/png;base64,iVBORw0..."
  }
}
```

- `total_received` — sum of all deposits received by this wallet, in `currency`.
- `transactions_count` — number of deposits received so far.
- `qr` — Base64-encoded QR data URI of the deposit address (always present for static wallets, the address is assigned at creation).

## Wallet list

`POST /v1/static-wallet/list`

### Request parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `status` | string | no | Filter by status (`active`, `inactive`) |
| `currency` | string | no | Filter by currency |
| `network` | string | no | Filter by network |
| `order_id` | string | no | Filter by order_id |
| `page` | int | no | Page number (default: 1) |
| `per_page` | int | no | Items per page (default: 20, max: 100) |

### Response example

```json
{
  "state": 0,
  "result": {
    "items": [
      {
        "uuid": "019b2265-...",
        "address": "TXYZabc123...",
        "currency": "USDT",
        "network": "TRX-TRC20",
        "status": "active",
        "total_received": "1250.50",
        "transactions_count": 3
      }
    ],
    "paginate": {
      "count": 1,
      "current_page": 1,
      "per_page": 20,
      "total": 1,
      "total_pages": 1,
      "has_more": false
    }
  }
}
```

## Enable / disable wallet

Toggle whether a static wallet accepts new payments.

`POST /v1/static-wallet/disable`

`POST /v1/static-wallet/enable`

### Request

Both endpoints take a single parameter:

```json
{
  "uuid": "019b2265-34d8-7001-a230-8f97de90d481"
}
```

### Response example

```json
{
  "state": 0,
  "result": {
    "uuid": "019b2265-34d8-7001-a230-8f97de90d481",
    "status": "inactive",
    "message": "Static wallet disabled successfully"
  }
}
```

For `enable`, `status` is `"active"` and `message` reads `"Static wallet enabled successfully"`.

## Wallet transactions

Get a list of all deposits received by a static wallet.

`POST /v1/static-wallet/transactions`

### Request parameters

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `uuid` | string | yes | Static wallet UUID |
| `date_from` | date | no | Start date (YYYY-MM-DD) |
| `date_to` | date | no | End date (YYYY-MM-DD) |
| `page` | int | no | Page number (default: 1) |
| `per_page` | int | no | Items per page (default: 15, max: 5000) |

### Response example

```json
{
  "state": 0,
  "result": {
    "items": [
      {
        "uuid": "abc123-def456-...",
        "order_id": "USER-123",
        "amount": "100.00",
        "currency": "USDT",
        "payment_status": "paid",
        "txid": "0xabc123def456...",
        "fee_amount": "3.00",
        "net_amount": "97.00",
        "created_at": "2026-01-20T15:30:00Z"
      }
    ],
    "paginate": {
      "count": 1,
      "hasPages": true,
      "perPage": 15,
      "page": 1
    }
  }
}
```

- `fee_amount` — platform fee deducted from this deposit, in `currency`.
- `net_amount` — amount credited to the merchant balance after the fee.

## Static wallet webhooks

When a payment is received on a static wallet, the system sends a webhook to `url_callback`.

> **WARNING:** The webhook format for static wallets differs from regular payment webhooks. Notably, static wallet webhooks include a `merchant_amount` field which you should use for crediting.

### Webhook payload

```json
{
  "uuid": "a28b293f-5c76-4053-8062-ae9ca4ab784b",
  "order_id": "USER-7666308594",
  "amount": "10.00000000",
  "currency": "USDT",
  "amount_usd": "10.00000000",
  "exchange_rate": "1.00000000",
  "payer_currency": "USDT",
  "payer_amount": "10.00000000",
  "network": "TRX-TRC20",
  "address": "TMU9Tgpchvgbywkbj5SdC8KJS73t5m3M7G",
  "payment_status": "paid",
  "txid": "8369ede26a0da05b1bae154b4bb4072eb2453db30ba86b21831902670929454f",
  "tx_explorer_url": "https://tronscan.org/#/transaction/8369ede26a0da05b1bae154b4bb4072eb2453db30ba86b21831902670929454f",
  "payment_amount": "10.00000000",
  "merchant_amount": "9.920000000000000000",
  "created_at": "2026-05-09T16:13:04+03:00",
  "sign": "dd958d1405febce670a9a196e9141784b9f2a5f39cd6d1832d6f3f68d0de1e10"
}
```

> **INFO:** Static wallet webhooks **do not** include `url` or `expires_at` (since the address is permanent, not a session). They **do** include `exchange_rate` and `created_at`.

### Field reference

| Field | Type | Description |
|-------|------|-------------|
| `uuid` | string | Transaction (invoice) UUID for this deposit |
| `order_id` | string | Your static wallet `order_id` |
| `amount` | decimal (8 dp) | Crypto amount received |
| `currency` | string | Crypto received (matches the wallet's `currency`) |
| `amount_usd` | decimal (8 dp) | USD value at the time of receipt |
| `exchange_rate` | decimal | Crypto / USD rate used |
| `payer_currency` | string | Same as `currency` for static wallets |
| `payer_amount` | decimal (8 dp) | Same as `amount` for static wallets |
| `network` | string | Blockchain network |
| `address` | string | Static wallet address |
| `payment_status` | string | Current deposit status. Normally `paid`; AML processing can produce `aml_lock`, which must not be credited automatically. |
| `txid` | string | Blockchain transaction hash |
| `tx_explorer_url` | string \| null | Blockchain explorer transaction URL. `null` when `txid` is absent or the transfer is internal P2P. |
| `payment_amount` | decimal (8 dp) | Same as `amount` |
| `merchant_amount` | decimal (18 dp) | **Amount after fee deduction** — use this for crediting |
| `created_at` | string (ISO 8601) | When the deposit was received |
| `sign` | string (hex) | HMAC-SHA256 signature of the payload |

## Best practices

- **Unique `order_id`** — Use a unique `order_id` for each user or order
- **Idempotency** — Check `txid` before processing to avoid duplicate credits
- **Verify signatures** — ALWAYS verify the `sign` signature before crediting funds
- **Use `merchant_amount`** — Credit users based on `merchant_amount`, not `payment_amount`

## Lifecycle and idempotency

A static wallet is a reusable deposit identity, not an invoice. It has no expected amount and no expiry. One address can produce any number of deposit transactions over its lifetime.

Creation is idempotent for the same merchant project, `order_id`, `currency`, and `network`: the existing wallet is returned. Keep that tuple stable and persist the returned wallet `uuid`; do not use a new `order_id` every time the same customer opens the deposit screen.

Deposit idempotency is different from wallet idempotency:

- `order_id` identifies the reusable wallet/customer mapping;
- wallet `uuid` identifies the permanent wallet record;
- webhook `uuid` identifies one detected deposit transaction;
- `txid` identifies the on-chain transfer and is the primary deduplication key for crediting.

Use a database uniqueness constraint for the processed chain/network/txid identity and claim it in the same transaction that credits the customer's internal balance.

## Enable and disable semantics

Disabling a wallet prevents the application from processing it as an active deposit target; it does not erase the address or its history and cannot stop a blockchain transfer already sent by a user.

> **DANGER:** Never tell users that funds sent to an inactive address are automatically returned. Blockchain transfers are irreversible. Disable only after removing the address from your UI, and keep an operational recovery procedure for late deposits.

Re-enabling preserves the same wallet identity and address. Do not create a replacement merely to change the label; labels are not settlement identifiers.

## Static wallet edge cases

| Situation | Correct handling |
|-----------|------------------|
| Duplicate creation request | Accept the returned existing wallet and verify its persisted tuple instead of expecting a new address. |
| Multiple deposits to one address | Create a separate local deposit row for every transaction `uuid`/`txid`; never mark the wallet itself as “paid.” |
| Duplicate webhook | Return HTTP 200 after finding the already committed txid; never credit again. |
| Confirmation delay or chain re-observation | Keep processing idempotent and reconcile from `/v1/static-wallet/transactions`. |
| Deposit below an auto-convert minimum | Expect source-currency credit with no completed `convert` block. |
| Auto-convert succeeds | Store source payment values and the target `convert` result separately. |
| Wrong token or wrong network | Do not fabricate a credit. Record evidence and escalate to support/recovery because recoverability is chain-specific. |
| Memo/tag-based chain | Display and validate every destination field returned by the platform; an address alone may be insufficient when a memo is required. |
| AML lock | Do not credit the end user until the authoritative status is released through the compliance process. |
| Wallet disabled after address display | Remove it from the UI immediately, but continue monitoring operational alerts for late transfers. |

## Reconciliation model

Run a periodic job that pages through `/v1/static-wallet/transactions`, upserts deposits by txid, and compares their `merchant_amount`, status, and optional conversion result with your internal ledger. Webhook delivery should make reconciliation fast, but reconciliation must make it complete.