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 | Prunes expired authorizations and spent claim markers | 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.
5. Keeper injection and the fee ante
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.
tokenfactory also installs a fee ante decorator. For a transaction carrying a
MsgExecute*, it charges the authorization holder in ucen, enforces the
signed max_fee, marks the authorization attempted, and rejects a nested or
multi-message execution. There is no reservation store wrapper since
v1.2: authorizations no longer reserve balances or lock
NFTs.
Ante handler
The existing ante chain is wrapped by three tokenfactory decorators:
FeePayerDecorator // outer
NFTSendCheckDecorator // inner
ZeroBalanceCheckDecorator
... the pre-existing ante chain ...
FeePayerDecorator implements the v1.2 settlement-fee rule. NFTSendCheckDecorator
re-checks NFT sends, including ones nested inside an authz.MsgExec.
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 application registers the plan names v0.55 and v1.1. The handler runs
RunMigrations with the version map the chain carried in. 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 |
tokenfactory | 2 | 3 | MigrateSwapAuthorizationKindV3 |
The tokenfactory 1→2 migration seeds the three genesis counters — factory
denoms, pool policies, and blacklisted addresses — that the state limits depend
on. The 2→3 migration binds every pre-existing authorization to SWAP.
There is no v1.2 plan. v1.2 keeps tokenfactory at consensus version 3 and
ships no migration, because a v1.2 network starts from a new genesis. See
Upgrading a network.