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 answers201, 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
EVMaddress 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 achainTyperather than achainId.
| 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:
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.