Application wiring
This page describes how the five modules are assembled into the application: registration order, what runs in each block, and the three mechanisms the chain uses to connect modules to each other.
:::note Naming
x/nft is the Go package; nftrwa is the module name, the store key, and the
query prefix. There is no centerchain.v1 core module: the five directories
under proto/centerchain/ are income, liquidity, nft, nftmarket, and
tokenfactory.
:::
Registered modules
Beyond the standard Cosmos SDK modules, the application registers:
| Module | On-chain name | Store key | Consensus version |
|---|---|---|---|
| Token factory | tokenfactory | tokenfactory | 3 |
| Liquidity | liquidity | liquidity | 5 |
| NFT | nftrwa | nftrwa | 1 |
| NFT market | nftmarket | nftmarket | 1 |
| Income | income | income | 1 |
IBC is registered outside the app-config composition path, together with the transfer application, interchain accounts, the 07-tendermint light client, and the 06-solomachine light client. There is no CosmWasm module.
Order matters
InitGenesis runs tokenfactory before nftmarket, because the market's
genesis import reads IsDenomRestricted to fail closed on a restricted denom in
a listing price or an accepted-denom list.
The five CenterChain modules run last in all three lifecycle lists, after the
standard modules, in the order nftrwa, nftmarket, tokenfactory,
liquidity, income.
Block execution
| Module | BeginBlock | EndBlock |
|---|---|---|
nftrwa | No-op | No-op |
nftmarket | Prunes up to 200 expired listings | No-op |
tokenfactory | No-op | No-op |
liquidity | Loads state, activates due fee updates, settles every emission schedule | In-memory invariants every block; bank reconciliation every 10,000 blocks |
income | No-op | Solvency check every 10,000 blocks |
Both invariant checks emit an alarm event and return no error. A violated invariant is therefore observable in the event stream without halting the chain.
How modules connect
The chain does not use the SDK's SendHooks interface anywhere. It uses three
other mechanisms instead.
1. Bank send restrictions
Two modules append a function to the bank's restriction list:
| Module | Call | Position |
|---|---|---|
income | PrependSendRestriction | Runs first. |
tokenfactory | AppendSendRestriction | Runs last. |
The order is deliberate: income must checkpoint a holder's accrual against the pre-transfer balance before tokenfactory decides whether the transfer is allowed at all.
2. The NFT transfer policy
The vendored cosmossdk.io/x/nft fork exposes a package-level transfer policy.
tokenfactory installs itself as that policy, so every NFT send is evaluated by
the same blacklist and restriction logic as a bank send.
A second ante decorator, NFTSendCheckDecorator, re-checks NFT sends — including
ones nested inside an authz.MsgExec, up to eight levels deep — and rejects
locked, blacklisted, or unknown-counterparty transfers before they reach the
handler.
3. Named burn hooks
x/nft keeps a named hook registry that runs on every burn in sorted order. Two
hooks are registered:
| Name | Registered by | Effect |
|---|---|---|
income | x/income | Releases the token's earmarked claimable. |
nftmarket | x/nftmarket | Refunds the listing deposit and clears the listing. |
The first hook to return an error aborts the burn and rolls the transaction back.
4. Keeper injection
The remaining coupling is direct keeper wiring rather than hooks:
liquidityregisters itself withtokenfactoryas the module allowed to create and mint LP denoms, and injects the tokenfactory keeper as its denom eligibility and restriction source.nftmarketinjects the tokenfactory keeper to read the restricted-denom rule.incomeinjects the tokenfactory keeper to read authority metadata and the blacklist, and the NFT keeper for the burn hook.liquidityis given an IBC asset-health checker that verifies the transfer module's denom trace, the channel, the connection, and the client state.
5. A store wrapper
tokenfactory also wraps the bank's KV store service. On any write that
decreases a balance, the wrapper checks the swap reservation counters and
rejects the write if the new balance would fall below what open swap
authorizations have reserved. This is enforced at the store layer, so it holds
regardless of which code path performed the transfer.
Ante handler
The existing ante chain is wrapped by two tokenfactory decorators:
ZeroBalanceCheckDecorator // outer
NFTSendCheckDecorator // inner
... the pre-existing ante chain ...
ZeroBalanceCheckDecorator runs only in CheckTx and simulate, and re-applies
CanSendTokens to every MsgSend in the transaction after the rest of the
chain has run. Its purpose is to make the result visible in a simulation, not
to change consensus behaviour.
Module accounts
| Account | Permissions | Blocked from receiving |
|---|---|---|
tokenfactory | Minter, burner | No |
liquidity | Minter, burner | Yes |
nft | None | Yes |
nftmarket | None | Yes |
income | None | No — the yield pool must be able to receive |
The liquidity module also creates per-pool accounts on demand:
liquidity-reserve-{id}, liquidity-farm-{id}, and liquidity-fee-{id}.
Chain identity
| Setting | Value |
|---|---|
| Bech32 account prefix | center |
| Validator prefixes | centervaloper, centervalcons |
| Bond and mint denom | ucen |
| Base and display denom | ucen / cen, symbol CEN, exponent 6 |
Upgrades
The upgrade handler runs RunMigrations under the plan name v0.55. The
registered migrations are:
| Module | From | To | Migration |
|---|---|---|---|
liquidity | 1 | 2 | MigrateLegacyState |
liquidity | 2 | 3 | MigrateFarmLockDurationBound |
liquidity | 3 | 4 | MigratePoolLifecycleParams |
liquidity | 4 | 5 | MigratePoolFeeCounters |
tokenfactory | 1 | 2 | MigratePoolPolicyGenesisV2 |
The tokenfactory migration seeds the three genesis counters — factory denoms, pool policies, and blacklisted addresses — that the state limits depend on.