M Handle currencies#

Use multi-currency Ledgers to easily build products that track currencies, stocks and inventories.

A Ledger can contain Ledger Accounts of different currencies, like USD and GBP bank accounts. Ledger Accounts can also be multi-currency, like one representing a stock portfolio, with a balance for each symbol.

You can post Ledger Entries with multiple currencies. It must follow the Accounting Equation per currency, so you'll need at least four Ledger Lines.

FRAGMENT includes a list of common currencies. You can also add your own custom currencies

a. Track exposure

#

Multi-currency ledgers often reflect transitory states: a company accepts payment in one currency intending to convert it to another currency. Between accepting and converting the money, the exchange rate could change. Tracking the potential gain or loss from this change is called exposure.

To track exposure, use a Change Ledger Account that has multiple balances, one for each currency. Here's an example that tracks exposure between USD and EUR:

In this example,

  • User 1 starts out with 100 USD, balanced by 100 USD in the platform's bank.
  • User 1 pays User 2, but they have different wallet currencies, so we record the exchange rate they were given in an Exposure account. The exposure account balance is 100 USD - 90 EUR.
  • The platform now owes User 2 EUR, so it exchanges 100 USD for 95 EUR. In this case, due to exchange rate fluctuation, they got a different rate. The exposure account balance records the difference in exchange rates: 5 EUR.

b. Ledger Accounts

#

To create a multi-currency Ledger, set defaultCurrencyMode to multi and unset defaultCurrency:

Multi-currency Ledger
{
  "key": "...",
  "chartOfAccounts": {
    "defaultCurrencyMode": "multi",
    "accounts": [
      {...},
      {...}
    ]
  },
  "ledgerEntries": {...}
}

For Ledger Accounts in a single currency, such as bank accounts, set currencyMode and currency on the account directly:

Single-currency Ledger Account
{
  "key": "...",
  "chartOfAccounts": {
    "defaultCurrencyMode": "multi",
    "accounts": [
      {
        "key": "bank-account",
        "currencyMode": "single",
        "currency": {
          "code": "USD"
        }
      },
      {...}
    ]
  },
  "ledgerEntries": {...}
}

Like other Ledger Account properties, currencyMode and currency are inherited by child Ledger Accounts unless they are overridden.

c. Ledger Entries

#

You can define multi-currency Ledger Entries types in your Schema in the same way as single-currency Ledger Entries.

Multi-currency Ledger Accounts accept Ledger Lines in any currency, so Ledger Entries that post to them must specify the currency of each Ledger Line:

Multi-currency Ledger Entry
{
  "key": "...",
  "chartOfAccounts": {
    "defaultCurrencyMode": "multi",
    "accounts": [
      {
        "type": "asset",
        "key": "bank-account"
      },
      {...}
    ]
  },
  "ledgerEntries": {
    "types": [
      {
        "type": "user_funds_account_usd",
        "description": "Fund {{funding_amount}} USD",
        "lines": [
          {
            "key": "funds_arrive_in_bank",
            "account": { 
              "path": "bank-account"
            },
            "amount": "{{funding_amount}}",
            "currency": {
              "code": "USD"
            }
          },
          {...other line}
        ]
      }
    ]
  }
}

You can parameterize currency to make your Ledger Entries more reusable:

Parameterized multi-currency Ledger Entry
{
  "key": "...",
  "chartOfAccounts": {
    "defaultCurrencyMode": "multi",
    "accounts": [
      {
        "type": "asset",
        "key": "bank-account"
      },
      {...}
    ]
  },
  "ledgerEntries": {
    "types": [
      {
        "type": "user_funds_account",
        "description": "Fund {{funding_amount}} {{currency}}",
        "lines": [
          {
            "key": "funds_arrive_in_bank",
            "account": { 
              "path": "bank-account"
            },
            "amount": "{{funding_amount}}",
            "currency": {
              "code": "{{currency}}"
            }
          },
          {...other line}
        ]
      }
    ]
  }
}

You can also post multi-currency Ledger Entries to Ledger Account templates which parameterize currency. This is useful for creating linked Ledger Accounts if you have multiple bank accounts in different currencies in a multi-currency Ledger.

Parameterized Ledger Account templates
{
  "key": "...",
  "chartOfAccounts": {
    "defaultCurrencyMode": "multi",
    "accounts": [
      {
        "key": "bank-accounts",
        "template": true,
        "currencyMode": "single",
        "currency": {
          "code": "{{currency}}"
        }
      },
      {...}
    ]
  },
  "ledgerEntries": {
    "types": [
      {
        "type": "user_funds_account",
        "description": "Fund {{funding_amount}} {{currency}}",
        "lines": [
          {
            "key": "funds_arrive_in_bank",
            "account": { 
              "path": "bank-accounts:{{currency}}"
            },
            "amount": "{{funding_amount}}",
            "currency": {
              "code": "{{currency}}"
            }
          },
          {...other line}
        ]
      }
    ]
  }
}

Ledger Entry Conditions against multi-currency Ledger Accounts need to specify the currency the condition applies to:

Parameterized Ledger Account templates
{
  "key": "...",
  "chartOfAccounts": {
    "defaultCurrencyMode": "multi",
    "accounts": [
      {...},
      {...}
    ]
  },
  "ledgerEntries": {
    "types": [
      {
        "type": "p2p_transfer",
        "description": "Move {{funding_amount}} {{currency}}",
        "lines": [
          {...},
          {...}
        ],
        "conditions": [
          {
            "account": {
              "path": "liabilities/users:{{from_user_id}}"
            },
            "currency": {
              "code": "{{currency}}"
            },
            "postcondition": {
              "ownBalance": {
                "gte": "0"
              }
            }
          }
        ]
      }
    ]
  }
}

d. Read balances

#

Balances on multi-currency Ledger Accounts are lists of currency and amount, as opposed to just a single amount. They support all the same balance queries as single-currency accounts. You can either:

  1. Use plural field names (e.g. balances instead of balance) to query all currencies at once
  2. Use singular field names with a currency argument to query a specific currency
Latest#

To read a specific currency's balance, pass in the currency argument:

Get specific currency balance
query GetUSDBalance(
  $ledgerAccount: LedgerAccountMatchInput!
) {
  ledgerAccount(ledgerAccount: $ledgerAccount) {
    ownBalance(currency: { code: USD })
  }
}
GetUSDBalance variables
{
  "ledgerAccount": {
    "path": "assets/banks/user-cash",
    "ledger": {
      "ik": "multi-currency-ledger"
    }
  }
}

To read all the balances for a multi-currency Ledger Account, use ownBalances:

Get all currency balances
query GetAllBalances(
  $ledgerAccount: LedgerAccountMatchInput!
) {
  ledgerAccount(ledgerAccount: $ledgerAccount) {
    ownBalances {
      nodes {
        currency {
          code
        }
        amount
      }
    }
  }
}
Aggregated#

To read aggregated balances for multi-currency Ledger Accounts, pass in the currency argument, or query childBalances and balances:

Get aggregated balances read
query GetBalances(
  $ledgerAccount: LedgerAccountMatchInput!
) {
  ledgerAccount(ledgerAccount: $ledgerAccount) {

    # Query specific currency
    childBalance(currency: { code: USD })
    balance(currency: { code: USD })

    # Query all currencies
    childBalances {
      nodes {
        currency {
          code
        }
        amount
      }
    }
    balances {
      nodes {
        currency {
          code
        }
        amount
      }
    }
  }
}

If a Ledger Account has a descendant that is a multi-currency Ledger Account or if it has descendants of different currencies, it has childBalances and balances.

Consistent#

Balance reads are eventually consistent by default. To read consistent balances for multi-currency Ledger Accounts, pass in the consistencyMode argument:

Get strongly-consistent ownBalances read
query GetOwnBalances(
  $ledgerAccount: LedgerAccountMatchInput!
) {
  ledgerAccount(ledgerAccount: $ledgerAccount) {

    # Query specific currency
    ownBalance(
      consistencyMode: strong
      currency: { code: USD }
    )

    # Query all currencies
    ownBalances(consistencyMode: strong) {
      nodes {
        currency {
          code
        }
        amount
      }
    }
  }
}

consistencyMode can be set to:

  • strong to perform a strongly consistent balance read
  • eventual to perform an eventually consistent balance read
  • use_account to use the Schema value of the Ledger Account's consistencyConfig.ownBalanceUpdates setting

Only ownBalance and ownBalances can be queried with consistencyMode: strong.

Point in time#

To query the balance of a Ledger Account at a particular point in time, use the at argument:

Historical balance queries
query GetHistoricalBalances(
  $ledgerAccount: LedgerAccountMatchInput!
) {
  ledgerAccount(ledgerAccount: $ledgerAccount) {

    # Query specific currency
    end_of_year: balance(
      at: "1969"
      currency: { code: USD }
    )
    end_of_month: balance(
      at: "1969-07"
      currency: { code: USD }
    )

    # Query all currencies
    end_of_year_all: balances(at: "1969") {
      nodes {
        currency {
          code
        }
        amount
      }
    }
    end_of_month_all: balances(at: "1969-07") {
      nodes {
        currency {
          code
        }
        amount
      }
    }
  }
}
Net change#

Multi-currency Ledger Accounts support reading the net change over a period:

  • ownBalanceChanges, how much ownBalances changed
  • childBalanceChanges, how much childBalances changed
  • balanceChanges, how much balances changed
Balance change queries
query GetBalanceChanges(
  $ledgerAccount: LedgerAccountMatchInput!
) {
  ledgerAccount(ledgerAccount: $ledgerAccount) {

    # Query specific currency
    ownBalanceChange(
      period: "1969"
      currency: { code: USD }
    )

    # Query all currencies
    ownBalanceChanges(period: "1969") {
      nodes {
        currency {
          code
        }
        amount
      }
    }

  }
}

Balance change queries require you to specify a period. This can be a year, quarter, month, day or hour.

Over a period#

Use balancesDuring and balanceChangesDuring to track balances and balance changes over time. For multi-currency accounts, you can query either a specific currency or all currencies:

Balance history queries
query GetBalanceHistory(
  $ledgerAccount: LedgerAccountMatchInput!
) {
  ledgerAccount(ledgerAccount: $ledgerAccount) {

    # Query specific currency
    usdBalances: balancesDuring(
      startTime: "2021"
      duration: 12
      granularity: monthly
      currency: { code: USD }
    ) {
      nodes {
        at
        amount {
          amount
        }
      }
    }
    
    # Query all currencies
    allBalances: balancesDuring(
      startTime: "2021"
      duration: 12
      granularity: monthly
    ) {
      nodes {
        at
        amount {
          currency {
            code
          }
          amount
        }
      }
    }

    # Query specific currency changes
    usdChanges: balanceChangesDuring(
      startTime: "2021"
      duration: 100
      granularity: daily
      currency: { code: USD }
    ) {
      nodes {
        period
        amount {
          amount
        }
      }
    }

    # Query all currency changes
    allChanges: balanceChangesDuring(
      startTime: "2021"
      duration: 100
      granularity: daily
    ) {
      nodes {
        period
        amount {
          currency {
            code
          }
          amount
        }
      }
    }
  }
}

e. Custom currencies

#

You can define your own currencies to track any type of value, like rewards points, stocks or physical items.

To create a custom currency, call the createCustomCurrency mutation:

createCustomCurrency mutation
mutation CreateCustomCurrency (
    $customCurrency: CreateCustomCurrencyInput!,
) {
  createCustomCurrency(
    customCurrency: $customCurrency
  ) {
    ... on CreateCustomCurrencyResult {
      customCurrency {
        code
        customCurrencyId
        precision
        name
        customCode
      }
    }
    ... on Error {
      code
      message
    }
  }
}
createCustomCurrency variables
{
  "customCurrency": {
    "customCurrencyId": "blue-gems",
    "precision": 0,
    "name": "Blue Gems",
    "customCode": "BLUE"
  }
}

To use a custom currency, set the customCurrencyId on the currency field of a Ledger Account and Ledger Line:

Custom currency Ledger Account
{
  "key": "...",
  "chartOfAccounts": {
    "defaultCurrencyMode": "multi",
    "accounts": [
      {
        "key": "gems-issued",
        "currencyMode": "single",
        "currency": {
          "code": "CUSTOM",
          "customCurrencyId": "blue-gems"
        }
      },
      {...}
    ]
  },
  "ledgerEntries": {
    "types": [
      {
        "type": "issue-blue-gems",
        "description": "Issue blue gems",
        "lines": [
          {
            "key": "increase-pool",
            "account": { 
              "path": "gems-issued"
            },
            "amount": "{{amount}}",
            "currency": {
              "code": "CUSTOM",
              "customCurrencyId": "blue-gems"
            }
          },
          {...other line}
        ]
      }
    ]
  }
}