- Config, docs, scripts, and backup manifests - Submodule refs unchanged (m = modified content in submodules) Made-with: Cursor
11 KiB
ISO-20022 Intake / Gateway Contract on Different Blockchain Networks
Version: 1.0
Last Updated: 2026-02-23
Status: Active
Companion to: SMART_CONTRACTS_ISO20022_FIN_METHODOLOGY.md
1. Purpose
This document describes how the intake or gateway contract that receives ISO-20022 (and Fin) messages works across different blockchain networks: same logical contract, same address where possible, two delivery paths (relayer-submitted vs cross-chain), and per-chain configuration without breaking deterministic deployment.
2. Role of the Intake / Gateway Contract
The ISO intake contract is the single on-chain entry point that:
- Accepts a canonical ISO message (see SMART_CONTRACTS_ISO20022_FIN_METHODOLOGY.md) from either:
- an off-chain relayer (gateway that parsed MX/MT and submits the canonical payload), or
- a cross-chain message (e.g. CCIP) that carries the canonical payload from another chain.
- Enforces idempotency (by
instructionId/msgId), authorisation (relayer role or CCIP router), and optional policy (ComplianceGuard, allowlists). - Executes the intended action: mint, transfer, or unlock for bridge, and emits events with canonical metadata for audit and ISO-20022 reporting.
The contract does not parse raw MX/MT; it only ever sees the canonical struct. Parsing and mapping happen off-chain or on the source chain before submission.
3. Same Address on Every Network
To keep integration simple and avoid per-chain address maps, the intake contract is deployed at the same address on every supported chain, following the same pattern as UniversalCCIPBridge and MULTI_CHAIN_EXECUTION_DETERMINISTIC_DEPLOYMENT.
3.1 Deterministic Deployment (CREATE2)
- Formula:
address = keccak256(0xff ++ deployer ++ salt ++ keccak256(bytecode))[12:]. - Identical bytecode on every chain (same compiler, no chain-specific branches in bytecode).
- Identical constructor / initializer args for the core contract; any chain-specific config (e.g. CCIP router, relayer list) is set after deployment via
initialize()or setters.
3.2 Suggested Salt and Initialization
| Item | Value |
|---|---|
| Contract name | ISO20022IntakeGateway (or equivalent) |
| Salt | keccak256("ISO20022IntakeGateway") (fixed, documented) |
| Constructor | Minimal (e.g. none) or same admin everywhere |
| initialize(args) | admin, optional ccipRouter, optional relayer; same admin on all chains; ccipRouter can be set to 0 and configured per chain later |
This yields one canonical intake contract address across all networks (e.g. 138, 1, 56, 10, 137, 42161, 8453, 43114, and 651940 if supported). Integrators and off-chain gateways can use that single address regardless of chain.
4. Two Ways Messages Reach the Intake Contract
Messages reach the intake contract in two ways: direct submission by a relayer (same chain) or delivery via a cross-chain protocol (e.g. CCIP) from another chain.
4.1 Path A: Relayer-Submitted (Same Chain)
Flow:
- Off-chain gateway receives ISO-20022 MX or SWIFT Fin MT.
- Gateway parses, validates, and maps to the canonical struct (see methodology doc).
- Gateway (as a relayer) calls the intake contract on the target chain:
submitInbound(CanonicalMessage calldata m)for credits (mint / release), orsubmitOutbound(CanonicalMessage calldata m)for debits (burn / lock),
with the relayer’s EOA or contract holding RELAYER_ROLE (or INTAKE_RELAYER_ROLE).
On-chain:
msg.sendermust have the relayer role.- Contract checks
processedInstructions[m.instructionId](orprocessedMessages[m.msgId]); reverts if already processed. - Contract optionally checks ComplianceGuard / PolicyManager using
m.debtorId,m.creditorId,m.purpose. - Contract performs the action (mint, transfer, bridge unlock) and sets
processedInstructions[m.instructionId] = true. - Contract emits an event with canonical fields for audit and pacs.002/camt.054 mapping.
Per-chain: Only the relayer address(es) need to be configured per chain (e.g. different gateway EOA or multisig per network). The intake contract bytecode and address stay the same.
4.2 Path B: Cross-Chain Delivery (e.g. CCIP)
Flow:
- On the source chain, an authorised sender (e.g. the same intake contract at the same address, or a dedicated “sender” contract) encodes the canonical struct into
bytes dataand sends a CCIP (or other cross-chain) message to the destination chain, with receiver = intake contract address (same canonical address). - On the destination chain, the CCIP router calls the intake contract’s receive entry point (e.g.
ccipReceive(Any2EVMMessage calldata message)). - The intake contract:
- Verifies the call is from the CCIP router (or a designated receiver adapter) via
msg.sender == ccipRouteror a ROUTER_ROLE check. - Decodes
message.datato obtain the CanonicalMessage. - Applies replay protection using
message.messageIdand/or the decodedinstructionId(must not already be inprocessedMessages/processedInstructions). - Optionally validates source chain and sender from
message.sourceChainSelectorandmessage.sender(allowlist or “same intake contract on source chain”). - Executes the same logic as Path A (mint / transfer / unlock) and emits the same canonical events.
- Verifies the call is from the CCIP router (or a designated receiver adapter) via
Per-chain: The CCIP router address is chain-specific. It is set in initialize() or via setCCIPRouter(address) after deployment so that the same bytecode is used everywhere. On chains without CCIP (e.g. 651940), the router can be set to address(0) and Path B disabled; only Path A (relayer) is used.
5. Contract Interface (Summary)
The intake contract exposes at least:
| Entry point | Caller | Purpose |
|---|---|---|
| submitInbound(CanonicalMessage) | Relayer (Path A) | Process an inbound credit (mint / release from bridge). |
| submitOutbound(CanonicalMessage) | Relayer (Path A) | Process an outbound debit (burn / lock for bridge). |
| ccipReceive(Any2EVMMessage) | CCIP router only (Path B) | Decode payload to CanonicalMessage and process as inbound (or outbound if encoded so). |
Optional:
- setCCIPRouter(address) – Admin; for deterministic deploy, init with router=0 then set per chain.
- addRelayer(address) / removeRelayer(address) – Admin; manage who can call submitInbound/submitOutbound.
Idempotency key: instructionId (and optionally msgId). Storage: mapping(bytes32 => bool) public processedInstructions; and, for CCIP, mapping(bytes32 => bool) public processedMessages; keyed by CCIP messageId to avoid replay from the transport layer.
6. How It Works on Different Networks (By Chain Type)
6.1 Chains With CCIP (e.g. 138, 1, 56, 10, 137, 42161, 8453, 43114)
- Deploy the intake contract via CREATE2 with the same salt and init args (e.g. admin; router=0).
- Post-deploy: Call
setCCIPRouter(ccipRouterAddress)with that chain’s CCIP router. - Relayer: Grant RELAYER_ROLE to the gateway(s) that will submit canonical messages on this chain.
- Behaviour: Both Path A (relayer) and Path B (CCIP) are active. Messages can arrive from off-chain (Path A) or from another chain (Path B) with the same canonical format.
6.2 Chains Without CCIP (e.g. ALL Mainnet 651940)
- Deploy the same contract at the same address via CREATE2 (same salt, same init; no CCIP router).
- Leave CCIP router as
address(0)(or never set it). Path B is unused. - Relayer: Only Path A is used; the off-chain gateway submits canonical messages via
submitInbound/submitOutboundfrom an address with RELAYER_ROLE. - Optionally, a custom cross-chain transport (e.g. AlltraCustomBridge-style) could later call a dedicated function that accepts the same canonical payload, with access control analogous to the CCIP router check.
6.3 Same Address, Different Config
- Address: Identical across all networks (CREATE2 + same bytecode + same constructor/init args).
- Config that can differ per chain:
- CCIP router address (or 0),
- Relayer list (RELAYER_ROLE),
- Optional: ComplianceGuard / PolicyManager / vault addresses if set via setters after deploy.
No per-chain address map is needed in application logic; only the single intake contract address is used, and chain-specific behaviour is controlled by which roles and router are set on that chain.
7. Security and Replay
- Path A: Idempotency by
instructionId(and optionallymsgId). Only RELAYER_ROLE can submit; relayer identity is per chain. - Path B: Replay protection by CCIP
messageIdand by decodedinstructionId; only the CCIP router (or ROUTER_ROLE) can callccipReceive. Validate source chain and sender if required (e.g. only accept from the same intake contract on allowed source chains). - Payload integrity: Optional check of
payloadHashin the canonical struct against an off-chain attested hash; contract can store or emit it for audit.
8. Downstream Actions
The intake contract does not hold balances long-term; it forwards the intent to:
- Mint: Call token factory or mint controller (with reserve/attestation checks as in MULTI_CHAIN_EXECUTION_ISO20022_EMONEY).
- Transfer: Call token
transferor a vault that holds tokens. - Bridge unlock: Call the bridge/vault contract’s release or unlock function with the same canonical metadata so that bridge and e-money runbooks stay aligned.
All such downstream calls should carry or emit the same canonical identifiers (instructionId, msgId, debtorId, creditorId, payloadHash) for audit and ISO-20022 reporting.
9. Related Documents
| Document | Description |
|---|---|
| SMART_CONTRACTS_ISO20022_FIN_METHODOLOGY.md | Canonical format, mapping, validation, and contract interface for ISO/Fin. |
| MULTI_CHAIN_EXECUTION_ISO20022_EMONEY.md | E-Money and ISO-20022 canonical message semantics. |
| MULTI_CHAIN_EXECUTION_CROSS_CHAIN_MESSAGE_HANDLING.md | Cross-chain message handling, same address, replay, sender verification. |
| MULTI_CHAIN_EXECUTION_DETERMINISTIC_DEPLOYMENT.md | CREATE2, salts, and deployment order. |
Document Control
- Owner: Configuration / Integration
- Review: When intake contract interface or supported chains change