Chain Integration
The API talks to the blockchain through the chain module (apps/api/src/modules/chain/), which is @Global() so every module can inject it. Application code works with domain types behind four injection tokens — it never imports CosmJS or builds protobuf by hand outside this module.
The four tokens
Defined in chain/interfaces/ and bound in chain.module.ts:
| Token | Interface | Responsibility |
|---|---|---|
CHAIN_GOVERNANCE | ChainGovernanceService | Query groups, members, policies, proposals, votes |
CHAIN_TOKEN | ChainTokenService | Query balances and total supply |
CHAIN_BROADCAST | ChainBroadcastService | Broadcast signed txs, simulate gas |
TRANSACTION_BUILDER | TransactionBuilderService | Build MsgSubmitProposal, MsgVote, MsgExec, etc. |
Consumers inject with @Inject(CHAIN_GOVERNANCE) and depend on the interface type. Implementations live in chain/cosmos/ (CosmJS); there are also chain/stubs/ used in tests, and a per-organization RPC context (chain/context/) so each Organization can target its own node when PER_ORG_RPC_ENABLED=true.
modules/<feature>/services/ ──@Inject──▶ CHAIN_* / TRANSACTION_BUILDER (interfaces/)
│
cosmos/ (CosmJS) │ or stubs/ (tests)
▼
cbdc-node (Cosmos SDK RPC)
Transaction flow
When a user submits a proposal (e.g. a mint):
- The feature service asks
TRANSACTION_BUILDERto build the appropriateMsgSubmitProposal(wrapping the innerx/grouporx/cbdcmessage) plus the proposer's vote, usingEXEC_TRYso it executes the moment the threshold is met. - The custody module signs the tx bytes with the proposer's encrypted key (
chain-tx-executor.service.ts). CHAIN_BROADCASTsubmits the signed tx and waits for inclusion.- If the proposer's vote alone meets the policy threshold, the chain executes immediately; otherwise the proposal stays open for other members to vote.
Custody
The custody module (apps/api/src/modules/custody/) manages per-user signing keys:
- Keys are AES-256-GCM encrypted at rest with the server secret
CUSTODY_SECRET. chain-tx-executor.service.tsdecrypts in-memory, signs, and broadcasts — the plaintext key never leaves the module.
cbdc-nodeis an Ethermint-based Cosmos SDK fork, so signing useseth_secp256k1(HD coin type 60) and the/ethermint.crypto.v1.ethsecp256k1.PubKeypubkey type — seescripts/setup-chain.tsfor the canonical signer.
Instant payment memo encryption
A cross-organization instant payment settles as a real MsgSend of wholesale CBDC between the two banks' ops accounts. The payment's retail details (sender/recipient, paymentId, free-text reference) travel in the transaction memo, which is encrypted so only the two banks can read it — a block-explorer watcher sees an opaque ip1.… token, not who paid whom or the concept. The wholesale amount and the two bank ops-addresses stay visible on-chain.
It uses envelope encryption sealed to both banks' ops-account secp256k1 pubkeys (AES-256-GCM content key, wrapped per bank via ECDH + HKDF-SHA256). See Instant Payment Memo Encryption for the full scheme, on-chain format, and decryption flow.
The chain itself
apps/chain only wraps the chain: a Makefile builds the cbdcd binary from the git submodule at apps/chain/node (repo Peersyst/cbdc-node) and init-chain.sh runs a single-node localnet. The custom x/cbdc module provides the MsgMint / MsgBurn messages (/cbdc.MsgMint, /cbdc.MsgBurn); multisig governance uses the standard Cosmos SDK x/group module. The submodule source is not vendored into this repository.
Swapping the chain
Because consumers depend only on the four interfaces, supporting a different chain means writing new adapters under chain/ and re-binding the tokens in chain.module.ts — no changes to feature services, controllers, or the frontend.