Hub Contracts¶
The hub module is the pool-manager control plane. Pools, share classes, holdings, accounting, and prices are administered here; the results propagate to spoke chains as verified messages. All hub contracts are unmodified Centrifuge V3 (see the fork framing).
| Contract | Role |
|---|---|
Hub |
Central control plane; entry point for all pool-manager actions |
HubRegistry |
Canonical registry of pools, assets, managers, and dependencies |
ShareClassManager |
Share-class lifecycle, issuance tracking, share prices |
Accounting |
Per-pool double-entry journal |
Holdings |
Per-pool asset bookkeeping and cross-chain snapshot tracking |
HubHandler |
Inbound handler for hub-bound cross-chain messages |
Hub¶
Hub coordinates everything: share-class operations delegate to ShareClassManager, holdings operations to Holdings with immediate accounting reconciliation, and every cross-chain notification is dispatched through the messaging layer. Its wired dependencies include the holdings, accounting, registry, share-class manager, outbound dispatcher, multi-adapter, gateway, and an optional feeHook.
Access control tiers¶
- Ward (
auth) — system contracts only:file,createPool, and direct accounting-amount updates. - Hub manager — per-pool administrators. Almost every pool method requires
hubRegistry.manager(poolId, msgSender()). A hub manager has full rights over all actions of its pool. The manager set is maintained viaupdateHubManager. Note the use ofmsgSender()rather thanmsg.sender— the Hub supports batched multicalls, andmsgSender()resolves the true caller through the batching context. - Request manager —
requestCallbackis gated to the pool's registered hub-side request manager (see BatchRequestManager).
Accounting discipline¶
Every accounting mutation follows a strict cycle:
lock() reverts unless total debits equal total credits, so no Hub operation can leave the books unbalanced. Holding amount changes map to Asset/Equity accounts (Expense/Liability for liabilities); valuation re-marks map to Asset versus Gain/Loss accounts.
Fee hook extension point¶
After price updates, the Hub calls feeHook.accrue(poolId, scId) if a fee hook is configured. This is the extension point that ArkonixFeeManager targets — it can also be used directly as the share-price-update wrapper. See Arkonix Extensions.
Function surface¶
- System:
file,createPool. - Pool notify/config:
notifyPool,notifyShareClass,notifySharePrice,notifyAssetPrice,updateRestriction,updateVault,updateContract,setRequestManager,updateBalanceSheetManager,updateHubManager,setAdapters, share metadata, price-age limits. Every cross-chain notify emits locally and then dispatches the message with an explicit refund address. - Holdings config:
initializeHolding/initializeLiability,updateHoldingValue/updateHoldingValuation/updateHoldingIsLiability,setHoldingAccountId. - Accounting:
createAccount,setAccountMetadata,updateJournal. - Views:
holdingAccounts,liabilityAccounts,pricePoolPerAsset.
HubRegistry¶
The canonical registry (ward = Hub). State: asset decimals, pool metadata, the pool's base currency (which doubles as the pool-exists flag), the hub-manager set per pool, named per-pool dependencies, and each pool's hub-side request manager per spoke chain.
Invariants: assets and pools register exactly once; a pool must exist before it can be updated.
ShareClassManager¶
Share-class lifecycle and issuance tracking (ward = Hub). State highlights:
- Salts: globally unique deployment salts for share tokens; a salt must be prefixed with its pool ID.
- Issuance:
totalIssuanceper share class, plusissuancePerNetworkper spoke chain — per-chain issuance/revocation reconciliation with a guard against negative issuance. - Prices:
pricePoolPerShareper share class stores the price with its computation timestamp; future timestamps are rejected. - Metadata: names 1–128 bytes, symbols 1–32 bytes.
- IDs: share-class IDs derive from the pool ID plus an incrementing per-pool counter, with preview views for the next ID.
Accounting — double-entry bookkeeping¶
Accounting maintains a per-pool journal under a lock/unlock discipline:
unlock(poolId)→addDebit(account, amount)/addCredit(...)/addJournal(debits[], credits[])→lock().lock()revertsUnbalancedunless total debits equal total credits — balance is enforced at the contract level, not by convention.- Accounts are debit-normal or credit-normal; an account's value is the absolute difference of total debits and credits, signed by its normal side.
- Account types:
Asset,Equity,Loss,Gain,Expense,Liability. - Transient storage tracks in-flight journal IDs, so interleaved entries within one transaction share a journal.
Why this matters for auditability
Every fund movement, issuance, and valuation change in a pool produces balanced journal entries in a conventional double-entry ledger, on-chain. An auditor or due-diligence reviewer can reconstruct a pool's books directly from contract state and events — assets, equity, gains, losses, and liabilities all tie out by construction, because unbalanced entries cannot be committed.
Holdings¶
Per-pool asset bookkeeping plus snapshot tracking. For each (pool, shareClass, asset), Holdings stores the valuation source, a liability flag, the asset amount, and its value; account-ID mappings link each holding to its accounting accounts.
Key mutators (all ward-gated): initialize, increase/decrease (amounts convert to values via PricingLib.convertWithPrice; decrease clamps at zero), update (re-mark via the holding's valuation, returning a signed delta for accounting), updateValuation, and setSnapshot (nonce-guarded).
Snapshots — knowing when a spoke is in sync¶
Per (pool, shareClass, spokeChain), Holdings records a snapshot flag with a nonce: whether that chain's reported assets and shares are fully in sync with the hub. The flag is set when a spoke's BalanceSheet queues fully drain, and it is what makes price-per-share derivation safe — NAV computed from holdings is only meaningful when no deltas are still in flight. A snapshot hook (onSync) fires when a chain drains its queues, which the NAV automation managers consume.
HubHandler¶
The inbound message handler for the hub side (counterpart to the Spoke's handlers). It processes:
registerAsset— records assets announced by spoke chains.request— routes investment requests to the pool's hub-side request manager.updateHoldingAmount— applies a spoke's queued holding delta: holding increase/decrease, accounting entries, and snapshot update.updateShares— applies issuance/revocation deltas with snapshot update.initiateTransferShares— coordinates burn-on-origin / mint-on-target cross-chain share transfers.