Skip to main content

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:

TokenInterfaceResponsibility
CHAIN_GOVERNANCEChainGovernanceServiceQuery groups, members, policies, proposals, votes
CHAIN_TOKENChainTokenServiceQuery balances and total supply
CHAIN_BROADCASTChainBroadcastServiceBroadcast signed txs, simulate gas
TRANSACTION_BUILDERTransactionBuilderServiceBuild 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):

  1. The feature service asks TRANSACTION_BUILDER to build the appropriate MsgSubmitProposal (wrapping the inner x/group or x/cbdc message) plus the proposer's vote, using EXEC_TRY so it executes the moment the threshold is met.
  2. The custody module signs the tx bytes with the proposer's encrypted key (chain-tx-executor.service.ts).
  3. CHAIN_BROADCAST submits the signed tx and waits for inclusion.
  4. 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.ts decrypts in-memory, signs, and broadcasts — the plaintext key never leaves the module.

cbdc-node is an Ethermint-based Cosmos SDK fork, so signing uses eth_secp256k1 (HD coin type 60) and the /ethermint.crypto.v1.ethsecp256k1.PubKey pubkey type — see scripts/setup-chain.ts for 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.