This project demonstrates a full cross-border settlement pipeline:
- 🟦 On-chain stablecoin escrow (ERC-20)
- 🟩 Off-chain fiat payout simulation (Node.js + ethers.js)
- 🔗 Event-driven orchestration
- 🔒 Oracle-based settlement authorisation
- ⏳ TTL + refund safety logic
- 🧪 Foundry tests
- ⚙️ Forge automation scripts
- 🚀 End-to-end local demo (Anvil + Foundry + Node)
This prototype mirrors the architecture of real-world payment system where:
Stablecoin flows settle on-chain, and fiat transfers occur off-chain, coordinated by a backend oracle.
┌───────────────────────────────────────────┐
│ User / Client │
│ (Fintech, Wallet, App, Merchant) │
└───────────────────────────────────────────┘
│
▼
┌───────────────────────────────────────────┐
│ CrossBorderSettlement.sol (L1/L2) │
│ - Escrows stablecoins │
│ - Emits PaymentInitiated │
│ - Oracle releases funds to payoutAgent │
│ - Refunds on TTL expiry │
└───────────────────────────────────────────┘
│ (event)
▼
┌──────────────────────────────────────────────┐
│ Off-Chain Oracle (Node.js + Ethers.js) │
│ - Listens for PaymentInitiated │
│ - Simulates fiat payout │
│ - markCompleted(paymentId, ref) on-chain │
└──────────────────────────────────────────────┘
│ (transaction)
▼
┌───────────────────────────────────────────┐
│ Liquidity Provider / Payout Agent │
│ Receives tokens after oracle settlement │
└───────────────────────────────────────────┘
User Settlement Contract Off-Chain Oracle Payout Agent
| | | |
|-- initiatePayment() --> | | |
| |-- emit PaymentInitiated -->| |
| | |-- simulate payout --> |
| | | |
| | |-- markCompleted() -----> |
| |-- transfer to payoutAgent -------------------------------->|
| | | |
Anyone (after TTL) Settlement Contract
| |
|--- refund(paymentId) -->|
| |
|<-- funds returned ------|
Refund allowed if:
- sender calls refund anytime before completion
- oracle calls refund
- any address can refund after TTL expiry
Core responsibilities:
- Escrow stablecoins
- Maintain payment struct (sender, token, amount, corridor, payoutAgent, TTL, status)
- Emit events
- Release tokens to payout agent only on oracle confirmation
- Allow refunds safely
- Enforce role-based access control
- Prevent re-entrancy
- Pause the system when needed
initiatePayment() // user → escrow
markCompleted() // oracle → payout agent
refund() // sender/oracle/anyone after TTL
pause()
unpause()
State machine:
Initiated → Completed
Initiated → Refunded
(No backwards transitions allowed)
Imitates a real fiat payments backend:
- Connects to RPC (Anvil, local node, testnet)
- Subscribes to PaymentInitiated events
- Simulates fiat payout with a mock delay
- Calls markCompleted(paymentId, offchainRef)
- escrow correctness
- status machine
- TTL-based expiries
- refund controls
- oracle-only settlement
- pausing
Deploys MockERC20 + settlement contract.
Mints → approves → initiates a payment → triggers oracle.
anvil
forge script script/Deploy.s.sol:Deploy --rpc-url http://127.0.0.1:8545 --broadcast
cd offchain
npm start
forge script script/InitiatePayment.s.sol:InitiatePayment --rpc-url http://127.0.0.1:8545 --broadcast
Oracle output:
New PaymentInitiated event:
Simulating off-chain fiat payout...
markCompleted tx sent: 0x...
Payment completed on-chain for paymentId: 0
┌──────────────┐
│ Initiated │
└──────────────┘
/ / markCompleted refund()
(oracle) (sender/oracle/anyone after TTL)
| |
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Completed │ │ Refunded │
└──────────────┘ └──────────────┘
- oracle-gated settlement
- refund safety
- TTL liveness guarantee
- replay protection
- role-based access control
- non-reentrancy
- pause protection
| This Demo | Real Wordl |
|---|---|
| Escrow contract | Cross-chain stablecoin settlement |
| Oracle service | Payment orchestration backend |
| Mock payout | Bank/PSP real payout |
| markCompleted | Confirming off-chain fiat leg |
| TTL refund | Fail-safes for async rails |
- corridor limits
- liquidity-provider registry
- real stablecoin support
- docker compose full stack
- frontend dashboard