L 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. To read all balances in all currencies, query the plural versions of the singular balance field.

Latest#

You can read the latest balance in a specific currency or list the latest balance in all currencies.

Multi-currency Ledger Accounts have three balance lists:

  • ownBalances, the sum of all Ledger Lines in the Ledger Account per currency, excluding Ledger Lines in child Ledger Accounts
  • childBalances, the sum of all Ledger Lines in child Ledger Accounts per currency
  • balances, the sum of all Ledger Lines, including child Ledger Accounts per currency

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

Get USD balance read
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 balances read
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) {

    childBalance(currency: { code: USD })
    childBalances {
      nodes {
        currency {
          code
        }
        amount
      }
    }

    balance(currency: { code: USD })
    balances {
      nodes {
        currency {
          code
        }
        amount
      }
    }

  }
}

If any 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#

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) {
    ownBalance(
      consistencyMode: strong
      currency: { code: USD }
    )
    ownBalances(consistencyMode: strong) {
      nodes {
        currency {
          code
        }
        amount
      }
    }
  }
}
Historical#

To read historical balances for multi-currency Ledger Accounts, pass in the at argument:

Get historical balances read
query GetOldBalances(
  $ledgerAccount: LedgerAccountMatchInput!
) {
  ledgerAccount(ledgerAccount: $ledgerAccount) {
    ownBalance(at: "1969", currency: { code: USD })
    ownBalances(at: "1969") {
      nodes {
        currency {
          code
        }
        amount
      }
    }
  }
}
Balance changes#

Multi-currency Ledger Accounts support reading balance changes:

  • 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) {
    
    ownBalanceChange(
      period: "1969"
      currency: { code: USD }
    )
    ownBalanceChanges(period: "1969") {
      nodes {
        currency {
          code
        }
        amount
      }
    }

    childBalanceChange(
      period: "1968-12"
      currency: { code: USD }
    )
    childBalanceChanges(period: "1968-12") {
      nodes {
        currency {
          code
        }
        amount
      }
    }

    balanceChange(
      period: "1968-12-25"
      currency: { code: USD }
    )
    balanceChanges(period: "1968-12-25") {
      nodes {
        currency {
          code
        }
        amount
      }
    }
      
  }
}

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

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}
        ]
      }
    ]
  }
}