Skip to main content
Cross-border 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 shows how to orchestrate that flow on Solana with Dfns wallets handling key management and transaction signing.

Get the code

dfns/dfns-solutions — cross-border-payments-solana
The system involves three main actors:

Bank

Deploys stablecoins and the payment program, mints tokens, and executes settlements

FX Provider

Quotes the exchange rate and records the converted amount on-chain

Receiver

Receives funds in the destination currency (e.g., tSGD)
Setup — The Bank deploys two SPL token stablecoins (tEUR and tSGD) and the cross-border payment Anchor program. Payment flow:
  1. The Bank initiates a payment, specifying the receiver and amount in tEUR
  2. The FX Provider sets the conversion rate (how much tSGD the receiver gets)
  3. The Bank executes the payment — tEUR is burned from the sender and tSGD is minted to the receiver
The execution is atomic: the burn and mint happen in the same Solana transaction. If either fails, nothing happens.

Prerequisites

  1. Clone the cross-border-payments-solana 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 2 wallets: Bank and Receiver on Solana Devnet
  5. Fund the Bank wallet with devnet SOL for transaction fees (solana airdrop 2 <BANK_WALLET_ADDRESS> --url devnet)
  6. Make sure you have installed Node.js v18+, Rust, and Cargo
  7. Install Solana CLI and Anchor (v0.32+) — the included setup.sh script can install these

Project Structure

programs/cross-border-payment/
  src/lib.rs            Anchor program — 3 instructions + state

dfns/
  DfnsClient.ts         Dfns SDK setup, env config, exports
  broadcast.ts          Shared helper to serialize + broadcast via Dfns
  deploy-program.ts     Deploy/upgrade the Anchor program binary
  deploy-stablecoin.ts  Deploy SPL token mints with Metaplex metadata
  init-payment.ts       Initialize a payment PDA
  set-fx-rate.ts        Set the FX conversion rate
  execute-payment.ts    Execute the atomic burn/mint swap
  mint-tokens.ts        Mint tokens to any address
  server.ts             Express API server for the web UI
  ui.html               Interactive web UI (single file, no build step)

tests/
  cross-border-payment.ts   Full Anchor test suite (4 tests)

setup.sh                    One-command toolchain installer

Configuration

1

Clone and install

git clone https://github.com/dfns/dfns-solutions.git
cd dfns-solutions/cross-border-payments-solana

# Install Solana CLI, Anchor, and Node dependencies
./setup.sh

# Or if you already have the toolchain:
npm install
2

Set up environment variables

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

# Dfns Wallet ID
BANK_WALLET_ID=wa-...

# Set after deploying (steps below)
SOURCE_MINT=
TARGET_MINT=
PROGRAM_ID=CEMoNh21BbxrVdPM6N9xwpqFHD8dxAFkBscZqPEdfrbe
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 (wa-xxxx...)
SOURCE_MINTSource stablecoin mint address (set after deploying stablecoins)
TARGET_MINTTarget stablecoin mint address (set after deploying stablecoins)
PROGRAM_IDAnchor program ID (set after deploying, or use the default)
3

Build and test

Build the Anchor program and run the test suite against a local validator:
anchor build
anchor keys sync
anchor test
4 tests cover the full flow: initialization, FX rate setting, atomic execution, and duplicate prevention. No Dfns credentials or devnet access required.

Deploy

1

Deploy stablecoins

Deploy two SPL token mints that represent the source and target currencies. The bank’s Dfns wallet becomes the mint authority.
npm run deploy:stablecoin -- "Test EUR" tEUR
npm run deploy:stablecoin -- "Test SGD" tSGD
Each command prints the mint address. Copy both into your dfns/.env as SOURCE_MINT and TARGET_MINT.
2

Deploy the program

Upload the compiled Anchor program to Solana Devnet. The Dfns wallet is used as the payer and upgrade authority.
anchor build
npm run deploy:program
The upgrade authority stays in the Dfns KMS, meaning future upgrades also go through Dfns.
3

Mint tokens to the sender

npm run mint-tokens -- <SOURCE_MINT> <BANK_WALLET_ADDRESS> 1000000000
This mints 1,000 tokens (amounts use 6 decimals: 1000000000 = 1,000.000000 tokens).

End-to-End Payment Flow

1

Initialize a payment

Create a payment record on-chain. This stores the sender, receiver, and input amount in a PDA.
npm run init-payment -- 1 <RECEIVER_ADDRESS> 500000
  • 1 — payment ID (unique per sender)
  • 500000 — amount in base units (0.5 tokens)
2

Set the FX rate

The FX provider locks in the conversion rate. In this demo, any signer can call set_fx_rate — a production system should restrict this to authorized accounts.For example, converting 0.5 tEUR at a rate of 1.6x gives 0.8 tSGD:
npm run set-fx-rate -- 1 <SENDER_ADDRESS> 800000
  • 800000 — the exact output amount in target token base units
3

Execute the payment

Trigger the atomic swap. The program burns the source tokens from the sender and mints the target tokens to the receiver:
npm run execute-payment -- 1
The script automatically creates the receiver’s token account if it doesn’t exist yet.
4

Verify balances

  • Sender: should show reduced tEUR balance
  • Receiver: should show new tSGD balance

Interactive UI

The solution includes a web UI for running the full payment flow:
npm run ui
Open http://localhost:3000. The UI provides:
  • A three-step form to initialize a payment, set the FX rate, and execute the swap
  • A flow visualization showing the current payment status (PendingFX → FXRateSet → Completed)
  • Live balances for sender and receiver (source tokens, target tokens, SOL)
  • Solana Explorer links for every transaction

CLI Reference

ScriptUsageDescription
npm run deploy:stablecoin-- <name> <symbol>Deploy an SPL token mint with Metaplex metadata
npm run deploy:program[buffer_address]Deploy or upgrade the Anchor program via Dfns
npm run mint-tokens-- <mint> <recipient> <amount>Mint tokens to an address
npm run init-payment-- <id> <receiver> <amount>Initialize a payment PDA on-chain
npm run set-fx-rate-- <id> <sender> <amount_out>Lock in the FX conversion rate
npm run execute-payment-- <id>Execute the atomic burn/mint swap
npm run uiStart the interactive web UI on port 3000
All amounts are in base units (6 decimals). To send 100 tokens, pass 100000000.

Settle to fiat with Payouts

If your cross-border flow ends in fiat bank deposits rather than on-chain tokens, use Payouts to convert stablecoins (USDC, USDT, EURC) to local currency across 94 countries and 63 currencies. Payouts supports Solana. See the usage guide or developer guide.
Last modified on March 9, 2026