Connect your sandbox to see this tutorial in action.

Tutorials

Currency exchange

Learn how to implement a currency exchange with Blnk.

A currency exchange is two transactions, one for each currency. Alice gives up USD, and a nostro balance pays her the GBP. Both legs are one atomic batch, so they succeed or fail together.

This tutorial shows how to implement that with Blnk.

Step

Design the money movement

Before we start building, we need to define our money movement. This helps us outline how one currency becomes another.

Alice converts $1,000.00 to GBP. At $1.00 = £0.79, she receives £790.00. Her USD balance pays @NostroUSD, and @NostroGBP pays her GBP balance.

Both are internal balances in the General Ledger. We send them in one bulk request with atomic set, so a failed GBP leg does not leave her USD already taken.

Step

Create a ledger

Before we start recording transactions, we need to set up our ledger to track customers and their balances.

Alice holds USD and GBP in the same ledger. The nostro balances are created in the General Ledger when we record the exchange.

Create the ledger:

curl -X POST "CORE_URL/ledgers" \
  -H "X-blnk-key: CORE_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "{{name}}",
    "meta_data": {
      "description": "{{description}}"
    }
  }'
const response = await blnk.Ledgers.create({
  name: '{{name}}',
  meta_data: {
    description: '{{description}}',
  },
});
ledger, _, _ := client.Ledger.Create(blnkgo.CreateLedgerRequest{
    Name: "{{name}}",
    MetaData: blnkgo.MetaData{
        "description": "{{description}}",
    },
})
response = blnk.ledgers.create({
  "name": "{{name}}",
  "meta_data": {
    "description": "{{description}}",
  },
})
ApiResponse<JsonNode> response = blnk.ledgers().create(
    CreateLedger.create()
        .name("{{name}}")
        .metaData(Map.of(
            "description", "{{description}}"
        )));

Step

Create an identity

An identity is how we record the customer. We pass Alice’s type, name, email, and phone number.

Anything else we want to keep goes in metadata. We’ll store her customer id there as customer_id. Both currency balances use this same identity.

Create the identity:

curl -X POST "CORE_URL/identities" \
  -H "X-blnk-key: CORE_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "identity_type": "individual",
    "first_name": "{{first_name}}",
    "last_name": "{{last_name}}",
    "email_address": "{{email_address}}",
    "phone_number": "{{phone_number}}",
    "meta_data": {
      "customer_id": "{{customer_id}}"
    }
  }'
const response = await blnk.Identity.create({
  identity_type: 'individual',
  first_name: '{{first_name}}',
  last_name: '{{last_name}}',
  email_address: '{{email_address}}',
  phone_number: '{{phone_number}}',
  meta_data: {
    customer_id: '{{customer_id}}',
  },
});
identity, _, _ := client.Identity.Create(blnkgo.Identity{
    IdentityType: blnkgo.Individual,
    FirstName: "{{first_name}}",
    LastName: "{{last_name}}",
    EmailAddress: "{{email_address}}",
    PhoneNumber: "{{phone_number}}",
    MetaData: blnkgo.MetaData{
        "customer_id": "{{customer_id}}",
    },
})
response = blnk.identity.create({
  "identity_type": "individual",
  "first_name": "{{first_name}}",
  "last_name": "{{last_name}}",
  "email_address": "{{email_address}}",
  "phone_number": "{{phone_number}}",
  "meta_data": {
    "customer_id": "{{customer_id}}",
  },
})
ApiResponse<JsonNode> response = blnk.identity().create(
    IdentityData.create()
        .identityType("individual")
        .firstName("{{first_name}}")
        .lastName("{{last_name}}")
        .emailAddress("{{email_address}}")
        .phoneNumber("{{phone_number}}")
        .metaData(Map.of("customer_id", "{{customer_id}}")));

Step

Create the USD balance

Alice needs a USD balance to convert from. It sits on the customer ledger and uses her identity.

We’ll use the ledger_id and identity_id from the earlier steps, set the currency to USD, and submit.

Create the USD balance:

curl -X POST "CORE_URL/balances" \
  -H "X-blnk-key: CORE_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "ledger_id": "{{ledger_id}}",
    "identity_id": "{{identity_id}}",
    "currency": "{{currency}}"
  }'
const response = await blnk.LedgerBalances.create({
  ledger_id: '{{ledger_id}}',
  identity_id: '{{identity_id}}',
  currency: '{{currency}}',
});
balance, _, _ := client.LedgerBalance.Create(blnkgo.CreateLedgerBalanceRequest{
    LedgerID: "{{ledger_id}}",
    IdentityID: "{{identity_id}}",
    Currency: "{{currency}}",
})
response = blnk.ledger_balances.create({
  "ledger_id": "{{ledger_id}}",
  "identity_id": "{{identity_id}}",
  "currency": "{{currency}}",
})
ApiResponse<JsonNode> response = blnk.ledgerBalances().create(
    CreateLedgerBalance.create()
        .ledgerId("{{ledger_id}}")
        .identityId("{{identity_id}}")
        .currency("{{currency}}"));

Step

Create the GBP balance

The GBP she receives needs its own balance. A balance has one currency, so we do not convert inside the USD balance.

It uses the same ledger and the same identity, with the currency set to GBP.

Create the GBP balance:

curl -X POST "CORE_URL/balances" \
  -H "X-blnk-key: CORE_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "ledger_id": "{{ledger_id}}",
    "identity_id": "{{identity_id}}",
    "currency": "{{currency}}"
  }'
const response = await blnk.LedgerBalances.create({
  ledger_id: '{{ledger_id}}',
  identity_id: '{{identity_id}}',
  currency: '{{currency}}',
});
balance, _, _ := client.LedgerBalance.Create(blnkgo.CreateLedgerBalanceRequest{
    LedgerID: "{{ledger_id}}",
    IdentityID: "{{identity_id}}",
    Currency: "{{currency}}",
})
response = blnk.ledger_balances.create({
  "ledger_id": "{{ledger_id}}",
  "identity_id": "{{identity_id}}",
  "currency": "{{currency}}",
})
ApiResponse<JsonNode> response = blnk.ledgerBalances().create(
    CreateLedgerBalance.create()
        .ledgerId("{{ledger_id}}")
        .identityId("{{identity_id}}")
        .currency("{{currency}}"));

Step

Record a deposit

Alice deposits $1,000.00 before she converts. The source is @WorldUSD, and the destination is her USD balance.

The amount is a decimal, and we multiply it by precision 100 to get precise_amount. The reference and the description are already set.

Record the deposit:

curl -X POST "CORE_URL/transactions" \
  -H "X-blnk-key: CORE_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "precise_amount": {{precise_amount}},
    "precision": 100,
    "reference": "FX-DEP-20261019-103000",
    "description": "USD deposit",
    "currency": "{{currency}}",
    "source": "{{source}}",
    "destination": "{{destination}}",
    "allow_overdraft": true
  }'
const amount = {{amount}};
const precision = 100;
const precise_amount = Math.round(amount * precision);

const response = await blnk.Transactions.create({
  precise_amount,
  precision,
  reference: 'FX-DEP-20261019-103000',
  description: 'USD deposit',
  currency: '{{currency}}',
  source: '{{source}}',
  destination: '{{destination}}',
  allow_overdraft: true,
});
amount := {{amount}}
precision := 100
preciseAmount := big.NewInt(int64(math.Round(amount * float64(precision))))

transaction, _, _ := client.Transaction.Create(blnkgo.CreateTransactionRequest{
    ParentTransaction: blnkgo.ParentTransaction{
        PreciseAmount: preciseAmount,
        Precision: precision,
        Reference: "FX-DEP-20261019-103000",
        Description: "USD deposit",
        Currency: "{{currency}}",
        Source: "{{source}}",
        Destination: "{{destination}}",
    },
    AllowOverdraft: true,
})
amount = {{amount}}
precision = 100
precise_amount = round(amount * precision)

response = blnk.transactions.create({
  "precise_amount": precise_amount,
  "precision": precision,
  "reference": "FX-DEP-20261019-103000",
  "description": "USD deposit",
  "currency": "{{currency}}",
  "source": "{{source}}",
  "destination": "{{destination}}",
  "allow_overdraft": True,
})
double amount = {{amount}};
int precision = 100;
int preciseAmount = (int) Math.round(amount * precision);

ApiResponse<JsonNode> response = blnk.transactions().create(
    CreateTransactions.create()
        .preciseAmount(preciseAmount)
        .precision(precision)
        .reference("FX-DEP-20261019-103000")
        .description("USD deposit")
        .currency("{{currency}}")
        .source("{{source}}")
        .destination("{{destination}}")
        .allowOverdraft(true));

Step

Exchange the currencies

We record both legs in one request to POST /transactions/bulk. The USD leg debits Alice and credits @NostroUSD. The GBP leg debits @NostroGBP and credits Alice.

Each amount is a decimal, and we multiply it by precision 100. The references and the description are already set. @NostroGBP starts at 0, so that leg turns on overdraft.

Exchange the currencies:

curl -X POST "CORE_URL/transactions/bulk" \
  -H "X-blnk-key: CORE_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "atomic": true,
    "transactions": [
      {
        "precise_amount": {{usd_amount_precise}},
        "precision": 100,
        "reference": "FX-USD-20261019-103000",
        "description": "USD to GBP conversion",
        "currency": "USD",
        "source": "{{usd_source}}",
        "destination": "@NostroUSD"
      },
      {
        "precise_amount": {{gbp_amount_precise}},
        "precision": 100,
        "reference": "FX-GBP-20261019-103000",
        "description": "USD to GBP conversion",
        "currency": "GBP",
        "source": "@NostroGBP",
        "destination": "{{gbp_destination}}",
        "allow_overdraft": true
      }
    ]
  }'
const usdAmount = {{usd_amount}};
const gbpAmount = {{gbp_amount}};
const precision = 100;

const response = await blnk.Transactions.createBulk({
  atomic: true,
  transactions: [
    {
      precise_amount: Math.round(usdAmount * precision),
      precision,
      reference: 'FX-USD-20261019-103000',
      description: 'USD to GBP conversion',
      currency: 'USD',
      source: '{{usd_source}}',
      destination: '@NostroUSD',
    },
    {
      precise_amount: Math.round(gbpAmount * precision),
      precision,
      reference: 'FX-GBP-20261019-103000',
      description: 'USD to GBP conversion',
      currency: 'GBP',
      source: '@NostroGBP',
      destination: '{{gbp_destination}}',
      allow_overdraft: true,
    },
  ],
});
usdAmount := {{usd_amount}}
gbpAmount := {{gbp_amount}}
precision := 100

batch, _, _ := client.Transaction.CreateBulk(blnkgo.CreateBulkTransactionRequest{
    Atomic: true,
    Transactions: []blnkgo.CreateTransactionRequest{
        {
            PreciseAmount: big.NewInt(int64(math.Round(usdAmount * float64(precision)))),
            Precision: precision,
            Reference: "FX-USD-20261019-103000",
            Description: "USD to GBP conversion",
            Currency: "USD",
            Source: "{{usd_source}}",
            Destination: "@NostroUSD",
        },
        {
            PreciseAmount: big.NewInt(int64(math.Round(gbpAmount * float64(precision)))),
            Precision: precision,
            Reference: "FX-GBP-20261019-103000",
            Description: "USD to GBP conversion",
            Currency: "GBP",
            Source: "@NostroGBP",
            Destination: "{{gbp_destination}}",
            AllowOverdraft: true,
        },
    },
})
usd_amount = {{usd_amount}}
gbp_amount = {{gbp_amount}}
precision = 100

response = blnk.transactions.create_bulk({
  "atomic": True,
  "transactions": [
    {
      "precise_amount": round(usd_amount * precision),
      "precision": precision,
      "reference": "FX-USD-20261019-103000",
      "description": "USD to GBP conversion",
      "currency": "USD",
      "source": "{{usd_source}}",
      "destination": "@NostroUSD",
    },
    {
      "precise_amount": round(gbp_amount * precision),
      "precision": precision,
      "reference": "FX-GBP-20261019-103000",
      "description": "USD to GBP conversion",
      "currency": "GBP",
      "source": "@NostroGBP",
      "destination": "{{gbp_destination}}",
      "allow_overdraft": True,
    },
  ],
})
double usdAmount = {{usd_amount}};
double gbpAmount = {{gbp_amount}};
int precision = 100;

ApiResponse<JsonNode> response = blnk.transactions().createBulk(
    BulkTransactions.create()
        .atomic(true)
        .transactions(List.of(
            CreateTransactions.create()
                .preciseAmount((int) Math.round(usdAmount * precision))
                .precision(precision)
                .reference("FX-USD-20261019-103000")
                .description("USD to GBP conversion")
                .currency("USD")
                .source("{{usd_source}}")
                .destination("@NostroUSD"),
            CreateTransactions.create()
                .preciseAmount((int) Math.round(gbpAmount * precision))
                .precision(precision)
                .reference("FX-GBP-20261019-103000")
                .description("USD to GBP conversion")
                .currency("GBP")
                .source("@NostroGBP")
                .destination("{{gbp_destination}}")
                .allowOverdraft(true)
        )));

Step

Confirmation

The batch is queued. When both legs are applied, Alice’s USD balance is 0 and her GBP balance is £790.00.

If you have a Blnk Cloud account or an Enterprise license, you can confirm this by checking the dashboard or using the CLI.

Print every balance:

blnk balances list