Home / Architecture

Platform Architecture

Complete contract dependency graph, granular freeze manager, custodian wallet management, mint/escrow, depositary separation, circuit breakers, oracle, settlement, security, data model, middleware, upgradeability, and monitoring.

System Architecture & Legacy Integrations

Tokenize bridges traditional banking infrastructure with blockchain technology through a layered architecture.

In Plain English

Think of it like this: your bank already has systems (SAP, Oracle) that manage your accounts. Tokenize adds a new "layer" on top that talks to these existing systems. Your money still sits in traditional bank accounts, but now you can also use blockchain to move value faster and cheaper. It's not replacing your bank — it's making your bank's services work better.

Legacy Banking Systems

Core Banking System

SAP S/4HANA or Oracle Financials — handles traditional EUR deposits and customer records

Integration: REST API → Middleware Service
SWIFT Network (Messaging Only)

SWIFT is the messaging network, not a payment rail. EUR settlement goes via TARGET2 (central bank) or STEP2 (EBA Clearing). Flow: SAP/Oracle → SWIFT message (pacs.008) → correspondent bank → TARGET2 settlement → recipient bank.

Integration: SWIFT pacs.008 message → ISO 20022 XML to correspondent bank
SEPA Instant

European payment infrastructure for EUR transfers — fallback for EU corridors

Integration: SEPA → CorridorRegistry
KYC/AML Provider

TraditionalKYC provider (e.g., Sumsub, Jumio) — performs initial identity verification

Integration: KYC Result → IdentityRegistry (on-chain)

Tokenize Platform

Middleware Service

Node.js service that bridges off-chain data with on-chain contracts

Connects: Legacy systems ↔ Smart contracts
Asset Management Oracle

Updates NAV based on underlying asset performance and fund valuations

Feeds: NAV → RegulatedYieldVault
Yield Distribution Engine

Automates yield accrual and distribution to vault participants

Connects: YieldEngine → RegulatedYieldVault
Smart Contracts (Sepolia)

7 deployed contracts handling payments, compliance, and vault operations

All verified on: Etherscan

End-to-End Data Flow

Legacy Core

Middleware

Smart Contracts

USDC deposits

Data transformation

On-chain settlement

1. Where SAP/Oracle Lives in the Architecture

SAP S/4HANA and Oracle Financials are bank-internal enterprise systems. They live entirely on the bank's private infrastructure — never on the blockchain, never exposed to public networks. The diagram below shows the three-layer architecture:

Layer 1 — Fiat
SAP S/4HANA
Oracle Financials
Core Banking
General Ledger
Bridge Layer
Middleware Service
Node.js / Java Service
REST API + RPC
ONLY bridge between legacy ↔ blockchain
Layer 2 — Blockchain
CBPR Contract
IdentityRegistry
CorridorRegistry
USDC (ERC-20)
SAP/Oracle: Bank's private network (never on-chain) Middleware: Reads events + writes transactions Contracts: Sepolia testnet (public) Production target: Base L2 (low fees, regulated) or permissioned EVM (Besu/Quorum for consortium banking)

Key Principle: SAP/Oracle receives payment status updates via the Middleware Service REST API. The legacy system creates ledger entries for accounting reconciliation, but it never directly interacts with the blockchain. The Middleware Service is the sole bridge — it listens to on-chain events (via WebSocket/JSON-RPC) and pushes updates to SAP/Oracle, and it receives payment instructions from SAP/Oracle and submits them to the CBPR contract.

2. Rich Data Exchange — How Banks Do It

Cross-border payments carry far more than just sender, recipient, and amount. They include invoice references, purpose of payment, HSN/tax codes, country of origin, remittance information, and regulatory data. Here's how the industry handles this:

Legacy SWIFT MT103 — Structured Text Fields

SWIFT MT103 (single customer credit transfer) uses predefined text fields. The most important for remittance data:

:70: Remittance Information
Up to 140 characters. Used for invoice numbers, reference codes, and payment purpose.
INV-2024-001 / PO-4455 / Services rendered Q4
:72: Sender to Receiver Info
Up to 106 characters per line, max 6 lines. Used for regulatory data, tax references, HSN codes.
/ACCOUNT/DE89370400440532013000
/HBRC/DEUTDEFF
/SUPP/Supplier Invoice #12345

Limited: 140 chars total in field :70: is insufficient for rich structured data (invoice PDFs, multiple line items, tax breakdowns).

Current Standard ISO 20022 pacs.008 — Structured XML

ISO 20022 is the new global standard replacing SWIFT MT messages. Under the SWIFT CBPR+ programme, MT messages were retired for cross-border payments in November 2025. The pacs.008.001.08 message (Customer Credit Transfer) supports rich structured data with dedicated fields:

<Document>
  <DocumentType>pacs.008.001.08</DocumentType>
  <FIToFICstmrCdtTrf>
    <GrpHdr>
      <MsgId>BANK20241215001</MsgId>
      <NbOfTxs>1</NbOfTxs>
      <InstgAgt>...</InstgAgt>
    </GrpHdr>
    <CdtTrfTxInf>
      <PmtId>
        <InstrId>PO-2024-0415</InstrId>
        <EndToEndId>INV-2024-001</EndToEndId>
      </PmtId>
      <Amt>
        <InstdAmt Ccy="USD">1000.00</InstdAmt>
      </Amt>
      <XpctdExecDt>2024-12-15</XpctdExecDt>
      <RmtInf>
        <Strd>
          <CdtrRefInf>
            <Tp>
              <CdOrPrtry>
                <Cd>INVX</Cd>
              </CdOrPrtry>
            </Tp>
            <Ref>INV-2024-001</Ref>
          </CdtrRefInf>
          <AddtlRmtInf>
            Goods shipped 2024-12-01. HSN Code: 8517.70. 
            Country of origin: China. Tax ID: DE123456789.
            PO Reference: PO-2024-0415. Net USD 1000.00.
          </AddtlRmtInf>
        </Strd>
      </RmtInf>
      <DbtrAgt>...</DbtrAgt>
      <Dbtr>
        <Nm>Alice Corp Inc</Nm>
        <PstlAdr>...</PstlAdr>
      </Dbtr>
      <CdtrAgt>...</CdtrAgt>
      <Cdtr>
        <Nm>Bob GmbH</Nm>
        <PstlAdr>...</PstlAdr>
      </Cdtr>
    </CdtTrfTxInf>
  </FIToFICstmrCdtTrf>
</Document>

Advantage: Structured fields for invoice refs, HSN codes, country of origin, tax IDs, multiple line items. No character limits on structured data. This is what the Middleware Service receives from SAP/Oracle and what it maps to the on-chain transaction.

3. Blockchain Pattern: Hash Commitment + Off-Chain Storage

The industry standard across Ripple, Stellar, SWIFT GPI, and permissioned blockchains is: store a hash on-chain, store the full structured data off-chain. This pattern provides:

🔒
Integrity
Anyone can verify data hasn't been tampered with by comparing hashes
💾
Richness
Full ISO 20022 XML/JSON stored off-chain with no size limitations
💰
Cost
On-chain storage is expensive (~$0.01-0.10 per KB). Hash is only 32 bytes.
Why No Encryption?

For B2B cross-border payments, both sender and recipient are authorized parties. The payment data is not confidential between them — both need full visibility. The hash on-chain provides integrity verification (nobody can modify the data after submission), while the off-chain storage provides richness (full structured data).

Storage options: IPFS (decentralized), AWS S3 (centralized), or the bank's own database via the Middleware Service. The dataCID (Content Identifier) or URL points to where the full data lives.

Recommended CBPR.sol Changes

Only the hash needs to be on-chain. The full data is delivered through normal banking channels (SWIFT ISO 20022). The hash serves as a tamper-proof receipt:

// === NEW: Data hash field (add to Payment struct) ===
struct Payment {
    bytes32 txId;
    address sender;
    address recipient;
    uint256 amount;
    uint256 timestamp;
    uint256 corridorId;
    PaymentStatus status;
    // --- NEW FIELD ---
    bytes32 dataHash;        // keccak256 of ISO 20022 XML payload
}

// === NEW: Event for hash storage ===
event PaymentDataHashStored(
    bytes32 indexed txId,
    bytes32 dataHash,
    uint256 timestamp
);

// === NEW: Function to store the data hash ===
// Uses Gnosis Safe multi-sig (3-of-5) for governance
function storePaymentDataHash(
    bytes32 txId,
    bytes32 _dataHash
) external only(GnosisSafe(address(0x1))) {
    require(payments[txId].status == PaymentStatus.PENDING, "INVALID_TX");
    payments[txId].dataHash = _dataHash;
    emit PaymentDataHashStored(txId, _dataHash, block.timestamp);
}

// === Settlement requires multi-sig ===
function confirmSettlement(bytes32 txId, PaymentStatus status) external {
    require(GnosisSafe(safeAddress).getThreshold() >= 3, "NEED_3_OF_5");
    require(payments[txId].status == PaymentStatus.PENDING, "INVALID_TX");
    payments[txId].status = status;
    emit SettlementConfirmed(txId, status);
}

Why only a hash? The full ISO 20022 XML is delivered via SWIFT (the normal banking channel). Storing the hash on-chain creates a tamper-proof receipt that Bob's bank can use to verify the SWIFT message hasn't been altered. No IPFS, no S3, no off-chain storage needed on-chain.

4 — Two Parallel Channels for One Payment

This is the architecture you need to understand. One payment uses two completely separate channels simultaneously. They share the same txId as the link between them.

Channel 1: SWIFT (Banking)
Carries: Full ISO 20022 XML (invoice, line items, tax, remittance)
How Bob's bank is addressed: BIC code (retrieved from IdentityRegistry by Bob's wallet address)
Delivery: SWIFT pacs.008 message sent to Bob's bank BIC
How Bob gets data: His bank receives SWIFT message → parses into core system
Alice looks up Bob's BIC via wallet address
↓ reads from IdentityRegistry ↓
BIC: DEUTDEFF (Deutsche Bank)
↓ sends SWIFT to ↓
Bob's bank at DEUTDEFF
Channel 2: Blockchain (Escrow + Verification)
Carries: USDC escrow + data hash (32 bytes only)
How Bob is addressed: On-chain wallet address (e.g., 0x9ABC...DEF0)
Delivery: USDC locked in CBPR contract → released on settlement
How Bob verifies: His bank reads hash from contract → compares to SWIFT XML hash
Alice's wallet → CBPR contract (escrow)
keccak256(SWIFT_XML) → CBPR contract (hash)
Bob's wallet ← CBPR contract (USDC release)
Bob's bank reads hash from contract (verification)
How Do We Get Bob's BIC?

BIC is stored in the IdentityRegistry during KYC registration. When Bob's bank registers him, they store his BIC alongside his wallet address. Alice's frontend looks it up before initiating the payment.

IdentityRegistry stores BIC on-chain
// IdentityRegistry.sol — BIC stored during KYC
struct Identity {
    bool isVerified;
    string accreditationLevel;
    string jurisdiction;
    string bic;          // Bob's SWIFT/BIC code
    uint256 lastUpdated;
}

// When Bob registers, his bank calls:
// verifyIdentity(Bob, "Accredited", "EU", "DEUTDEFF")

// Alice's frontend looks up Bob's BIC by wallet address:
const identity = await identityRegistry.getIdentity(BobWallet);
const bic = identity.bic;  // "DEUTDEFF"
// Then includes BIC in the SWIFT message as the routing address
The Link: txId

The txId is the same identifier in both channels. It's what connects the SWIFT message to the on-chain escrow.

SWIFT Message (pacs.008)
<PmtId>
  <EndToEndId>{txId}</EndToEndId>
</PmtId>
On-Chain Transaction
txId = keccak256(
  sender, recipient,
  amount, timestamp,
  corridorId
)
dataHash = keccak256(SWIFT_XML)
Complete Flow — Both Channels Working Together
Alice enters Bob's wallet address (blockchain) and the USDC amount. Frontend looks up Bob's BIC from IdentityRegistry. The frontend generates a txId.
Channel 1 (SWIFT): Alice's bank parses IBAN → extracts BIC → sends ISO 20022 XML to Bob's bank BIC. The txId is included in the SWIFT message's EndToEndId field.
Channel 2 (Blockchain): Alice approves USDC → CBPR contract locks funds in escrow. The txId is the keccak256 hash of the payment parameters.
Hash storage: Alice's bank computes keccak256(SWIFT_XML) and stores it on-chain via storePaymentDataHash(txId, hash).
Bob's bank receives the SWIFT message (Channel 1). They parse it into their core system. This is normal banking — unchanged.
Bob's bank verifies: They compute keccak256(received_SWIFT_XML) and read the hash from the CBPR contract using the txId from the SWIFT message. If they match, the data is authentic.
Settlement: 3-of-5 multi-sig releases USDC from escrow to Bob's wallet address (Channel 2). Bob's bank credits Bob's account (Channel 1).
Off-ramp: USDC arrives at custodial redemption wallet (Circle/Coinbase Prime).
Fiat credit: Circle/custodian redeems USDC → EUR fiat → SEPA credit to Bob's IBAN at Deutsche Bank.

Key insight: The blockchain wallet address and the SWIFT banking address are completely separate. Alice enters Bob's wallet address (blockchain) and the USDC amount. Her bank sends SWIFT to Bob's bank BIC (retrieved from IdentityRegistry). The blockchain is just an escrow + hash verification layer. The txId links both channels together. This is the same pattern used by SWIFT GPI and Ripple — parallel channels, not replacement.

Travel Rule Compliance

The FATF Travel Rule (Recommendation 16) requires banks to exchange sender/recipient information for cross-border payments. This is not optional — it's a legal requirement enforced by regulators worldwide. Banks that don't comply face massive fines (e.g., Danske Bank paid €500M+ for AML failures).

Jurisdiction Thresholds
Jurisdiction Threshold Regulation Effective
European Union All amounts (€0+) EU TFR 2023/1113 December 2024
United States — Bank Wires $3,000+ FinCEN Bank Secrecy Act Ongoing
United States — VASPs $1,000+ FinCEN VASP Travel Rule Ongoing
FATF Recommendation 16 $1,000+ (guidance) Non-binding baseline Varies
Travel Rule Data Requirements
Sender Information (must include):
  • • Full legal name
  • • Account number (IBAN/wallet address)
  • • Physical address or national ID
  • • Legal Entity Identifier (LEI) for corporates
Recipient Information (must include):
  • • Full legal name
  • • Account number (IBAN/wallet address)
  • • Physical address or national ID
  • • LEI for corporates (if applicable)
How We Handle Travel Rule on-Tokenize
1. Travel rule data is stored in IdentityRegistry during KYC (name, address, LEI, BIC)
2. When initiating a cross-border payment, the Middleware Service retrieves both sender and recipient travel rule data from the registry
3. This data is included in the SWIFT pacs.008 message fields :50k: (Sender Info) and :59: (Recipient Info)
4. A hash of the Travel Rule data is stored on-chain alongside the payment hash for auditability
SEPA Instant Credit Transfer

For EU domestic payments, banks use SEPA Instant (not SWIFT). SEPA Instant provides 24/7 settlement in under 10 seconds, with a €100,000 limit per transaction. It's the standard for EU domestic payments and is required by EU regulation.

24/7 Availability
Weekends, holidays, any time
<10 Seconds
Target settlement time
€100,000
Per transaction limit

Tokenize uses SEPA Instant for EU→EU payments (fast, cheap). For cross-border (e.g., US↔EU), we use SWIFT pacs.008 with blockchain hash verification.

EURC — Euro-Pegged Stablecoin (EU-Leg Instrument)

Eliminates FX risk for EUR-denominated cross-border payments. Circle's EURC (Euro-pegged USDC) allows USDC holders to receive EURC directly on-chain, eliminating the need for FX conversion. The recipient's bank can then redeem EURC for EUR via SEPA Instant.

Without EURC
USDC → (FX swap) → EUR → SEPA → Bob's IBAN
FX risk + conversion fees
With EURC
USDC → EURC (1:1 peg) → SEPA → Bob's IBAN
No FX risk — EURC is 1:1 EUR backed
Payment-versus-Payment (PvP) Atomic FX Swap

True cross-border banking requires fiat-to-fiat conversion — not USDC-to-USDC. Bob's European supplier wants EUR in their bank account, not USDC. The PvP atomic swap ensures Alice's USDC is atomically swapped for a Euro stablecoin (e.g., EURC) via an institutional AMM before settling into Bob's wallet.

PvP Flow — Step by Step
Alice initiates: Sends 1,000 USDC to CBPR contract + specifies Bob's EUR amount target
AMM Lock: Institutional AMM (e.g., Uniswap V4 pools, or RFQ from institutional liquidity providers like Circle Institutional, Kraken Institutional) locks 1,000 USDC + commits EURC at pre-negotiated rate
Atomic Swap: ERC-7382 or custom atomic swap contract executes USDC → EURC exchange at locked rate. If swap fails, USDC is returned to Alice (no partial fills).
PvP Settlement: EURC is transferred to Bob's wallet. Simultaneously, SWIFT pacs.008 XML (now with EUR amount) is sent to Bob's bank. 3-of-5 multi-sig releases the swap.
Off-ramp: Bob's custodian redeems EURC → EUR fiat → SEPA Instant to Bob's IBAN
Without PvP
Bob receives USDC → Bob must independently find FX → Bob converts at unfavorable rate → Bob sends EUR via SEPA
3 separate steps, 3x fees, settlement risk
With PvP
Atomic swap in one transaction: USDC→EURC locked rate → EURC to Bob → SEPA to IBAN
Single atomic step, no counterparty risk, institutional rate

Why this matters for banks: Traditional correspondent banking uses Nostro/Vostro accounts in multiple currencies. PvP eliminates the need for pre-funded Nostro accounts — the FX and settlement happen atomically, freeing up billions in trapped capital (the "pre-financing" problem that SWIFT estimates costs banks $100B+ annually).

Failed Payment / Exception Handling

What happens if compliance check fails mid-flight? The CBPR contract includes a reversal path: if settlement is not confirmed within the challenge period (e.g., 24 hours), the Settlement Manager (or multi-sig) can call cancelPayment(txId) to reverse the escrow and return USDC to the sender.

Compliance fail: Transaction reverts immediately — no funds moved
Settlement challenge: Authorized parties can challenge settlement within the timelock window
Reversal: Settlement Manager (multi-sig) cancels payment → USDC returned to sender
Notification: Middleware Service notifies both banks of the reversal via webhook

5. Example: Full ISO 20022 JSON Payload

The Middleware Service converts the ISO 20022 XML into JSON for off-chain storage. The JSON hash is stored on-chain. Here's what the full payload looks like:

{
  "document": "pacs.008.001.08",
  "messageId": "BANK20241215001",
  "instructionId": "PO-2024-0415",
  "endToEndId": "INV-2024-001",
  "amount": {
    "value": 1000.00,
    "currency": "USD"
  },
  "expectedExecutionDate": "2024-12-15",
  "debtor": {
    "name": "Alice Corp Inc",
    "address": {
      "street": "123 Wall Street",
      "city": "New York",
      "state": "NY",
      "postalCode": "10005",
      "country": "US"
    },
    "account": {
      "routingNumber": "021000021",
      "accountNumber": "123456789",
      "bank": "JPMorgan Chase"
    }
  },
  "creditor": {
    "name": "Bob GmbH",
    "address": {
      "street": "456 Friedrichstrasse",
      "city": "Berlin",
      "postalCode": "10117",
      "country": "DE"
    },
    "account": {
      "iban": "DE89370400440532013000",
      "bank": "Deutsche Bank AG"
    }
  },
  "remittance": {
    "invoiceNumber": "INV-2024-001",
    "purchaseOrder": "PO-2024-0415",
    "purposeCode": "CBFF"
  },
  "lineItems": [
    {
      "description": "Electronic Components - IC Chips",
      "quantity": 100,
      "unitPrice": 8.50,
      "total": 850.00,
      "hsnCode": "8542.31",
      "countryOfOrigin": "CN"
    },
    {
      "description": "Shipping & Handling",
      "quantity": 1,
      "unitPrice": 150.00,
      "total": 150.00,
      "hsnCode": "9999.99",
      "countryOfOrigin": "US"
    }
  ],
  "tax": {
    "type": "VAT",
    "rate": 0.19,
    "amount": 190.00,
    "taxId": "DE123456789"
  },
  "regulatory": {
    "purposeOfPayment": "Payment for imported goods Q4 2024",
    "currencyOfAccount": "USD",
    "currencyOfSettlement": "USD",
    "localInstrument": "INST",
    "categoryPurpose": "TRAD"
  }
}

Hash computation: The Middleware Service computes keccak256(canonical_ISO_20022_XML) to get the 32-byte hash, then calls storePaymentDataHash(txId, hash) on the CBPR contract. Bob's bank receives the XML via SWIFT, normalizes it to the canonical form (same serialization spec), computes the same hash, and compares it to the on-chain value to verify integrity.

Why canonical XML (not JSON)? Bob's bank receives ISO 20022 XML via SWIFT — not JSON. Both parties must hash the same byte sequence. The canonical form is defined by the ISO 20022 XML Canonicalization (xcan) spec: deterministic whitespace, sorted attributes, UTF-8 encoding. The JSON payload shown above is for developer readability only — in production, the hash is computed over the canonical XML string that Bob's bank also receives.

Production Readiness — Cross-Border Payments

✅ Implemented
  • • CBPR contract with escrow + multi-sig release
  • • IdentityRegistry with KYC/gating
  • • CorridorRegistry with validation
  • • ISO 20022 pacs.008 data model
  • • Hash commitment (canonical ISO 20022 XML)
  • • Two-channel architecture (SWIFT + blockchain)
⚠️ Required for Go-Live
  • • CASP authorization under MiCA
  • • Oracle SLA (chainlink/feed provider)
  • • Reserve custodian agreement (Circle/Fireblocks)
  • • TARGET2/STEP2 settlement integration
  • • Audit report (smart contract + compliance)
  • • NBB regulatory sandbox approval

6. Complete Data Flow — Rich Data in Production

1
SAP/Oracle generates ISO 20022 XML

The bank's ERP system creates the full payment instruction with all structured data (invoice refs, line items, tax, regulatory fields).

2
Middleware Service receives XML via REST API

SAP calls POST /api/payments/initiate with the full ISO 20022 payload. Middleware validates format and maps to on-chain parameters.

3
Middleware computes hash of ISO 20022 XML

Middleware computes keccak256(ISO_20022_XML) to get the data hash. The full XML will be delivered via SWIFT (normal banking channel). The hash is what gets stored on-chain.

4
Middleware calls CBPR.initiatePayment() on-chain

Transaction submitted with sender, recipient, amount, corridor. Compliance and corridor validation happen in the contract.

5
Middleware calls CBPR.storePaymentDataHash() on-chain

Stores the pre-computed hash on-chain. PaymentDataHashStored event emitted. This is the tamper-proof receipt.

6
Alice's bank sends ISO 20022 XML via SWIFT to Bob's bank

The full payment message (with all invoice details, line items, tax data) is delivered through the SWIFT network in standard pacs.008 format. This is how banks have always exchanged payment data.

7
Bob's bank verifies the hash

Bob's bank computes keccak256(received_SWIFT_XML) and compares it to the hash on the CBPR contract. If they match, the data is authentic — Alice's bank sent exactly what Bob received.

Transaction Privacy & Permissioned Scaling

In Plain English

Right now, everything on the blockchain is public — anyone can see how much money you have and where it goes. Banks can't work with that. So for production, banks use two tricks: (1) "Zero-Knowledge Proofs" — like proving you're over 18 without showing your ID, or (2) a "private blockchain" — like a club-only version of blockchain where only invited members can see the transactions. Both keep your financial data private while still letting regulators verify everything.

Public blockchains like Sepolia are unsuitable for production banking because transaction graphs, wallet balances, and asset movements are completely public. Banks require either cryptographic privacy or network-level privacy.

Zero-Knowledge Proof (ZKP) Layer

ZKPs allow the platform to prove compliance without revealing transaction details. A ZK-Rollup batches transactions off-chain and posts only a cryptographic proof on-chain — proving that all transactions were valid without exposing amounts, counterparties, or purposes.

What's Proven (On-Chain)
  • • "This payment is from a verified sender" (ZK-SNARK proof)
  • • "This payment is below the daily limit" (amount ≤ threshold)
  • • "The sender is not on any sanctions list" (ZK-set membership)
  • • "Total daily volume is within regulatory limits" (aggregated proof)
What's Hidden (Off-Chain)
  • • Exact transaction amounts
  • • Counterparty identities (wallet addresses)
  • • Payment purposes / invoice details
  • • Full transaction history of any single user
Regulator Access

Regulators receive a "view key" that allows them to decrypt specific transactions for audit purposes — similar to how tax authorities can access bank records with proper authorization. The proof mechanism itself remains verifiable by anyone.

Technology Stack

Prover: circom / Semaphore / aztec-connect for ZK-SNARK generation
Rollup: zkSync / Scroll / Polygon zkEVM for transaction batching
Verifier: On-chain verifier contract on Ethereum mainnet (L1)

Permissioned L2 Subnets

Instead of a public chain, banks use permissioned EVM-compatible networks where every participant is KYC'd. Only authorized nodes can read/write transactions. This is how real banking consortiums operate — think of it as a "private Ethereum" where only invited banks participate.

Hyperledger Besu (Permissioned Ethereum)

Enterprise Ethereum variant used by JPMorgan (Onyx), Goldman Sachs, and ING. Features: private transactions via Tessera, RBAC, validator membership management.

Quorum (Loom Network)

Enterprise Ethereum with implicit state privacy. Used by central banks for CBDC pilots. Features: block encryption, private contracts, restricted block producers.

Polygon CDK / Avalanche Evergreen

Customizable L2 chains with configurable privacy. Banks can deploy their own chain with custom consensus, privacy rules, and validator sets.

Key Difference from Public Chains
Validator set: Only KYC'd institutions can run nodes (not anonymous miners)
Privacy: Transactions are encrypted — only sender, recipient, and auditor can read them
Consensus: PBFT / Raft (faster than PoW/PoS, final in seconds)
Compliance: Smart contracts can enforce transfer restrictions at the protocol level

Production Recommendation: Hybrid Approach

For a production banking platform, the recommended approach combines both:

Option A: Permissioned L2 (Most Common for Banks)

Deploy on Hyperledger Besu or Quorum. All participants are pre-approved. Transactions are private by default. Best for intra-bank operations and known correspondent banking relationships. Adopted by: JPMorgan Onyx, Project Guardian (HKMA), Project Agorá (BNP Paribas).

Option B: ZK-Rollup on Public L1 (Most Innovative)

Batch transactions on a ZK-Rollup, post proofs to Ethereum mainnet. Balances are private, compliance proofs are public. Best for cross-border payments where counterparties may not trust each other but need regulatory assurance. Adopted by: Central Bank Digital Currency (CBDC) pilots, institutional DeFi protocols.

Cross-Chain Interoperability

In Plain English

Different banks use different blockchains — some use Ethereum, some use private networks. Chainlink CCIP is like a universal translator that lets these different blockchains talk to each other safely. It's like how international wire transfers work: your bank (on one system) sends money through a network that connects to your friend's bank (on a different system), and everything arrives correctly.

Banks will not agree on a single blockchain. The future is multi-chain — tokenized deposits on one chain, mutual funds on another, and cross-border payments bridging them. Chainlink CCIP provides secure, verifiable cross-chain messaging.

How CCIP Enables Multi-Chain Banking

1
Deposit Chain

Customer deposits EUR on Ethereum L2 (e.g., Base). EUR tokens minted via MintEscrow.

2
CCIP Bridge

Chainlink CCIP locks EUR tokens on L2 and mints wrapped representation on bank's permissioned chain.

3
Fund Chain

Wrapped tokens used to purchase TMMF/TMF on the bank's consortium chain (Hyperledger Besu).

4
Redemption

Reverse flow: redeem fund shares → burn wrapped tokens → CCIP unlocks EUR tokens on L2 → customer redeems EUR.

Why CCIP over Traditional Bridges?
❌ Traditional Lock-Mint Bridges (Risky)
  • • Single smart contract holds all locked tokens (honeypot)
  • • No verification of cross-chain state
  • • $2B+ lost to bridge hacks in 2022-2024
  • • No cryptographic proof of cross-chain events
✅ Chainlink CCIP (Secure)
  • • Decentralized oracle network verifies cross-chain messages
  • • On-chain verification via CCIP Receiver contracts
  • • Interop standard (ERC-7164) for cross-chain apps
  • • Used by Aave, Chainlink, SWIFT (trial) for cross-chain

Granular Freeze Manager — Notabene-Style Amount-Level Freezing

In Plain English

When a bank receives USDC that turns out to be non-compliant (e.g., from a sanctioned address), they can't just freeze the entire wallet — that would trap all legitimate funds too. Instead, only the bad amount gets frozen, like putting a lien on a specific portion of a bank account. The rest stays usable. This is how Notabene handles it in production.

Traditional token contracts only support binary freeze states: a wallet is either active or frozen. For banking, this is insufficient. A bank needs to freeze specific amounts from specific sources while keeping the rest of the balance liquid — this is the granular freeze pattern.

Binary Freeze (Insufficient)

State: Wallet Frozen = true

All outgoing transfers blocked. All incoming transfers blocked. Entire balance locked. No distinction between compliant and non-compliant funds.

Problem

Bank receives €50,000 compliant USDC + €500 non-compliant USDC from sanctioned address. Freezing the wallet locks €50,500 — the bank's own legitimate €50,000 is trapped.

Real-World Impact

Bank cannot process payroll, cannot settle trades, cannot serve customers. Regulatory compliance destroys business continuity. This is why binary freeze is unacceptable for banking.

Granular Freeze (Banking-Grade)

State: Balance = €50,500 | Frozen = €500 | Spendable = €50,000

Only €500 from the sanctioned source is frozen. The bank's own €50,000 remains fully liquid. Each frozen amount is tracked with source, reason, and timestamp.

How It Works

On receiving non-compliant USDC, the FreezeManager creates a freezeEntry: {amount: 500, source: 0xSanctioned, reason: "AML_flag", timestamp: 1700000000}. The token's _updateAllowance is overridden to deduct frozen amounts from spendable balance.

Resolution

When compliance team approves: unfreeze(amount, entryId). Or when funds are confiscated: confiscate(entryId, toAddress). Balance is returned to spendable or transferred to regulatory authority.

Granular Freeze Manager Architecture

Data Structures
// Freeze entry — tracks individual frozen amounts
struct FreezeEntry {
    uint256 amount;           // Amount frozen (in smallest unit)
    address source;            // Source address of frozen funds
    FreezeReason reason;       // AML_flag, court_order, regulatory_hold
    uint256 timestamp;         // When freeze was applied
    bytes32 referenceId;       // External reference (court order #, etc.)
    bool active;               // Can be deactivated
}

enum FreezeReason {
    AML_flag,           // Automated AML alert
    court_order,        // Legal order to freeze
    regulatory_hold,    // Regulatory investigation hold
    sanctions_match,    // OFAC/EU sanctions list match
    disputed_transaction // Payment dispute under investigation
}
Key Functions
// Apply granular freeze — called by compliance admin
function freeze(address target, uint256 amount, FreezeReason reason, bytes32 refId) external onlyFreezeAdmin;

// Release frozen amount back to spendable balance
function unfreeze(address target, uint256 entryId) external onlyFreezeAdmin;

// Confiscate frozen funds to regulatory address
function confiscate(uint256 entryId, address to) external onlyFreezeAdmin;

// Override transfer to deduct frozen amounts
function _updateAllowance(address owner, address spender, uint256 newAllowance, int256 delta) internal override {
    uint256 frozen = totalFrozen(target);
    // Spendable = balance - frozen
    uint256 spendable = IERC20(target).balanceOf(target) - frozen;
    require(spendable >= delta, "Insufficient spendable balance");
    super._updateAllowance(owner, spender, newAllowance, delta);
}

// Query spendable balance (what matters for banking)
function spendableBalance(address target) external view returns (uint256) {
    return IERC20(target).balanceOf(target) - totalFrozen(target);
}

Integration with Permissioned Token

// PermissionedToken inherits from ERC-3643 + FreezeManager
contract PermissionedToken is ERC3643, FreezeManager {
    // Transfers only allowed between verified identities
    // AND with sufficient spendable (non-frozen) balance
    
    function _update(address from, address to, uint256 amount) internal override {
        // 1. Check ERC-3643 identity verification
        require(isVerified[from], "Sender not verified");
        require(isVerified[to], "Recipient not verified");
        
        // 2. Check granular freeze — spendable balance
        require(spendableBalance(from) >= amount, "Insufficient spendable balance");
        
        // 3. Check compliance (sanctions, whitelist)
        require(complianceCheck(from, to, amount), "Compliance check failed");
        
        super._update(from, to, amount);
    }
}
ERC-3643
Identity verification layer
Granular Freeze
Amount-level freeze controls
Compliance Check
Sanctions + whitelist validation

Custodian Wallet Management Architecture

In Plain English

Banks don't hold crypto keys themselves — they use professional custodians (like BNY Mellon, Copper, or Fireblocks) who manage keys using advanced security (MPC, HSM). Think of it like a safe deposit box: the custodian has the keys, but multiple people need to agree to open it, and every action is recorded. For the Tokenize platform, the custodian holds the actual USDC/EUR, while smart contracts control when and how funds move.

In a banking-grade tokenization platform, wallet management is separated into distinct layers: custody (holding keys), execution (signing transactions), and governance (approving actions). This separation ensures no single point of failure and provides proper audit trails.

Three-Layer Custody Architecture

Layer 1 — Key Generation & Storage (HSM/MPC)
Hardware Security Module (HSM)
  • • FIPS 140-2 Level 3 certified hardware
  • • Private keys never leave the HSM boundary
  • • Examples: Thales Luna, AWS CloudHSM, Azure Key Vault
  • • Used by: BNY Mellon, State Street Custodial
Multi-Party Computation (MPC)
  • • Private key split into shards across multiple parties
  • • No single party ever holds the full key
  • • Signatures generated collaboratively without key reconstruction
  • • Examples: Fireblocks, Copper, Anchorage
Layer 2 — Transaction Execution (Custodian API)
// Custodian flow:
// 1. Smart contract emits TransferApproved event
// 2. Custodian API receives webhook
// 3. Custodian validates against internal policy engine
// 4. If approved, custodian signs transaction with HSM/MPC
// 5. Signed TX broadcast to network

// Middleware polls custodian API for execution status
const status = await custodianAPI.getTransaction(txHash);
if (status.executed) {
    // Update on-chain state
    await cbprContract.confirmExecution(txHash);
}
Layer 3 — Governance & Approval (Multi-Sig)
Treasury Multi-Sig

Gnosis Safe (5-of-7) for large transfers. Required for amounts above threshold. Signers: CFO, CTO, Compliance Officer, Risk Manager, + 2 external.

Policy Engine

Automated rules: daily transfer limits, whitelist-only recipients, time-locks for large transfers, geographic restrictions, counterparty limits.

Audit Trail

Every action logged: who approved, when, why, amount, recipient. Immutable log stored both on-chain (event) and off-chain (SIEM system).

Traditional Custodian

Providers: BNY Mellon, State Street, Simonds
Model: Full custodial service with insurance
Best for: Large banks with existing relationships
Integration: REST API + SWIFT messaging
Cost: $50K-200K/year + per-transaction fees
Settlement: T+1 via traditional rails

Cloud Custodian (Recommended MVP)

Providers: Fireblocks, Copper, Ironwood
Model: MPC-based, API-first, developer-friendly
Best for: MVP and mid-size deployments
Integration: REST API + webhooks + SDK
Cost: $10K-50K/year + per-transaction
Settlement: Near-instant on-chain

Self-Custody (Advanced)

Model: Bank manages own HSM + multi-sig
Best for: Large banks with crypto infrastructure
Tools: Gnosis Safe + AWS CloudHSM
Integration: Direct RPC + HSM signing
Cost: High capex, lower opex long-term
Settlement: Direct on-chain

Custodian ↔ Smart Contract Interaction Flow

┌─────────────┐    ┌──────────────┐    ┌───────────────┐    ┌─────────────┐
│  Bank App   │    │  Middleware  │    │  Custodian    │    │  Blockchain │
│  (UI)       │    │  (Node.js)   │    │  API          │    │  (EVM)      │
└──────┬──────┘    └──────┬───────┘    └───────┬───────┘    └──────┬──────┘
       │                   │                     │                    │
       │  1. Initiate      │                     │                    │
       │     transfer      │                     │                    │
       │──────────────────>│                     │                    │
       │                   │  2. Validate        │                    │
       │                   │     policy          │                    │
       │                   │────────────────────>│                    │
       │                   │                     │  3. Check policy   │
       │                   │                     │     + AML screening│
       │                   │                     │<───────────────────│
       │                   │                     │     (read-only)    │
       │                   │  4. Policy OK       │                    │
       │                   │<────────────────────│                    │
       │                   │  5. Request sign    │                    │
       │                   │────────────────────>│                    │
       │                   │                     │  6. Sign with HSM  │
       │                   │                     │     (key never     │
       │                   │                     │      leaves HSM)   │
       │                   │                     │  7. Return sig     │
       │                   │<────────────────────│                    │
       │                   │  8. Build + send TX │                    │
       │                   │────────────────────────────────────────>│
       │                   │                     │                    │  9. Transfer
       │                   │                     │                    │     on-chain
       │                   │                     │                    │  10. Emit
       │                   │                     │                    │     event
       │                   │  11. Poll for       │                    │
       │                   │     confirmation    │                    │
       │                   │<────────────────────────────────────────│
       │                   │  12. Update status  │                    │
       │<──────────────────│                     │                    │
       │  13. Show done   │                     │                    │

Mint/Escrow Contract — Compliance-Gated Token Minting

In Plain English

Tokens can only be minted when a bank confirms that real euros are deposited in a segregated account. The Mint/Escrow contract acts as a gatekeeper: it verifies that (1) the depositor is KYC'd, (2) the deposit is confirmed by the bank's custodian, and (3) the amount matches. Only then does it allow new tokens to be created. This 1:1 backstopping is what makes tokenization trustworthy.

The mint/escrow pattern ensures that token supply always corresponds to actual fiat deposits. This is the core mechanism that makes tokenized deposits and tokenized funds truly backed — not speculative or unbacked. The escrow also handles the reverse: burning tokens to release fiat.

Mint Flow (Fiat → Token)

1
Depositor wires fiat to segregated account

Customer transfers EUR to the bank's segregated custodial account at a correspondent bank. The bank's core system records the deposit.

2
Custodian confirms deposit via webhook

Bank's middleware receives confirmation that funds are received and settled in the segregated account. Deposit reference is generated.

3
Middleware calls MintEscrow.mint(tokenReceiver, amount, depositRef)

Contract verifies: (a) caller is authorized mint operator, (b) receiver is KYC'd via IdentityRegistry, (c) depositRef matches on-chain record.

4
Token minted 1:1 to receiver

_mint(tokenReceiver, amount) called. Minted event emitted with deposit reference for audit trail.

Burn Flow (Token → Fiat)

1
Holder requests redemption

Customer initiates redemption via bank app, specifying amount and destination bank account (must be verified same owner).

2
Middleware calls MintEscrow.burn(tokenHolder, amount, payoutRef)

Contract burns tokens from holder, verifies they are the token owner, and records the payout reference for fiat disbursement.

3
Bank releases fiat from segregated account

Bank's treasury system receives burn confirmation and initiates SEPA transfer from segregated account to customer's verified bank account.

4
Fiat sent → Burn confirmed on-chain

SEPA confirmation received. Middleware updates burn record on-chain: BurnConfirmed(burnId, payoutRef, timestamp).

MintEscrow Contract Architecture

contract MintEscrow {
    IERC3643 public token;
    IdentityRegistry public identityRegistry;
    address public depositary;  // The entity holding fiat
    
    struct DepositRecord {
        string depositRef;     // Bank's internal reference
        address depositor;     // Who deposited
        uint256 amount;        // Amount in tokens
        uint256 timestamp;     // When recorded
        DepositStatus status;  // Pending, Confirmed, Reversed
    }
    
    enum DepositStatus { Pending, Confirmed, Reversed }
    
    // Mint — only callable by authorized operator after fiat confirmation
    function mint(address receiver, uint256 amount, string calldata depositRef) external onlyMintOperator {
        require(identityRegistry.isVerified(receiver), "Receiver not KYC'd");
        require(amount > 0, "Must mint > 0");
        
        // Record deposit reference for audit
        depositRecords[depositRef] = DepositRecord({
            depositor: receiver,
            amount: amount,
            timestamp: block.timestamp,
            status: DepositStatus.Confirmed
        });
        
        token.mint(receiver, amount);
        emit Minted(receiver, amount, depositRef);
    }
    
    // Burn — tokens destroyed, fiat released
    function burn(address holder, uint256 amount, string calldata payoutRef) external {
        require(holder == msg.sender || token.isApprovedForAll(holder, msg.sender), "Not authorized");
        require(token.balanceOf(holder) >= amount, "Insufficient balance");
        
        token.burn(holder, amount);
        
        burnRecords[payoutRef] = BurnRecord({
            burner: holder,
            amount: amount,
            timestamp: block.timestamp,
            status: BurnStatus.Released
        });
        
        emit Burned(holder, amount, payoutRef);
    }
    
    // Audit: verify total supply matches total deposits
    function verifyBacked() external view returns (bool) {
        return token.totalSupply() == totalFiatDeposited();
    }
}

Depositary & Custodian Separation — The Banking Architecture Pattern

In Plain English

In traditional banking, the entity that holds your money is not the same as the entity that manages your account. Your money sits at a custodian bank (like BNY Mellon or a central bank), while your retail bank manages your account. This separation is required by EU law (UCITS Directive, AIFMD). The same pattern applies to tokenization: the smart contract platform manages the tokens, but the actual fiat sits in segregated accounts at a depositary institution.

Under EU regulations (UCITS Directive 2014/91/EU, AIFMD 2011/61/EU), every tokenized fund MUST have an independent depositary (custodian) that holds the underlying assets separately from the fund manager. This ensures that even if the fund manager goes bankrupt, the underlying assets are protected and belong to token holders.

Four-Party Depositary Architecture

Token Holder

Investor/customer who holds tokenized assets. Has redemption rights.

Rights: View balance, redeem, receive distributions
Fund Manager

Makes investment decisions. Manages the TMMF/TMF portfolio. Operates the smart contract platform.

Role: Investment decisions, smart contract ops
Depositary

Holds underlying assets in segregated accounts. Verifies fund manager actions. Independent from manager.

Role: Asset safekeeping, oversight, reconciliation
External Auditor

Independent verification of NAV, reserves, and compliance. Reports to regulators.

Role: NAV verification, reserve attestation
Key Regulatory Requirement: Segregation of Assets

Under UCITS Art. 22 and AIFMD Art. 21, the depositary must hold all fund assets in segregated accounts that are clearly identified and separate from the depositary's own assets. In the event of the depositary's insolvency, these assets are ring-fenced and do not form part of the depositary's estate. This is why the MintEscrow contract tracks the depositary address — it's a regulatory requirement that must be verifiable on-chain.

Depositary Verification Flow

Daily Reconciliation

Depositary's treasury system compares: (1) bank account balance in segregated account vs. (2) on-chain total supply. Any discrepancy triggers an alert.

On-Chain Proof

Depositary signs a daily attestation: depositaryAttest(totalSupply, fiatBalance, timestamp). Anyone can verify the depositary has confirmed 1:1 backing.

Override Protection

If depositary detects mismatch, they can pause minting via depositaryPause(). Fund manager cannot override depositary pause. This is the nuclear option — used only for serious discrepancies.

Depositary Contract Integration

contract DepositaryInterface {
    // Depositary can pause minting if
    // fiat balance != token supply
    function depositaryPause(bool paused) external;
    function isDepositaryPaused() external view returns (bool);
    
    // Daily attestation
    function attestFiatBacking(
        uint256 onChainSupply,
        uint256 fiatBalance,
        bytes32 rootHash
    ) external returns (bool);
    
    // Get last attestation
    function lastAttestation() external view returns (
        uint256 supply,
        uint256 balance,
        uint256 timestamp
    );
}

// MintEscrow integrates depositary check
function mint(address receiver, uint256 amount, ...) external {
    require(!depositary.isDepositaryPaused(), "Depositary paused");
    require(token.totalSupply() + amount <= 
            depositary.getVerifiedFiatBalance(), 
            "Insufficient fiat backing");
    // ... proceed with mint
}

Circuit Breaker & Emergency Controls

In Plain English

Sometimes things go wrong — a smart contract bug is found, a key is compromised, or there's a market crisis. Circuit breakers are emergency stop mechanisms at different levels: you can pause individual functions, pause specific tokens, or pause the entire system. Think of it like circuit breakers in a power grid — you can trip one breaker for a single room without shutting down the whole building. Banks require multiple levels of emergency controls, each with different authorization requirements.

A well-designed circuit breaker system provides granular emergency controls without requiring a single point of failure. Different types of emergencies require different response levels — a smart contract bug needs a different response than a sanctioned address receiving funds.

Four Levels of Emergency Controls

Level 1: Global Pause (System-Wide)

What it does: Stops ALL state-changing operations across ALL contracts. No transfers, no mints, no burns, no updates.

Who can trigger: 5-of-7 multi-sig (governance council)

When to use: Critical smart contract vulnerability, major exploit in progress

Recovery: 5-of-7 multi-sig to resume. Minimum 24-hour delay for audit.

Level 2: Token-Specific Pause

What it does: Pauses transfers for a specific token (e.g., non-compliant USDC variant)

Who can trigger: Compliance officer + depositary (2-of-3)

When to use: Specific token variant found to have compliance issues

Recovery: Compliance team verifies fix, lifts pause (2-of-3).

Level 3: Address-Specific Freeze

What it does: Freezes specific wallet addresses (granular, amount-level)

Who can trigger: Compliance officer (single, but logged)

When to use: OFAC sanctions match, court order, AML alert

Recovery: Compliance review + depositary approval to unfreeze.

Level 4: Function-Specific Throttle

What it does: Limits rate/amount of specific functions (e.g., max mint per hour)

Who can trigger: Auto-triggered by threshold, adjustable by ops team

When to use: Unusual volume spike, potential flash loan attack

Recovery: Auto-resumes after cooldown, or ops team resets.

CircuitBreaker Contract Architecture

contract CircuitBreaker {
    enum BreakerLevel { NONE, GLOBAL, TOKEN, ADDRESS, FUNCTION }
    
    mapping(bytes32 => BreakerLevel) private _breakers;
    mapping(bytes32 => uint256) private _activatedAt;
    
    // Multi-sig required for global pause
    function triggerGlobalPause() external onlyGovernance {
        _activateBreaker("GLOBAL", BreakerLevel.GLOBAL);
    }
    
    // Compliance can freeze addresses
    function freezeAddress(address target, string calldata reason) external onlyCompliance {
        bytes32 key = keccak256(abi.encodePacked("ADDRESS_", target));
        _activateBreaker(key, BreakerLevel.ADDRESS);
        emit AddressFrozen(target, reason, block.timestamp);
    }
    
    // Modifier for all contracts
    modifier notPaused() {
        require(!_isPaused("GLOBAL"), "Global pause active");
        _;
    }
    
    // Auto-resume function-level throttles
    function _activateBreaker(bytes32 key, BreakerLevel level) internal {
        _breakers[key] = level;
        _activatedAt[key] = block.timestamp;
        
        if (level == BreakerLevel.FUNCTION) {
            // Auto-resume after 1 hour
            _scheduleResume(key, 1 hours);
        }
        
        emit BreakerTriggered(key, level, msg.sender, block.timestamp);
    }
}

Complete Contract Architecture — Dependency Graph & Data Flow

In Plain English

All the smart contracts on the platform form an interconnected system. Some contracts depend on others — like how the payment contract needs to check the identity registry before allowing a transfer. Understanding how these contracts relate to each other is essential for building, maintaining, and auditing the system. This section shows the complete architecture: every contract, every dependency, and every data flow.

Contract Dependency Graph

┌─────────────────────────────────────────────────────────────────────────┐
│                        TOKENIZE PLATFORM ARCHITECTURE                     │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ┌──────────────────┐                                                   │
│  │  FRONTEND (UI)   │  ← React/Next.js dashboard                        │
│  └────────┬─────────┘                                                   │
│           │ RPC (ethers.js/viem)                                        │
│           ▼                                                             │
│  ┌──────────────────┐                                                   │
│  │  MIDDLEWARE      │  ← Node.js service                                │
│  │  (API Gateway)   │                                                   │
│  └──┬──────────┬────┘                                                   │
│     │          │                                                        │
│     ▼          ▼                                                        │
│  ┌────────┐ ┌────────┐                                                 │
│  │ Oracle │ │Custodian│ ← Fireblocks/Copper API                        │
│  │(prices)│ │(keys)  │                                                   │
│  └───┬────┘ └───┬────┘                                                   │
│      │          │                                                        │
│      ▼          ▼                                                        │
│  ┌─────────────────────────────────────────────────────────────────┐     │
│  │                    BLOCKCHAIN LAYER (Smart Contracts)            │     │
│  │                                                                 │ │
│  │  ┌──────────────────┐     ┌──────────────────┐                 │     │
│  │  │ IdentityRegistry │◄────│ CorridorRegistry │                 │     │
│  │  │ (KYC/AML)        │     │ (Routing rules)  │                 │     │
│  │  └────────┬─────────┘     └──────────────────┘                 │     │
│  │           │                                                      │     │
│  │           ▼                                                      │     │
│  │  ┌──────────────────┐     ┌──────────────────┐                 │     │
│  │  │  FreezeManager   │     │  CircuitBreaker  │                 │     │
│  │  │ (Granular freeze)│     │ (Emergency ctrl) │                 │     │
│  │  └────────┬─────────┘     └──────────────────┘                 │     │
│  │           │                                                      │     │
│  │           ▼                                                      │     │
│  │  ┌─────────────────────────────────────────────────────────┐     │     │
│  │  │              Permissioned Token (ERC-3643)               │     │     │
│  │  │  ┌────────────┐  ┌────────────┐  ┌──────────────────┐  │     │     │
│  │  │  │ CBPR       │  │ MintEscrow │  │ TMMF/TMF Vaults  │  │     │     │
│  │  │  │ (Payments) │  │ (Mint/Burn)│  │ (Fund management)│  │     │     │
│  │  │  └────────────┘  └────────────┘  └──────────────────┘  │     │     │
│  │  └─────────────────────────────────────────────────────────┘     │     │
│  │                                                                 │ │
│  │  ┌──────────────────┐     ┌──────────────────┐                 │     │
│  │  │ YieldEngine      │     │ FundNAVTracker   │                 │     │
│  │  │ (Distribution)   │     │ (NAV calculation)│                 │     │
│  │  └──────────────────┘     └──────────────────┘                 │     │
│  │                                                                 │ │
│  └─────────────────────────────────────────────────────────────────┘     │
│                                                                           │
│  All contracts depend on: IdentityRegistry (KYC check)                   │
│  Core token: Permissioned ERC-3643 with FreezeManager mixin             │
│  Governance: Multi-sig (Gnosis Safe) for critical operations             │
│  Depositary: External attestation of fiat backing                        │
└─────────────────────────────────────────────────────────────────────────┘

Contract Data Flow Matrix

Contract Reads From Writes To Triggered By
CBPR IdentityRegistry, CorridorRegistry Events → Middleware Middleware (user action)
PermissionedToken IdentityRegistry, FreezeManager Events → All Any approved caller
MintEscrow IdentityRegistry, PermissionedToken PermissionedToken (mint/burn) Custodian confirmation
TMMF/TMF Oracle (NAV), FundNAVTracker Events → Middleware Oracle update + user actions
YieldEngine TMMF/TMF vaults TMMF/TMF (distribute) Scheduled (oracle update)
FreezeManager Token balances Freeze entries mapping Compliance admin
CircuitBreaker Breaker state Breaker state Governance / auto-threshold

MVP Building Blocks — What to Build First

For a bank team building the MVP, here's the recommended build order. Each block enables the next. Don't build everything at once — start with the core and add complexity incrementally.

Wave 1: Core Identity & Token (Weeks 1-4)
Foundation
IdentityRegistry
PermissionedToken (ERC-3643)
CorridorRegistry

Deliverable: KYC-gated token that can only be transferred between verified parties.

Wave 2: Mint/Burn & Custody (Weeks 5-8)
Backing
MintEscrow
Custodian Integration (Fireblocks API)
Depositary Attestation

Deliverable: 1:1 backed token minting — fiat deposit triggers token mint, token burn releases fiat.

Wave 3: Payments Rail (Weeks 9-12)
Transactions
CBPR Contract
Cross-chain (CCIP)
Middleware Service

Deliverable: Cross-border payment rail with compliance checks, corridor validation, and hash-verified payment data.

Wave 4: Fund Management (Weeks 13-18)
Investment
TMMF Contract
TMF Contract
Oracle + NAV Tracker
YieldEngine

Deliverable: Tokenized money market fund and mutual fund with oracle-driven NAV and yield distribution.

Wave 5: Compliance & Safety (Weeks 19-22)
Production-Ready
FreezeManager
CircuitBreaker
Sanctions Oracle
Audit Trail

Deliverable: Granular freeze, emergency controls, sanctions screening, and complete audit trail. Production-ready compliance layer.

Oracle & Price Feed Architecture

In Plain English

Smart contracts can't see the outside world on their own — they need oracles to bring in price data, NAV calculations, and other off-chain information. But you can't just trust any data source. Banks require multiple layers of verification: multiple price sources, time-weighted averages, and fallback mechanisms. Think of it like a bank's treasury department — they don't trust a single price quote, they get quotes from multiple dealers and use the median.

The oracle architecture is critical for fund valuation (TMMF/TMF NAV), FX conversion rates, and compliance checks. A compromised oracle could manipulate fund NAVs, causing massive financial harm. The architecture must provide tamper-evident, time-stamped price feeds with multiple verification layers.

Three-Layer Oracle Architecture

Layer 1: Data Aggregation (Off-Chain)
Price Sources
  • Chainlink Price Feeds: EUR/USD, USD/EUR, major FX pairs
  • Benchmark Rates: EURIBOR, STR (European Central Bank)
  • Fund NAV Providers: Independent fund administrators
  • Custom Oracles: Bank's internal treasury system
Aggregation Logic
  • Median Calculation: Remove highest/lowest, average rest
  • Time-Weighted: VWAP over configured window (e.g., 5 min)
  • Staleness Check: Reject if price older than threshold
  • Deviation Check: Reject if >5% from previous
Layer 2: Oracle Network (Decentralized)
// Chainlink-compatible oracle request
function requestPriceFeed(
    bytes32 requestId,
    string[] calldata pairs,  // ["EUR/USD", "USD/EUR"]
    uint32 validAfter,        // Minimum timestamp
    uint32 validBefore        // Maximum timestamp
) external onlyAuthorizedRelayer {
    // Oracle nodes fetch from multiple sources
    // Aggregate using median + deviation check
    // Sign result with their private key
    // Submit to on-chain aggregator contract
}

// On-chain: verify oracle signature + check staleness
function submitPrice(
    bytes32 requestId,
    uint256 price,
    uint256 timestamp,
    bytes calldata signature
) external {
    require(!isStale(timestamp, STALENESS_THRESHOLD), "Stale price");
    require(verifyOracleSignature(requestId, price, signature), "Invalid signature");
    prices[requestId] = PriceUpdate(price, timestamp);
}
Layer 3: On-Chain Verification (Smart Contract)
Staleness Guard

Prices older than N minutes are rejected. For NAV updates: max 1 hour. For FX rates: max 5 minutes. Prevents replay of stale prices.

Deviation Guard

Prices deviating >5% from previous are rejected or require multi-sig approval. Prevents oracle manipulation attacks.

Fallback Mode

If oracle is down, circuit breaker pauses fund operations. Depositary can override with manual NAV (requires 2-of-3 approval).

NAV Update Flow — TMMF/TMF

┌─────────────┐    ┌──────────────┐    ┌───────────────┐    ┌─────────────┐
│  Fund Admin │    │  Oracle      │    │  NAV Tracker  │    │  TMMF/TMF   │
│  (Off-chain)│    │  Network     │    │  (On-chain)   │    │  Contract   │
└──────┬──────┘    └──────┬───────┘    └───────┬───────┘    └──────┬──────┘
       │                   │                     │                    │
       │  1. Calculate     │                     │                    │
       │     NAV from      │                     │                    │
       │     underlying    │                     │                    │
       │     assets        │                     │                    │
       │──────────────────>│                     │                    │
       │                   │  2. Oracle nodes    │                    │
       │                   │     fetch + aggregate│                    │
       │                   │<────────────────────│                    │
       │                   │  3. Signed price    │                    │
       │                   │────────────────────>│                    │
       │                   │                     │  4. Verify:        │
       │                   │                     │     - signature    │
       │                   │                     │     - staleness    │
       │                   │                     │     - deviation    │
       │                   │                     │  5. Update NAV     │
       │                   │                     │───────────────────>│
       │                   │                     │                    │  6. Emit
       │                   │                     │                    │     NavUpdated
       │  7. Notify        │                     │                    │
       │     investors     │                     │                    │
       │<──────────────────│                     │                    │

PriceFeed Oracle Contract

contract PriceFeedOracle {
    struct PriceUpdate {
        uint256 price;          // Price in 18 decimal places
        uint256 timestamp;      // When price was fetched
        uint256 roundId;        // Chainlink round ID
        bool valid;             // Is this price still valid?
    }
    
    mapping(bytes32 => PriceUpdate) public prices;
    uint256 public constant STALENESS_THRESHOLD = 3600; // 1 hour
    uint256 public constant DEVIATION_THRESHOLD = 5;    // 5%
    
    // Update price with verification
    function updatePrice(
        bytes32 pair,
        uint256 price,
        uint256 timestamp,
        uint256 roundId
    ) external onlyOracleNode {
        require(timestamp > block.timestamp - STALENESS_THRESHOLD, "Stale");
        
        PriceUpdate storage prev = prices[pair];
        if (prev.valid && prev.price > 0) {
            uint256 deviation = abs(price - prev.price) * 100 / prev.price;
            require(deviation <= DEVIATION_THRESHOLD, "Deviation too high");
        }
        
        prices[pair] = PriceUpdate(price, timestamp, roundId, true);
        emit PriceUpdated(pair, price, timestamp);
    }
    
    // Get current price (reverts if stale)
    function getCurrentPrice(bytes32 pair) external view returns (uint256) {
        PriceUpdate memory p = prices[pair];
        require(p.valid && block.timestamp - p.timestamp < STALENESS_THRESHOLD, "Stale");
        return p.price;
    }
    
    // Fallback: depositary can set manual price in emergency
    function setManualPrice(bytes32 pair, uint256 price) external onlyDepositary {
        prices[pair] = PriceUpdate(price, block.timestamp, 0, true);
        emit ManualPriceSet(pair, price, block.timestamp);
    }
}

Settlement Architecture — Dual-Leg (Fiat + Crypto)

In Plain English

In banking, settlement means the actual movement of money. For Tokenize, there are TWO settlement legs happening: (1) the fiat settlement through traditional banking rails (SEPA, TARGET2) and (2) the crypto settlement on-chain (token transfers). The key is that these happen atomically — either both complete or neither does. This is called "Delivery vs Payment" (DvP) and is required by EU law for securities settlement.

Settlement architecture defines how fiat and crypto legs are coordinated to ensure atomic settlement. The platform must handle both PvP (Payment vs Payment) for FX swaps and DvP (Delivery vs Payment) for security settlements. Each has different timing, finality, and risk profiles.

PvP — Payment vs Payment (FX Swaps)

Use Case

USDC → EURC atomic swap for cross-border payments. Both legs settle on-chain simultaneously.

Mechanism

On-chain atomic swap using either: (a) ERC-8025 atomic transfer protocol, or (b) Chainlink CCIP for cross-chain atomic swaps. Both legs must succeed or both revert.

Finality

On-chain finality: ~12 seconds (Ethereum) or ~400ms (Base L2). No counterparty risk — atomic by design.

DvP — Delivery vs Payment (Security Settlements)

Use Case

Tokenized fund subscription/redemption. Fiat deposit triggers token mint; token burn releases fiat.

Mechanism

Two-phase settlement: (1) Fiat leg via SEPA/TARGET2, (2) Crypto leg via smart contract. Coordinated by middleware with state machine tracking.

Finality

Fiat: T+0 (SEPA Instant) or T+1 (TARGET2). Crypto: immediate. Total: T+0 to T+1 depending on corridor.

Settlement State Machine

enum SettlementState {
    INITIATED,        // 1. Payment initiated
    FIAT_PENDING,     // 2. Fiat leg in progress (SEPA/TARGET2)
    FIAT_CONFIRMED,   // 3. Fiat settled in segregated account
    CRYPTO_PENDING,   // 4. Crypto leg initiated
    CRYPTO_CONFIRMED, // 5. Token minted/transferred
    COMPLETE,         // 6. Both legs settled
    FAILED,           // 7. Settlement failed
    REFUNDED          // 8. Funds returned on failure
}

// Settlement tracking contract
contract SettlementManager {
    struct Settlement {
        string settlementId;
        SettlementState state;
        address initiator;
        uint256 amount;
        string fiatRef;       // SEPA/TARGET2 reference
        string cryptoTxHash;  // On-chain tx hash
        uint256 timestamp;
        string failureReason; // If state == FAILED
    }
    
    // Advance state through the machine
    function advanceState(
        string calldata settlementId,
        SettlementState newState,
        bytes calldata proof  // Fiat confirmation or crypto tx
    ) external onlySettlementOperator {
        Settlement storage s = settlements[settlementId];
        require(canTransition(s.state, newState), "Invalid transition");
        
        s.state = newState;
        if (newState == SettlementState.CRYPTO_PENDING) {
            s.cryptoTxHash = extractTxHash(proof);
        }
        if (newState == SettlementState.FAILED) {
            s.failureReason = string(proof);
        }
        
        emit SettlementStateChanged(settlementId, newState);
    }
    
    // State transition matrix
    function canTransition(SettlementState from, SettlementState to) internal pure returns (bool) {
        // Define valid transitions
        return _validTransitions[from][to];
    }
}

Atomic FX Swap — PvP Implementation

// Atomic FX Swap — ERC-8025 style
contract AtomicFXSwap {
    // Swap USDC → EURC atomically
    function executeFXSwap(
        address usdcToken,
        address eurcToken,
        uint256 usdcAmount,
        uint256 minEurcAmount,  // Slippage protection
        uint256 deadline,
        bytes calldata signature // Counterparty signature
    ) external {
        // 1. Verify counterparty signed the terms
        verifySignature(usdcAmount, minEurcAmount, deadline, signature);
        
        // 2. Transfer USDC from sender (escrow)
        IERC20(usdcToken).transferFrom(msg.sender, address(this), usdcAmount);
        
        // 3. Transfer EURC from counterparty (escrow)
        IERC20(eurcToken).transferFrom(
            recoverCounterparty(signature), 
            msg.sender, 
            eurcAmount
        );
        
        // 4. Release USDC to counterparty
        IERC20(usdcToken).transfer(
            recoverCounterparty(signature), 
            usdcAmount
        );
        
        // All or nothing — if step 3 fails, step 2 is reverted
        emit FXSwapExecuted(msg.sender, usdcAmount, eurcAmount);
    }
}
Atomic
Both legs or neither
Slippage Protection
minEurcAmount prevents adverse price movement
No Counterparty Risk
Escrow ensures both parties fulfill

Security Architecture — Defense in Depth

In Plain English

Banks don't rely on a single security measure — they use multiple layers, like a castle with moats, drawbridges, and guards at every door. For Tokenize, this means: hardware security modules for keys, multi-signature approval for transactions, time-locks for large transfers, continuous monitoring for anomalies, and regular penetration testing. If one layer fails, others catch it.

Security architecture for a banking-grade tokenization platform must address: key management (HSM/MPC), access control (RBAC + multi-sig), transaction validation (policy engine), monitoring (SIEM + anomaly detection), and incident response (playbooks + automated containment).

Five Layers of Security

Layer 1: Physical & Infrastructure Security
HSM: FIPS 140-2 Level 3 (Thales Luna, AWS CloudHSM)
Network: VPC isolation, security groups, WAF, DDoS protection
Compute: Container hardening, patch management, CIS benchmarks
Secrets: HashiCorp Vault / AWS Secrets Manager, rotation every 90 days
Layer 2: Key Management & Cryptography
Wallet Keys: MPC (Fireblocks) or HSM — private key never leaves secure boundary
Contract Admin Keys: Gnosis Safe 5-of-7 multi-sig (separate signers per role)
Oracle Keys: Threshold signature scheme (TSS) — no single point of compromise
Encryption: AES-256-GCM at rest, TLS 1.3 in transit, field-level encryption for PII
Layer 3: Access Control & Authorization
RBAC: Role-based access (admin, compliance, operator, viewer) with least privilege
Multi-Sig: Gnosis Safe for contract changes, treasury operations, emergency controls
Time-Locks: 24-hour delay for critical operations (contract upgrades, pause)
Layer 4: Transaction Validation & Policy Engine
Pre-flight Checks: Amount limits, whitelist validation, sanctions screening before submission
Policy Engine: Rules-based: daily limits, geographic restrictions, counterparty limits
Post-flight Audit: Every transaction logged to SIEM, correlated with compliance events
Layer 5: Monitoring, Detection & Response
SIEM: Splunk / Elastic SIEM — real-time log aggregation and correlation
Anomaly Detection: ML-based detection of unusual transaction patterns, volume spikes
Incident Response: Automated playbooks: freeze, pause, alert, escalate (SOC 24/7)

Security Architecture — Request Flow

┌─────────────────────────────────────────────────────────────────────────┐
│                        SECURITY REQUEST FLOW                             │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  User Request                                                          │
│  └─> API Gateway (WAF + Rate Limit)                                    │
│       └─> Middleware Service                                            │
│            └─> Policy Engine (pre-flight)                               │
│                 ├─ Check amount limits                                  │
│                 ├─ Check whitelist                                      │
│                 ├─ Check sanctions (Chainalysis/elliptic)               │
│                 └─ Check corridor rules                                 │
│            └─> Custodian API (if crypto transfer)                       │
│                 └─> HSM/MPC (signing)                                   │
│            └─> Smart Contract (on-chain)                                │
│                 └─> CircuitBreaker (is system paused?)                  │
│                      └─> FreezeManager (is sender/recipient frozen?)    │
│                           └─> IdentityRegistry (is sender KYC'd?)       │
│                                └─> PermissionedToken (transfer)         │
│                                                                         │
│  Post-Transaction:                                                     │
│  └─> Event Emission → Kafka → SIEM (Splunk/Elastic)                    │
│       └─> Anomaly Detection (ML model)                                 │
│            └─> Alert if anomaly detected → SOC 24/7                    │
└─────────────────────────────────────────────────────────────────────────┘

Data Model Architecture — On-Chain vs Off-Chain

In Plain English

Not all data belongs on the blockchain. Storing data on-chain is expensive and public. The architecture separates data into three categories: (1) on-chain — critical state that needs to be tamper-proof and verifiable (balances, identities, freezes), (2) off-chain structured — operational data that needs to be queryable (payment details, audit logs), and (3) off-chain unstructured — documents and large data (KYC documents, contracts, invoices). Each category has different storage, access, and retention policies.

Data architecture for a tokenization platform must balance: on-chain verifiability vs. cost, privacy vs. transparency, queryability vs. decentralization, and regulatory retention requirements vs. data minimization principles (GDPR).

On-Chain Data

What's Stored
  • • Token balances (ERC-3643)
  • • Identity verification status
  • • Freeze entries (amount, source, reason)
  • • Settlement state machine
  • • Price feed updates
  • • Transaction hashes
Storage

EVM state trie. Cost: ~20k gas per storage write. Indexed by Etherscan.

Off-Chain Structured

What's Stored
  • • Payment details (ISO 20022 XML)
  • • Audit trail events (append-only)
  • • Settlement records with proofs
  • • Oracle price history
  • • Compliance screening results
  • • KYC document hashes (not documents)
Storage

PostgreSQL + Kafka. Encrypted at rest. Accessible by middleware and authorized parties.

Off-Chain Unstructured

What's Stored
  • • KYC documents (passport, ID)
  • • Signed contracts (prospectus, KID)
  • • Invoice PDFs
  • • Audit reports
  • • Regulatory filings
  • • Internal memos
Storage

Encrypted S3 / Azure Blob. IPFS for document integrity (hash on-chain, content off-chain).

Data Flow — Payment Example

Payment Initiation:
┌─────────────────────────────────────────────────────────────────┐
│ Off-Chain (Structured)                                          │
│ • Payment details stored in PostgreSQL (encrypted)              │
│ • ISO 20022 XML generated and signed                            │
│ • keccak256(paymentXML) computed → hash                         │
└─────────────────────────────────────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────────────┐
│ On-Chain                                                          │
│ • PaymentHash stored in CBPR contract (immutable)               │
│ • PaymentHashStored event emitted → indexed                     │
│ • Balance updated, FreezeManager checked, Identity checked      │
└─────────────────────────────────────────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────────────┐
│ Off-Chain (Structured + Unstructured)                           │
│ • ISO 20022 XML sent via SWIFT to recipient bank                │
│ • Recipient bank verifies hash matches on-chain                 │
│ • Settlement confirmation logged to PostgreSQL                  │
│ • Audit trail appended (append-only log)                        │
│ • KYC documents referenced by hash (not stored on-chain)       │
└─────────────────────────────────────────────────────────────────┘

Middleware Architecture — The Bridge Layer

In Plain English

The middleware is the glue between the blockchain and the bank's existing systems. It receives requests from SAP/Oracle, validates them, talks to the smart contracts, and sends back results. It also listens for blockchain events and updates the bank's internal systems. Think of it as a translator that speaks both "bank" (REST APIs, ISO 20022) and "blockchain" (JSON-RPC, smart contracts).

Middleware architecture must handle: request/response translation, event listening and processing, policy enforcement, retry logic, idempotency, and graceful degradation when blockchain is unavailable. It is the single point of failure for off-chain operations and must be designed for high availability.

Middleware Service Components

Core Components
API Gateway

REST/GraphQL endpoints for frontend and legacy systems. Rate limiting, authentication, request validation.

Event Listener

Watches blockchain for events (PaymentHashStored, NavUpdated, AddressFrozen). Processes and stores off-chain.

Policy Engine

Validates transactions against business rules: amount limits, whitelist, corridor rules, sanctions.

Idempotency Manager

Prevents duplicate transactions. Tracks request IDs, ensures exactly-once semantics.

Integration Adapters
Blockchain Adapter

ethers.js/viem client. Manages connection pooling, gas estimation, transaction signing via custodian.

Custodian Adapter

Fireblocks/Copper API client. Manages key derivation, transaction submission, status polling.

Banking Adapter

SAP/Oracle REST API client. Sends payment instructions, receives settlement confirmations.

SWIFT Adapter

ISO 20022 XML generation/parsing. SWIFT pacs.008 message creation and validation.

Middleware Request Flow

┌──────────────────────────────────────────────────────────────────────┐
│                     MIDDLEWARE ARCHITECTURE                           │
│                                                                      │
│  ┌──────────┐    ┌──────────────┐    ┌──────────────────┐           │
│  │ Frontend │───>│ API Gateway  │───>│ Policy Engine    │           │
│  │ (React)  │    │ (Express)    │    │ (Validation)     │           │
│  └──────────┘    └──────┬───────┘    └────────┬─────────┘           │
│                         │                     │                       │
│                         ▼                     ▼                       │
│                  ┌──────────────┐    ┌──────────────────┐           │
│                  │ Idempotency  │    │ Blockchain       │           │
│                  │ Manager      │    │ Adapter (ethers) │           │
│                  └──────┬───────┘    └────────┬─────────┘           │
│                         │                     │                       │
│                         ▼                     ▼                       │
│                  ┌────────────────────────────────────────┐         │
│                  │           EVENT LISTENER               │         │
│                  │  • Listens to blockchain events         │         │
│                  │  • Processes: PaymentHashStored, Nav    │         │
│                  │    Updated, AddressFrozen               │         │
│                  │  • Updates PostgreSQL + Kafka           │         │
│                  └─────────────────┬──────────────────────┘         │
│                                    │                                 │
│                         ┌──────────┴──────────┐                     │
│                         ▼                     ▼                       │
│                  ┌──────────────┐    ┌──────────────────┐           │
│                  │ PostgreSQL   │    │ Kafka / Redpanda │           │
│                  │ (encrypted)  │    │ (event streaming)│           │
│                  └──────────────┘    └──────────────────┘           │
│                                                                      │
│  External Integrations:                                              │
│  • Custodian API (Fireblocks)  • SAP/Oracle REST                    │
│  • SWIFT Network (pacs.008)   • Chainlink Oracle                   │
└──────────────────────────────────────────────────────────────────────┘

Upgradeability Architecture — Proxy Patterns & Governance

In Plain English

Smart contracts are immutable — once deployed, they can't be changed. But banks need to fix bugs, add features, and comply with new regulations. The solution is a "proxy pattern": the actual logic lives in separate contracts, and a proxy contract forwards calls to them. This allows upgrading the logic without losing state. But upgrades must be governed — no single person should be able to change contracts unilaterally. Banks require multi-sig approval + time-locks for all upgrades.

Upgradeability is mandatory for production banking systems. The architecture must support: transparent upgradeable contracts (UUPS or transparent proxy), governance-controlled upgrades (multi-sig + time-lock), emergency pause (separate from upgrade), and full audit trail of all upgrades (what changed, who approved, when).

Transparent Proxy Pattern

How It Works

Proxy contract stores implementation address. All calls to proxy are forwarded to implementation. Upgrade = change implementation address (requires multi-sig).

Pros
  • • Admin can see proxy storage directly
  • • No delegation overhead for admin calls
  • • Standard pattern, well-audited
Cons
  • • Admin has special role (trust assumption)
  • • Storage collisions if not carefully designed
  • • Etherscan shows "is proxy" warning

UUPS (Universal Upgradeable Proxy Standard)

How It Works

Upgrade logic is IN the implementation contract. The _upgradeTo function is called by the proxy, but the implementation validates the caller has permission.

Pros
  • • Upgrade logic with implementation (more secure)
  • • Smaller bytecode (no admin logic in proxy)
  • • ERC-1822 standard
Cons
  • • Can upgrade to contract that doesn't support upgrade (bricking risk)
  • • More complex storage layout
  • • Requires careful auditing of upgrade path

Upgrade Governance Flow

┌─────────────────────────────────────────────────────────────────┐
│                    UPGRADE GOVERNANCE FLOW                       │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  1. Developer creates new implementation contract                │
│     └─> Solidity compiler (version-pinned, deterministic)       │
│                                                                 │
│  2. Internal security review                                     │
│     └─> Static analysis (Slither, MythX)                        │
│     └─> Manual code review (2+ reviewers)                       │
│     └─> Testnet deployment + integration tests                  │
│                                                                 │
│  3. Governance proposal                                          │
│     └─> Submit upgrade proposal to Gnosis Safe                  │
│     └─> Include: new bytecode hash, migration script, tests     │
│                                                                 │
│  4. Multi-sig approval (5-of-7)                                 │
│     └─> Signers: CFO, CTO, Compliance, Risk, + 2 external      │
│     └─> 24-hour time-lock before execution                      │
│                                                                 │
│  5. Execution                                                    │
│     └─> Proxy.upgradeTo(newImplementation)                      │
│     └─> Upgrade event emitted → logged to SIEM                  │
│     └─> New implementation address stored on-chain              │
│                                                                 │
│  6. Post-upgrade verification                                    │
│     └─> Automated tests run against new implementation          │
│     └─> If failure: emergency rollback to previous version      │
│                                                                 │
│  Audit Trail (immutable):                                        │
│  • Proposal submitted (tx hash, timestamp, proposer)            │
│  • Votes recorded (who approved, when)                          │
│  • Execution (tx hash, old impl, new impl)                      │
│  • Post-upgrade verification (pass/fail)                        │
└─────────────────────────────────────────────────────────────────┘

Monitoring & Observability Architecture

In Plain English

You can't manage what you can't measure. For a banking system, monitoring is not optional — it's a regulatory requirement. Every transaction, every contract call, every error must be logged, tracked, and alertable. The architecture uses three pillars: metrics (numbers and trends), logs (detailed event records), and traces (end-to-end request flows). If something goes wrong, you need to know immediately, understand what happened, and be able to prove it to regulators.

Monitoring architecture must provide: real-time alerting for critical events (failed transactions, oracle staleness, freeze events), historical audit trails for regulatory reporting, performance metrics for capacity planning, and anomaly detection for security incidents. All data must be tamper-evident (append-only logs with hashes).

Metrics

Key Metrics
  • • Transaction throughput (TPS)
  • • Settlement latency (P50/P95/P99)
  • • Oracle freshness (seconds since last update)
  • • Contract gas costs per operation
  • • Balance reconciliation delta
  • • Freeze/unfreeze rate
  • • Circuit breaker activations
Tooling

Prometheus + Grafana for time-series metrics. Custom dashboards per team (ops, compliance, dev).

Logs

Log Categories
  • • Transaction logs (hash, amount, parties, status)
  • • Compliance logs (KYC checks, sanctions screening)
  • • Security logs (auth attempts, policy violations)
  • • Operational logs (deployments, config changes)
  • • Oracle logs (price updates, staleness warnings)
Tooling

ELK Stack (Elasticsearch, Logstash, Kibana) or Splunk. Structured JSON logs with correlation IDs. 7-year retention (regulatory).

Traces

Trace Scope
  • • End-to-end payment flow (frontend → middleware → blockchain → SWIFT)
  • • Oracle price update flow (off-chain → oracle network → on-chain)
  • • Mint/burn flow (fiat confirmation → token mint → event emission)
  • • Compliance check flow (KYC → sanctions → corridor → decision)
Tooling

OpenTelemetry + Jaeger. Distributed tracing with W3C trace context. Correlates blockchain tx hashes with internal request IDs.

Alerting Tiers & Response

P1 — Critical (Page On-Call)
  • • Global circuit breaker activated
  • • Oracle staleness > 1 hour
  • • Balance reconciliation failure
  • • Smart contract exploit detected
  • • Response: 15 minutes

DORA Art. 18: Must be assessed against DORA major incident criteria. If classified as major, NBB notification required within 4 hours of classification (DORA Art. 19).

P2 — High (Notify Team)
  • • Transaction failure rate > 5%
  • • Oracle deviation warning
  • • Freeze event (compliance)
  • • Middleware error rate spike
  • • Response: 1 hour
P3 — Low (Ticket)
  • • Gas price anomaly
  • • Slow oracle updates
  • • Dashboard errors
  • • Performance degradation
  • • Response: 24 hours
DORA (Regulation 2022/2554) Alignment

DORA (Digital Operational Resilience Act) applies to all financial entities in the EU from January 17, 2025. The Tokenize platform's incident classification maps directly to DORA's ICT incident taxonomy:

Significant ICT Incident (P1)
  • • Affects ≥10% of users or assets ≥€10M
  • • Lasts >4 hours or data breach involved
  • NBB notification required within 4 hours
  • • Final report within 12 hours of detection
Major ICT Incident (P1 above threshold)
  • • Cross-border impact or systemic risk
  • NBB notification within 4 hours
  • • Final report within 24 hours
  • • Post-incident review within 30 days

Practical implication: Any P1 event (global circuit breaker, oracle staleness >1 hour, balance reconciliation failure, smart contract exploit) must trigger internal escalation within 15 minutes and NBB notification within 4 hours of detection. The monitoring system must maintain an immutable audit log of all incident events for regulatory inspection.