> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dfns.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Set up cross-border payments (Ethereum & EVM)

> Process cross-border payments with FX conversion on EVM networks using Dfns wallets, stablecoins, and policy-controlled treasury operations.

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.

<Card title="Get the code" icon="github" href="https://github.com/dfns/dfns-solutions/tree/m/cross-border-payments">
  Clone the repository to follow along.
</Card>

**The system involves three main actors:**

<CardGroup cols={3}>
  <Card title="Bank" icon="building-columns">
    Deploys contracts, mints tokens, manages roles, and sets the FX rate for each payment
  </Card>

  <Card title="Sender" icon="arrow-right-from-bracket">
    Initiates payments in the source currency (e.g., iEUR)
  </Card>

  <Card title="Receiver" icon="arrow-right-to-bracket">
    Receives funds in the destination currency (e.g., iAUD)
  </Card>
</CardGroup>

**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

```mermaid theme={null}
sequenceDiagram
    participant Bank
    participant Sender
    participant Dfns
    participant Contract as Smart Contract
    participant Receiver

    Sender->>Dfns: Initiate payment (100 iEUR)
    Dfns->>Contract: Sign & broadcast
    Bank->>Dfns: Set FX rate (1 iEUR = 1.5 iAUD)
    Dfns->>Contract: Sign & broadcast
    Sender->>Dfns: Execute payment
    Dfns->>Contract: Sign & broadcast
    Contract-->>Sender: Burn 100 iEUR
    Contract-->>Receiver: Mint 150 iAUD
```

## Prerequisites

1. Clone the [cross-border-payments solution](https://github.com/dfns/dfns-solutions/tree/m/cross-border-payments)
2. Create a [Dfns organization](https://app.dfns.io) 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](/guides/developers/service-account))
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](/guides/network-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

<Steps>
  <Step title="Clone and install">
    ```bash theme={null}
    cd cross-border-payments
    npm install
    ```
  </Step>

  <Step title="Set up environment variables">
    Copy the example environment file and fill in your values:

    ```bash theme={null}
    cp .env.example .env
    ```

    ```bash .env theme={null}
    # 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
    ```

    | Variable             | Description                                                                         |
    | -------------------- | ----------------------------------------------------------------------------------- |
    | `DFNS_API_URL`       | Dfns API base URL (`https://api.dfns.io`)                                           |
    | `DFNS_ORG_ID`        | Your [organization ID](/guides/find-organization-id)                                |
    | `DFNS_AUTH_TOKEN`    | [Service account](/guides/developers/service-account) auth token                    |
    | `DFNS_CRED_ID`       | Credential ID for the signing key (found in Settings > Service Accounts)            |
    | `DFNS_PRIVATE_KEY`   | Private key for [request signing](/guides/developers/signing-requests) (PEM format) |
    | `BANK_WALLET_ID`     | Wallet ID for the bank/admin role                                                   |
    | `SENDER_WALLET_ID`   | Wallet ID for the payment sender                                                    |
    | `RECEIVER_WALLET_ID` | Wallet ID for the payment receiver                                                  |
    | `BLOCKCHAIN_RPC_URL` | RPC endpoint for the target blockchain                                              |
  </Step>

  <Step title="Compile contracts">
    ```bash theme={null}
    npx hardhat compile
    ```
  </Step>
</Steps>

## Deploy the System

```mermaid theme={null}
sequenceDiagram
    participant Bank
    participant Dfns
    participant Contract as Smart Contract
    participant Sender

    Bank->>Dfns: Deploy stablecoins and payment contract
    Dfns->>Contract: Sign & broadcast
    Bank->>Dfns: Issue iEUR to Sender
    Dfns->>Contract: Sign & broadcast
    Contract-->>Sender: Mint iEUR
```

```bash theme={null}
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

<Steps>
  <Step title="Grant the MINTER_ROLE to the CrossBorderPayment contract">
    The payment contract needs permission to mint iAUD when settling payments:

    ```bash wrap theme={null}
    npm run receiver-cli grantRole MINTER_ROLE <iAUD_ADDRESS> <CBP_ADDRESS>
    ```
  </Step>

  <Step title="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:

    ```bash wrap theme={null}
    npm run sender-cli init <CBP_ADDRESS> <iEUR_ADDRESS> <RECEIVER_ADDRESS> 100
    ```

    Note the **Payment ID** from the output.
  </Step>

  <Step title="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):

    ```bash wrap theme={null}
    npm run fx-cli set-rate <CBP_ADDRESS> <PAYMENT_ID> 150
    ```
  </Step>

  <Step title="Execute the payment (Sender)">
    The sender confirms and executes the payment. This burns iEUR from the sender and mints iAUD to the receiver:

    ```bash wrap theme={null}
    npm run sender-cli execute <CBP_ADDRESS> <PAYMENT_ID>
    ```
  </Step>

  <Step title="Verify balances">
    * Sender: 900 iEUR (100 burned)
    * Receiver: 150 iAUD (150 minted)
  </Step>
</Steps>

## Run the Test Suite

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

```bash theme={null}
npx hardhat test
```

## Settle to fiat with Payouts

If your cross-border flow ends in fiat bank deposits rather than on-chain tokens, use [Payouts](/features/payouts) to convert stablecoins (USDC, USDT, EURC) to local currency across 94 countries and 63 currencies. See the [usage guide](/guides/payouts) or [developer guide](/guides/developers/payouts).
