Skip to content

Static wallets

A static wallet is an address that belongs to one of your customers for good, rather than to a single order. Nothing expires and nothing states an amount: you hand the address out once and reconcile whatever arrives on it against your own reference.

Use it for account top-ups and recurring deposits. Use an invoice when you need a specific amount for a specific order.

Issue an address

curl -X POST https://api.pay.nullswap.com/api/v1/static-wallets \
  -H "x-api-key: sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "chainType": "EVM", "reference": "customer-42" }'
Field Type Required Notes
chainType enum yes EVM, TRON, SOLANA, BITCOIN, LITECOIN, TON, MONERO
reference string yes Your own name for the customer. Max 128 characters
{
  "id": "f6a7b8c9-d0e1-4234-a567-8b9c0d1e2f34",
  "merchantId": "9f1d2c3b-4a5e-4f60-8b71-2c3d4e5f6a7b",
  "chainType": "EVM",
  "address": "0x1234567890abcdef1234567890abcdef12345678",
  "reference": "customer-42",
  "createdAt": "2026-01-01T12:00:00.000Z"
}

Two properties make this endpoint easy to work with:

  • It is idempotent on reference. Asking again with the same reference returns the address that reference already has rather than minting a second one. It always answers 201, whether it created something or not, so you can call it on every visit instead of storing the address yourself.
  • The address is issued per chain family, not per chain. One EVM address serves Ethereum, Arbitrum, BNB Chain and every other EVM network. A customer needs one address per family, not one per network — which is why this route takes a chainType rather than a chainId.
Status message Cause
400 InvalidRequestBodyError Missing field, or chainType is not one of the enum values
403 MerchantIsBlockedError The merchant is blocked
422 InvalidStaticWalletReferenceError reference is empty or longer than 128 characters
404 MerchantWalletForChainTypeNotFoundError You have no wallet configured for that family

Reading addresses

Method Path Purpose
GET /api/v1/static-wallets List, filtered by id, chainType, reference, address
GET /api/v1/static-wallets/{staticWalletId} Read one

reference is the normal way to find an address you issued earlier:

GET /api/v1/static-wallets?reference=customer-42&limit=10

Payments

curl "https://api.pay.nullswap.com/api/v1/static-wallets/payments?settlement=PENDING&limit=100" \
  -H "x-api-key: sk_live_..."

Without staticWalletId this lists every arrival across all of your addresses, which is what you reconcile against. Filters: id, staticWalletId, status, settlement, plus the usual pagination.

{
  "id": "a7b8c9d0-e1f2-4345-a678-9b0c1d2e3f45",
  "staticWalletId": "f6a7b8c9-d0e1-4234-a567-8b9c0d1e2f34",
  "chainTransactionId": "cc33dd44-ee55-4f66-8077-889900112233",
  "chainTransferId": "dd44ee55-ff66-4077-8188-990011223344",
  "chainId": "1c9a7f36-5f2b-4a83-9d51-0e6b7c8d9a10",
  "tokenId": "3b7e1a48-2c6d-4e9f-8a01-5d4c3b2a1908",
  "fromAddress": "0xfedcba0987654321fedcba0987654321fedcba09",
  "amount": "1500000",
  "status": "CONFIRMED",
  "confirmations": 12,
  "amlStatus": "PASSED",
  "amlResultId": "5c6d7e8f-9a0b-41c2-8d34-5e6f7a8b9c0d",
  "settlement": "SWEEPING",
  "sweepTransferId": "ee55ff66-0077-4188-8299-001122334455",
  "createdAt": "2026-01-01T12:00:00.000Z",
  "updatedAt": "2026-01-01T12:03:00.000Z"
}

Static wallets do not emit webhooks

No event in the webhook catalogue covers a static wallet. Reconciling them means polling this endpoint. A reasonable job filters on settlement=PENDING plus a createdAt-ordered window and runs every minute or two.

Watch settlement, not status

A static address collects funds and hands them on, so what matters is not whether the transaction confirmed but where the money ended up.

flowchart TD
    A["Deposit arrives"] --> B["status DETECTED<br/>settlement PENDING"]
    B --> C["Confirmed to the required depth"]
    C --> D{"AML screening"}
    D -- "PASSED / SKIPPED" --> E{"Payout wallet configured<br/>for this chain family?"}
    D -- "FAILED" --> F["settlement WITHHELD<br/>money stays on the address"]
    E -- "yes" --> G["settlement SWEEPING<br/>sweepTransferId set"]
    E -- "no" --> H["settlement PENDING<br/>nothing has moved"]
    G --> I["Credited to your balance"]
settlement Meaning
PENDING Not screened yet, or screened clean but no sweep could be raised. Nothing has moved.
SWEEPING Clean, and the sweep into your own wallet has been handed off. sweepTransferId is set
WITHHELD Flagged by screening, and staying on the static address
status Meaning
DETECTED On chain, not yet confirmed or screened. Never money you have
CONFIRMED Confirmed and screened
FAILED The transaction that carried it reverted or was reorged out

Note that PENDING is ambiguous by design — it covers both "we have not screened this yet" and "it is clean but we could not move it". Read amlStatus to tell the two apart: null means screening has not run.

There is no API route to release a WITHHELD payment. If you believe a verdict is wrong, quote its amlResultId when you raise it.

Once a payment reaches SWEEPING and the sweep settles, the funds show up under balances as CLEAN, and you withdraw them from there.