Skip to main content
International payments require burning a source currency, applying an exchange rate, and minting the destination currency to the receiver — coordinating multiple actors (bank, sender, FX provider) across each step. This solution blueprint shows how to orchestrate that flow on-chain with Dfns wallets handling the key management and transaction signing.

Get the code

Clone the repository to follow along.
The system involves three main actors:

Bank

Deploys contracts, mints tokens, manages roles, and sets the FX rate for each payment

Sender

Initiates payments in the source currency (e.g., iEUR)

Receiver

Receives funds in the destination currency (e.g., iAUD)
Setup — The Bank deploys two stablecoins (iEUR and iAUD) and the CrossBorderPayment contract. Payment flow:
  1. The Sender initiates a payment, specifying the receiver and amount in iEUR
  2. The Bank sets the conversion rate (how much iAUD the receiver gets)
  3. The Sender executes the payment — iEUR is burned from the sender and iAUD is minted to the receiver

Prerequisites

  1. Clone the cross-border-payments solution
  2. Create a Dfns organization if you don’t have one already, and note the organization id
  3. Create a service account for API access (see how to create one here)
  4. Create 3 wallets: Bank, Sender and Receiver on an EVM-compatible network (e.g.: Ethereum Sepolia)
  5. Fund the Bank and Sender wallets with Testnet ETH for gas fees (see using testnets)
  6. Make sure you have installed Node.js v18+

Project Structure

contracts/
  StableCoin.sol              ERC-20 stablecoin with mint, burn, pause, and role-based access
  CrossBorderPayment.sol      Multi-step payment contract with FX conversion

dfns/
  DfnsCommon.ts               Shared Dfns API client and blockchain configuration
  DeployCrossBorderPayment.ts Deploy the full system (2 stablecoins + payment contract)
  SenderCLI.ts                CLI for the sender: init payment, execute payment, approve tokens
  ReceiverCLI.ts              CLI for role management (grant MINTER_ROLE, etc.)
  FXProviderCLI.ts            CLI for the FX provider: set exchange rates

test/
  CrossBorderPayment.ts       End-to-end tests for the payment flow

Configuration

1

Clone and install

cd cross-border-payments
npm install
2

Set up environment variables

Copy the example environment file and fill in your values:
cp .env.example .env
.env
# Dfns API Configuration
DFNS_API_URL=https://api.dfns.io
DFNS_ORG_ID=or-xxx-xxx
DFNS_AUTH_TOKEN=eyJ...
DFNS_CRED_ID=xxx
DFNS_PRIVATE_KEY="-----BEGIN EC PRIVATE KEY-----\n...\n-----END EC PRIVATE KEY-----"

# Dfns Wallet IDs
BANK_WALLET_ID=wa-xxx-xxx
SENDER_WALLET_ID=wa-xxx-xxx
RECEIVER_WALLET_ID=wa-xxx-xxx

# Blockchain Configuration
BLOCKCHAIN_RPC_URL=https://ethereum-sepolia-rpc.publicnode.com
VariableDescription
DFNS_API_URLDfns API base URL (https://api.dfns.io)
DFNS_ORG_IDYour organization ID
DFNS_AUTH_TOKENService account auth token
DFNS_CRED_IDCredential ID for the signing key (found in Settings > Service Accounts)
DFNS_PRIVATE_KEYPrivate key for request signing (PEM format)
BANK_WALLET_IDWallet ID for the bank/admin role
SENDER_WALLET_IDWallet ID for the payment sender
RECEIVER_WALLET_IDWallet ID for the payment receiver
BLOCKCHAIN_RPC_URLRPC endpoint for the target blockchain
3

Compile contracts

npx hardhat compile

Deploy the System

npm run deploy
This script:
  1. Deploys iEUR (source stablecoin)
  2. Deploys iAUD (destination stablecoin)
  3. Deploys the CrossBorderPayment contract
  4. Transfers iAUD ownership to the payment contract (so it can mint on settlement)
  5. Mints 1000 iEUR to the Sender wallet
Save the three contract addresses from the output.

End-to-End Payment Flow

1

Grant the MINTER_ROLE to the CrossBorderPayment contract

The payment contract needs permission to mint iAUD when settling payments:
npm run receiver-cli grantRole MINTER_ROLE <iAUD_ADDRESS> <CBP_ADDRESS>
2

Initiate a payment (Sender)

The sender initiates a 100 iEUR payment to the receiver. This approves the token transfer and calls initPayment in one step:
npm run sender-cli init <CBP_ADDRESS> <iEUR_ADDRESS> <RECEIVER_ADDRESS> 100
Note the Payment ID from the output.
3

Set the FX rate (FX Provider)

The FX provider sets how much iAUD the receiver will get. For example, 150 iAUD for 100 iEUR (1.5x rate):
npm run fx-cli set-rate <CBP_ADDRESS> <PAYMENT_ID> 150
4

Execute the payment (Sender)

The sender confirms and executes the payment. This burns iEUR from the sender and mints iAUD to the receiver:
npm run sender-cli execute <CBP_ADDRESS> <PAYMENT_ID>
5

Verify balances

  • Sender: 900 iEUR (100 burned)
  • Receiver: 150 iAUD (150 minted)

Run the Test Suite

The test suite verifies the full payment flow on a local Hardhat network (no Dfns credentials needed):
npx hardhat test