Tracking a token that is not in the catalogue
ChainOS credits deposits of a fixed, published set of tokens. A deposit of a contract that is not in that set is not detected and not credited — the funds are at the address, they are yours, and nothing in your ledger knows about them.
That is a design choice rather than a limitation to work around. A chain carries thousands of
tokens, most of them worthless, and many of them built specifically to resemble one that is
not. An allowlist is the only thing standing between your customers' balances and a contract
named USDT that somebody deployed this morning.
This page is how you add a real one.
1. Check it is actually absent
curl -s $CHAINOS/v1/treasury/tokens -H "X-API-Key: $CHAINOS_KEY"
curl -s $CHAINOS/v1/treasury/tokens/USDC -H "X-API-Key: $CHAINOS_KEY"
{
"symbol": "USDC",
"deployments": [
{ "chain": "eth", "contract": "0xA0b8…", "decimals": 6, "monitored": true },
{ "chain": "polygon", "contract": "0x3c49…", "decimals": 6, "monitored": true }
],
"unsupportedChains": [
{ "chain": "bsc", "reason": "Circle does not issue native USDC on BNB Smart Chain." }
]
}
Every token response carries unsupportedChains alongside its deployments, each with a
reason. A missing pair is always one or the other — listed as deployed, or listed as
deliberately absent with an explanation. There is no third category of "unknown", and reading
this field while you are designing is how you find a gap at integration time rather than
through a 422 in production.
So before you request anything, check three things:
- Is the token listed at all?
- Is the chain you care about in
deployments, or inunsupportedChains? - Is the contract address in the deployment the one you expect?
Point 3 catches more problems than the other two. A token listed on a chain at a contract you did not expect usually means you are looking at a bridged version — see below.
2. Bridged, deprecated, and why Polygon has two USDCs
Two flags on a deployment matter more than they look.
deprecated — superseded, usually by a native issuance, and still monitored. Deposits
to a superseded bridged token are real money and are credited. A deprecated deployment is one
you should stop quoting to your users, not one you can stop watching.
bridgeable — whether the token can be moved cross-chain. False for anything you could not
redeem with its issuer. Bridging a third-party wrapper would hand you an asset nobody is
obliged to honour, so the catalogue refuses to treat it as bridgeable however liquid it looks.
Polygon's bridged USDC.e was both, and was carried as a separate catalogue entry until
registry v2 withdrew it. That is the shape every superseded wrapper takes: a different contract
from the native issuance, trading at a different price under stress, and not bridgeable with
CCTP. If you need one carried, request it — and if you accept both, treat them as two assets in
your ledger with two symbols. A customer's USDC.e balance is not a USDC balance, and merging
them is a decision you cannot unmake.
3. The decimals trap
Read this before you fill in the request form, because it is the field that costs the most.
USDT is six decimals on Ethereum and eighteen on BNB Smart Chain. Same issuer, same brand, same ticker, different precision.
This is why decimals appears on each deployment rather than once per token, and why you
must read it from the deployment you are actually crediting.
| Getting it wrong | The result |
|---|---|
| Using Ethereum's 6 for a BSC deposit | Credit the customer one million-millionth of what they sent |
| Using BSC's 18 for an Ethereum deposit | Credit them a trillion times too much |
Neither throws. Both are silent, and the second one is discovered by a customer.
Every deposit webhook carries decimals for exactly this reason. Store it per ledger row —
see Customer wallets §4 — and never keep a constant in your code.
4. Request the token
curl -X POST $CHAINOS/v1/treasury/token-requests \
-H "X-API-Key: $CHAINOS_KEY" -H "Content-Type: application/json" \
-d '{
"chain": "eth",
"contractAddress": "0x6B175474E89094C44Da98b954EedeAC495271d0F",
"symbol": "DAI",
"decimals": 18,
"note": "Requested by three institutional customers for settlement."
}'
This records a request and puts the contract in front of a ChainOS administrator. It does not begin monitoring, and deposits of the token are not credited while it is under review.
Do not advertise the token to your customers at this point. Funds sent to an address expecting them to appear will not appear, and you will be explaining why for as long as it takes.
The symbol and decimals you send are recorded as your claim, not adopted as fact. The
contract is read on chain, its decimals() is compared with what you declared, and a second
administrator approves publication before anything changes.
A mismatch between your declaration and the chain is precisely what that review exists to catch — so state the values as you have them, not as you think they ought to be. A request that quietly corrects itself to match the chain would remove the one signal that says you were looking at the wrong contract.
What to put in the request
| Field | Get it from |
|---|---|
contractAddress | The issuer's own documentation, not a block explorer search, and not a token list |
symbol | Whatever your ledger will call it. It is a label, not an identity |
decimals | Your own reading of the contract, per chain |
note | Why you need it. A named customer or a volume figure moves faster than "completeness" |
Source the contract address from the issuer. A block-explorer search for a ticker returns every contract that claims that name, ordered by how much attention they have attracted. That is how a lookalike contract gets into a token list, and a review is a poor place to discover it.
5. Track it
curl -s $CHAINOS/v1/treasury/token-requests -H "X-API-Key: $CHAINOS_KEY"
The field to act on is monitored. It is false in every state but published:
state | monitored | What it means |
|---|---|---|
under_review | false | With an administrator |
accepted | false | Approved into a pending change. Still not credited |
published | true | In the catalogue. Deposits are now detected and credited |
declined | false | Reviewed and declined. outcome carries the reason |
accepted is the state that catches people. It reads like success and credits nothing.
Posting the same contract twice is a no-op — your account counts once either way, so a retry after a timeout is safe.
6. When the catalogue changes
Publication is a versioned operation. GET /v1/treasury/routes returns the
registryVersion its answer came from, and a new version reaches every node within seconds of
being published. You do not need to do anything to pick it up.
A token already in the catalogue never changes its contract address or decimals. A correction is published as a new version — which is auditable — rather than edited in place, which is not.
Deposits that arrived while you were waiting
They are still there. The token was not monitored, so nothing detected them, but the funds sat at an address derived from your mnemonic the whole time.
Once the token is published, monitoring begins from that point. Whether earlier transfers
are picked up depends on the chain and how far back the ingest path looks, so treat a
pre-publication deposit as something you reconcile by hand: find it on the chain, record it in
your ledger with the right decimals, and match it to a customer from the address.
The lesson is the ordering. Request the token, wait for published, then tell customers they
may send it.
7. After it is published
Three things need doing, and none happens automatically.
Add it to your own asset table, with the per-chain contract and per-chain decimals. Your ledger needs to know it exists before a webhook arrives mentioning it.
Create a sweep policy for it. Sweep policies are per (chain, token). A newly published
token has no policy, so its deposits accumulate at customer addresses until you configure one.
Preview it first and set the dustFloor from this token's economics rather than copying the
one beside it. See Sweeps.
Check whether it can bridge. A token with bridgeable: false cannot be moved cross-chain
by ChainOS, whatever liquidity exists for it elsewhere. If your treasury design assumed
consolidating it onto one chain, check this before the design depends on it:
GET /v1/treasury/routes/check?token=DAI&from=eth&to=polygon
Most tokens outside the issuer-operated set are not bridgeable here, and that is the expected answer rather than a gap.
8. What is not built
Per-account token enablement does not exist. The catalogue is platform-wide: a published token is published for everybody, and there is no way to enable a token for your account alone or to opt out of one you do not want to credit.
In practice that means you should filter on your side. If a customer deposits a catalogue token you do not offer, your webhook handler will receive it — decide deliberately whether to credit it, hold it unattributed, or return it, rather than letting a default branch credit an asset you never agreed to hold.
Next
- The token catalogue — the reference page.
- Customer wallets — the ledger that stores
decimalsper row. - One asset, many chains — which tokens can actually cross, and why.