# Static Wallet Address

A **Static Wallet Address** is one permanent crypto address assigned to each of your
users. Create the user once and you get the address back; it stays active and reusable for
every payment that user ever makes. It does not rotate, it does not expire, and it is not
reissued.

**What it replaces.** The classic gateway model mints a fresh address for every payment and
makes you hand the customer new instructions each time. A static address ends that loop:

- **No address generation per payment.** One address per user, created once.
- **Automatic attribution.** Whatever lands on the address is credited to that user — you
  never match a payment by hand.
- **Fewer mistakes, fewer tickets.** The customer saves one address in their wallet and
  reuses it; there is no "which address was this one" question.
- **Fully automated over the API.** Create, read, credit and pay out with the same key.
- **A clean history.** Every deposit is already filed under the user it belongs to.

You can print the address on a receipt, save it to a customer's profile, or show it in an
app, and it will still be correct in five years. Everything else on the platform —
balances, payouts, cards — is built on top of these addresses.

```bash
curl -X POST https://api.cryptoinfra.build/v1/users \
  -H "Authorization: Bearer $CRYPTOINFRA_API_KEY" \
  -H "content-type: application/json" \
  -d '{"external_user_id": "10482"}'
```

```json
{
  "user_id": "10482",
  "addresses": [
    { "chain": "tron", "address": "TJYBSfZfwwa28mYZPkE9uodBwGbnwEqBK1" }
  ]
}
```

`external_user_id` is a number of 5 to 10 digits — the id this user has in your own
system. Anything else is refused with `400 validation_error`.

Read the static address again at any time with `GET /v1/users/{externalId}/addresses`. You
never need to store it yourself, though you may.

## One static address, not one per coin

The platform runs on **one network, TRON**. A static address belongs to the chain, and every
asset on it lands on the same one.

| Chain | The one address accepts | Confirmations before credit |
| --- | --- | --- |
| `tron` | TRX, USDT (TRC20), USDC (TRC20) | 19 |

So a customer sending USDT on TRON and a customer sending TRX use the *same* string. The
platform tells them apart by what the transaction actually moved, not by where it was sent.

This is why an address alone cannot tell you **which order** was paid — see
[Matching a payment to an order](#matching-a-payment-to-an-order) below.

### What is open

`GET /v1/assets` is the authority on what can be received right now. If an asset is not
listed there, it is not being watched, and **coins sent as it are not credited**. The
address list is shaped the same way: it is filtered to chains that can actually take money,
which is `tron` alone. It is still a list, so read it as one — by `chain`, not by position —
rather than assuming the shape of the array.

## How a payment becomes a balance

```
customer sends  →  confirming  →  credited
                                      ↓
                             balance + deposit.credited webhook
```

1. **`confirming`** — the transaction is in a block and the platform is counting
   confirmations against the number in the table above. Nothing is credited yet. A chain
   reorg here moves the deposit to `orphaned` and it is never credited.
2. **`credited`** — the balance moves, in the double-entry ledger, and
   [`deposit.credited`](webhooks) fires.

A deposit below the asset's minimum is written as `ignored_dust` instead (see below). The
contract also lists a `seen` status; it is legacy and no deposit is created in it.

You do not poll a chain and you do not run a node. Watch for the webhook, or read
`GET /v1/users/{externalId}/balances`.

### There is no deposit fee

**A deposit to an address we issue is credited in full.** A 100 USDT deposit credits
100.00 USDT; the only thing ever taken out of it is your own fee, if you have set one
(`merchant_fee` on the deposit). A deposit below `min_deposit` is never credited:
it is marked `ignored_dust` — a dust floor, not a charge.
`GET /v1/fees` returns your live schedule (`deposit.total` is `0`); read it rather than
hard-coding anything.

Every deposit row carries both figures, and they are not interchangeable — `amount` is what
the customer sent, `amount_credited` (with its `amount_credited_base` twin) is what reached
the balance. Reconcile a balance against `amount_credited` and reconcile a customer's claim
against `amount`. The `deposit.credited` webhook spells the same field `amountCredited`.

**Who sent it.** Every deposit carries `from_address` (the webhook: `fromAddress`). For USDT
and USDC it is the `from` of the token Transfer that paid the address — the wallet the money
came from, even when an exchange paid out through a batch or proxy contract signed by an
operator address. For TRX it is the sender. Show it to your user as "received from"; there is
no need to look the txid up on an explorer.

### Amounts too small to bother with

A deposit below an asset's `min_deposit` is marked `ignored_dust` and never credited: the
fee to move a few hundred base units exceeds the units themselves, so crediting it would
cost more than it is worth. `min_deposit` is per asset in `/v1/assets`;
read it from there rather than from this page, and treat `0` as "no floor".

## Matching a payment to an order

A static address is shared across every payment that customer ever makes, so an arriving
deposit tells you *who* paid, never *what for*. The address is a running balance per
customer — top-ups, wallets, exchange accounts, anything where money is just credit.

If you are billing per order, do **not** try to match on amount alone against a shared
address — two customers paying 25 USDT in the same minute are indistinguishable. Give each
order its own user (a user is free and permanent) and credit the order from that user's
`deposit.credited` event, or keep the order on your side and let the customer spend from
their balance.

## Ways money is actually lost

These are the failure modes that cost real customers real coins. All of them are the
customer's action, and none of them can be reversed by us, so the wording on your own
checkout matters more than anything in this API.

**Wrong network, right-looking address.** USDT exists on many chains and wallets happily
send the wrong one. USDT sent over any network other than TRON does not arrive and cannot be
recovered. Always print the network next to the address — "TRON (TRC20) only" — not just the
coin name.

**An asset that is not open.** Covered [above](#what-is-open): if it is not in
`/v1/assets`, it is not watched.

**A token nobody listed.** The TRON address accepts TRX, USDT and USDC. A different TRC20
token sent to it is not credited — the address is watched for the assets in `/v1/assets` and
nothing else.

**A stale address from your own database.** A static address never changes, so this should
be impossible — but if you cache them, cache them against the `external_user_id` you asked
for. Showing customer A the address of customer B credits B.

## What happens behind the static address

Two things worth knowing, both invisible to you:

**Keys are not on the API server.** Addresses are derived from watch-only extended public
keys. The server that answers these requests can generate an address and can never spend from
one.

**The ledger is the balance, not the chain.** A user's coins sit at their own address and
their payouts leave from it, so the two usually agree — but the ledger is net of holds and
of fees the moment they are charged, while the chain catches up a few seconds later, and
balances that existed before 4 September 2026 live in the platform's own wallets. Read
`GET /v1/users/{externalId}/balances`; a block explorer will mislead you here, and it is
the single most common support question merchants receive. Since 19 September 2026 that
response also carries `on_chain` per asset — what the address holds at that moment — so
the two figures sit side by side. They differ by design: our fees leave the address for
the platform's profit wallet as they settle, and earnings (your commission, your own fees)
are credited to the balance from there. `amount` is what a payout may spend.

## Reference

- [`POST /v1/users`](users) — create a user, get their static wallet address
- [`GET /v1/users/{externalId}/addresses`](users) — read it again
- [`GET /v1/users/{externalId}/balances`](balances-deposits) — the authoritative balance
- [`GET /v1/users/{externalId}/deposits`](balances-deposits) — one user's deposit history with both amounts
- `GET /v1/deposits` — every user's deposits, newest first, with `user_external_id` on each row; filters `status`, `asset_id`, `search` (txid, address or user id), `from`/`to` (IST days), `sort`/`dir`, `limit`/`offset`; dust transfers are hidden unless `dust=show`
- [`GET /v1/assets`](reference) — what can be received, with `min_deposit` and confirmations
- [`GET /v1/fees`](reference) — the live schedule (deposits are free; your own fee, if any)
