Transfers and compliance
Every send on CenterChain — bank, NFT, and swap settlement alike — passes through one function. The first rule that matches decides.
The blacklist
A blacklist is a per-denom list of addresses. Being blocked on one token has no effect on any other, and a denom with no authority metadata is never subject to it.
- Only the denom admin can add or remove an entry, and an account cannot blacklist itself.
- Adding is idempotent; removing decrements a global counter.
- The cap is 100,000 blacklisted addresses across the whole chain.
- The check is absolute. It applies under an open policy, to admins, to delegates, and to the module authority.
The restricted-denom rule
is_restricted is set by the denom admin. When it is set, a denom can only be
sent to an address that already holds a balance of it, unless the sender is
the admin, one of the delegates, or the send is an internal leg of a swap.
This is the on-chain circulation control: an issuer can close a token to new addresses while leaving existing holders able to trade among themselves.
:::note This is not a KYC list The rule tests a balance, not an identity. The chain can prove that a recipient already holds the token; it cannot prove who that recipient is. Treating this as an identity check would be a mistake in both directions — it blocks nothing extra, and it claims an assurance the chain cannot make. :::
The ante checks
Two decorators wrap the ante handler, both from tokenfactory:
| Decorator | When | What it does |
|---|---|---|
NFTSendCheckDecorator | Every transaction | Walks nft.MsgSend, including ones nested inside an authz.MsgExec up to eight levels deep, and rejects locked, blacklisted, or unknown-counterparty transfers. |
ZeroBalanceCheckDecorator | CheckTx and simulate only | Re-applies CanSendTokens to every MsgSend after the rest of the ante chain, so a simulation shows the true result. |
The zero-balance decorator does not run in DeliverTx. It exists so that a
dry run and a real transaction agree, not to change consensus behaviour.
Reservations
A fungible swap authorization reserves max_from against the holder's balance.
The reservation is enforced at the store layer: a wrapper around the bank's
KV store rejects any write that would drop a balance below the amount open
authorizations have reserved.
This holds regardless of which code path moved the funds. A transfer through the AMM, a direct send, a staking unbonding — anything that decreases the balance below the reservation fails.
Ordering with income
Income prepends its send restriction and tokenfactory appends its own, so the order on every bank send is:
1. income settle both sides against their pre-transfer balances
2. tokenfactory blacklist, then restriction
Income must run first: a holder's entitlement is a function of the balance they had before the transfer. Checking compliance first would mean settling a balance that no longer exists.
Blocklist versus freeze
The chain has no separate account-level freeze. What exists is:
| Mechanism | Scope | Effect |
|---|---|---|
| Per-denom blacklist | One denom, one address | No send in that denom, by anyone |
is_restricted | The whole denom | No transfer to a new address, by anyone except the admin and delegates |
| Income blocklist check | The claimant | A claim is rejected outright for a blocklisted holder |
A transfer barrier is not immutability. An admin can still burn, force transfer, or change the admin of a restricted denom. Restriction controls circulation; it does not protect the token from its own administrator.