Rafiki uses double-entry accounting to record financial transactions. In this method of bookkeeping, a transaction recorded to one account results in an equal and opposite entry to another account. For example, a $50 credit to one account results in a $50 debit from another account.
Transactions in Rafiki represent Interledger packet interactions, denominated in a given asset. Packet interactions can be successful, fail, or be rejected. Rafiki’s accounting layer processes the interactions and converts the activities into financial records, which are then written to your accounting database.
Accounts within Rafiki are your internal liquidity and settlement accounts used to fund payments, not the accounts that you service for your customers. This distinction is crucial for understanding how Rafiki handles transactions and settlements.
An asset represents a transferrable item of value. Although the Interledger Protocol (ILP) supports the transfer of any asset deemed to have value, assets are generally denominated in a currency. For example fiat currencies, central bank digital currencies, and branded currencies (such as merchant reward points).
Rafiki uses a combination of liquidity and settlement accounts to track the amounts available to fund transactions. Rafiki does not physically hold funds in each account. Instead, it uses double-entry accounting to record the transactions. The actual settlement of amounts owed, in which funds are physically exchanged, occurs outside of both Rafiki and the Interledger Protocol.
Liquidity accounts track deposits, withdrawals, and transfers that occur during the course of a transaction. Rafiki provides liquidity accounts for assets, peers, and payments.
Liquidity accounts hold either a zero or a positive balance. Rafiki ensures that the total debits to a liquidity account will not exceed the account’s total credits.
Asset liquidity ensures Rafiki has enough liquidity, denominated in a given asset, to handle cross-currency (foreign exchange) transactions.
An asset liquidity account represents the value that Rafiki has available for sending or forwarding ILP packets. You have one asset liquidity account for each asset you transact in.
Whenever an outgoing payment/incoming payment is in a different asset than the peering relationship, the liquidity of asset accounts change depending on the FX direction. Any transaction that would result in a negative balance will fail.
You can add a liquidity threshold for each asset liquidity account via the updateAsset mutation’s liquidityThreshold input argument.
When a threshold is entered, the asset.liquidity_low webhook event notifies you if an asset account’s liquidity drops below the threshold.
You should define and adjust asset liquidity based on your liquidity risk profile. You can deposit or withdraw asset liquidity as needed through Rafiki Admin or by using the Backend Admin API.
Asset liquidity example - cross-currency transactions
Your Rafiki instance is configured for two assets: EUR and USD.
Rafiki holds an asset liquidity account for both EUR and USD.
You’ve set the asset scale of both currencies to 0.
Your starting EUR liquidity is 10 and your USD liquidity is 50.
Cross-currency transaction #1:
Rafiki receives packets from a peer. These packets are all denominated in EUR, worth €10. 10 EUR move from the peer’s liquidity account on your Rafiki instance to your EUR asset liquidity account. Your EUR liquidity increases to 20 (10 + 10).
The EUR-to-USD exchange rate is applied, with €10 equating to $12 USD. Since your starting USD liquidity is 50, your USD asset liquidity account can cover the transfer of $12 USD to an incoming payment liquidity account. Your USD liquidity decreases to 38 (50 - 12).
Cross-currency transaction #2:
Rafiki receives packets from a peer. These packets are all denominated in EUR, worth €50. Your EUR liquidity increases to 70 (20 + 50).
The current EUR-to-USD exchange rate is applied, with €50 equating to $55 USD. The transaction fails. Your USD liquidity account is 38, so you don’t have enough liquidity to cover the transaction.
Peer liquidity is the credit line you’ve extended to a peer. A peer liquidity account represents the amount of the line of credit that the peer still has available to them. You have one liquidity account for each peer and the account is denominated in the asset you both agreed to transact in.
The amount of credit that you extend to a peer, the asset that you transact in, and the mechanism you use to settle are just a few items that should be defined in your respective peering agreements.
If a peer’s liquidity is insufficient (for example, they’ve used up their allotted credit line), payments will not be processed. Your peer should settle with you so that you can reset their liquidity.
You can add a liquidity threshold for each peer liquidity account via the updatePeer mutation’s liquidityThreshold input argument.
When a threshold is entered, the peer.liquidity_low webhook event notifies you if a peer’s liquidity drops below the threshold.
You should define and adjust each peer’s liquidity based on your liquidity risk profile. You can deposit or withdraw peer liquidity as needed through Rafiki Admin or by using the Backend Admin API.
Peer liquidity example
You and Cloud Nine Wallet are peers. You’ve agreed to extend Cloud Nine Wallet a line of credit worth $100.00 USD. This means Cloud Nine Wallet has $100.00 in their peer liquidity account on your Rafiki instance. Your Rafiki instance can receive packets that total up to $100.00 from Cloud Nine Wallet. When the $100.00 is used up, Cloud Nine Wallet settles with you by sending $100.00 via the shared settlement mechanism outlined in your peering agreement. When you receive the funds, you reset their liquidity in Rafiki.
An incoming payment liquidity account represents the value received for an incoming payment. Incoming payments are created via Open Payments. When the first packet for an incoming payment is received, a corresponding liquidity account is automatically created. You will have one liquidity account per incoming payment.
You are notified of created, completed, and expired incoming payments by listening for the appropriate webhook events. Since Rafiki doesn’t hold funds, anything you receive must be withdrawn and then credited to the recipient’s account on your ledger.
The liquidity account isn’t used again after the payment completes, but its record remains in your accounting database. When a new incoming payment occurs, a new liquidity account is created.
An outgoing payment liquidity account represents the value available to send in an outgoing payment. When an outgoing payment is created via Open Payments, a corresponding liquidity account is automatically created. You will have one liquidity account per outgoing payment.
You are notified of created, completed, and failed outgoing payments by listening for the appropriate webhook events. Liquidity must be deposited into the outgoing payment account before the payment can be processed.
You may occasionally have excess liquidity, such as when an outgoing payment only partially completes and a portion of the send-amount remains. Since Rafiki doesn’t hold funds, any excess liquidity that remains after an outgoing payment completes must be withdrawn from the outgoing payment liquidity account. How you choose to handle the excess is up to you. You could, for example, refund the excess to the sender or take the amount as a fee.
The account isn’t used again after the payment completes, but its record remains in your accounting database. When a new outgoing payment is created, a new liquidity account is created.
A wallet address liquidity account contains the value received to a wallet address via SPSP. When an incoming payment is created, a corresponding liquidity account is automatically created. You will have one account per wallet address.
Since Rafiki doesn’t hold funds, you must withdraw the liquidity when the payment completes and credit the funds to the recipient’s account on your ledger. You are notified to withdraw liquidity by listening for the appropriate webhook event.
Unlike the incoming and outgoing payment liquidity accounts, the same wallet address liquidity account will be used for future incoming SPSP payments.
A settlement account represents the total funds, denominated in a single asset, that you have deposited into Rafiki. You have one settlement account for each asset you transact in.
Settlement accounts hold either a zero or a negative balance. A negative balance on a settlement account means you’ve deposited more funds into Rafiki than you’ve withdrawn. The closer a settlement account’s balance is to 0, the more likely it is you need to settle with your peer for the amount owed and then deposit the amount back into Rafiki.
Rafiki ensures that the total credits to a settlement account do not exceed its total debits.
Settlement account example
You deposit $10,000 into a peer’s liquidity account, meaning you’ve extended a credit line of $10,000 to your peer.
Your peer liquidity account balance is $10,000 and your USD settlement account balance is now -$10,000.
An incoming payment from your peer for $100 is created, meaning your peer is using $100 of their line of credit. Since Rafiki doesn’t hold funds, you must withdraw the liquidity and credit the amount to the recipient’s account on your ledger.
Now, your peer liquidity account’s balance is $9,900 and your USD settlement account’s balance is -$9,900.
TigerBeetle is a high-performance distributed financial accounting database used by Rafiki’s backend service to store account balance data. Both liquidity and settlement accounts in Rafiki correspond to TigerBeetle credit and debit accounts, respectively.
TigerBeetle only holds balance data without any additional ILP packet metadata. For detailed information on TigerBeetle, including its consensus mechanism and its limitations, visit the official TigerBeetle documentation and blog. For more information about TigerBeetle in a production Rafiki environment, see Running Rafiki in production.
You can choose to use a separate Postgres database for accounting instead of using TigerBeetle. However, TigerBeetle is recommended due to its speed, efficiency, and dedicated design for handling double-entry/double-ledger accounting.
As with the accounts described above, Rafiki performs double-entry accounting for transfers, where increasing the total debits of one account increases the total credits of another account by the same amount, and vice versa.
Transfers can complete in either a single phase or in two phases.
Example: Sender creates an outgoing payment for 100 USD to an incoming payment at a peer’s Rafiki instance. The peering relationship is in EUR, so the payment is converted on the sending side.
Example: A Rafiki instance receives 10 USD from a peer (peering relationship in USD) to be deposited in an incoming payment liquidity account denominated in EUR. The payment is converted to EUR and deposited.