Making cross-ledger payments
Date | Responsible | Changes |
---|---|---|
December 20, 2022 | @Željko Rumenjak | Initial version |
January 19, 2023 | @Željko Rumenjak | Updated to use test banks |
January 22, 2023 | @Željko Rumenjak | Added video summaries |
February 1, 2023 | @Tomislav Herman | Changed CLI question title Handle → Core Id when confirming prepare/commit/abort |
February 27, 2023 | @Omar Monterrey | Added Version to Ledger URL |
March 7, 2023 | @Filip Herceg | • Changed the URL to ldg-stg.one • Changed bearer action to read |
March 13, 2023 | @Filip Herceg | Using placeholders for Ledger URL when connecting and explained the format of this URL. |
March 13, 2023 | @Tomislav Herman | Renamed in text: event → request and operation → request . Updated CLI output in some cases. |
May 10, 2023 | @Omar Monterrey | Removed q to quit from CLI example since pagination for cli is not shown by default anymore. |
September 18, 2023 | @Luis Fidelis | • Added schema selection for intents and wallets. • Added policy selection for intents and wallets. • Added intermediary steps about ledger login . |
April 25, 2024 | @Luis Fidelis | Removed references to Ngrok. |
June 5, 2024 | @Omar Monterrey | Updated signer create example with latest UX |
Introduction
In this tutorial you are going to build a bridge that connects to a Minka Ledger in the cloud.
The ledger in the cloud represents an automated clearinghouse enabling real time payments and allows for a simple and easy clearing and reconciliation process between the participants.
The cloud ledger supports advanced concepts like intent chains and routing which allows us to model alias resolution flows directly on the ledger.
This makes it much simpler to onboard and connect third parties to the system. This approach also avoids errors that could happen because of incorrect implementations on the client side when alias resolution is implemented by clients.
As part of this tutorial we will define and implement two main financial processes required for our RTP use case:
- setting up the environment
- sending money
- executing upload or debit operation
- executing download or credit operation
- third party integration
Clearing is a process of exchanging messages about debt (IOUs) between the participants of a system. Clearing is time sensitive and in our case needs to happen in real time and has to be automated. The target user can access funds as soon as clearing process finishes, with target to be less than one second.
Settlement involves the interbank exchange of funds, usually it is done by balance movements between accounts in an external system, a central bank or another payments network.
You can learn more about clearing and settlement here.
Getting started
We will follow through this tutorial by using Minka CLI tool to simulate all interactions with the ledger. This is the simplest way to understand all the actions that need to be performed and see how ledger operates.
In order to follow this tutorial you have to have nodeJS v18 or newer installed on your machine. This tutorial uses a Minka Ledger, so our first task is to connect to it, for this we will use the Minka CLI tool.
Install the Minka CLI through your terminal:
After installing the CLI tool, you will be able to interact with local or remote ledger instances by using the minka
command. Checkout all the commands available by typing minka --help
.
After we have installed a CLI, we can connect to a ledger by typing:
Now we have everything ready to start tracking balances in the ledger.
If you haven’t received a Ledger URL, you can ask responsible person to send it to you.
Your first payment intent
Balance movements are represented as payment intents in ledger. We are now going to make our first intent by using wallets that are already configured in the ledger.
tesla
and minka
are test banks already available in the ledger. Those banks are secured by keys stored in cloud. Passwords for decrypting those keys are the same as bank names to ease testing.
This is how we can initiate a payment intent from an account in tesla
bank to an account in minka
bank:
After creating an intent, we can check that it completed and check the balances of bank wallets.
Beforehand we need to login to the ledger with a participant of this intent to be able to read it , either tesla
or minka
signer. Let's use tesla
for this:
Now we are able to see the balances of tesla
wallet
Note that we are not allowed to see minka
balances when logged in to ledger with tesla
signer. So, let's login with tesla
to proceed
Video summary of CLI operations
Setting up a bank
Next, we will start building our own integration with a banking core. We are going to call our bank mint
for the purposes of this tutorial.
Use a unique name that isn’t already registered with the ledger for your bank and use that name instead of mint
. To remind you of this mint
is going to be marked in yellow throughout the tutorial.
First step is to create a signer for our new bank:
Signers represent keys for signing all ledger operations. In this case we are storing the signer in ledger, which means that signer keys are kept encrypted with the given password in the ledger.
Now that we have our signer, we should add this signer to the circle bank
because all the signers from circle bank
are allowed to create wallets in the ledger
We can create a wallet that is going to be used to track settlement balances of our new bank:
Wallets hold balances in ledger, the purpose of this wallet is to track aggregated balance for all bank transaction to simplify the settlement process.
We can check the wallets in the system by logging to the ledger with ach
signer and using the following commands:
Signers from bank
are able to see their own wallets only, on the other hand signers from circle ach
are able to see all the wallets in the ledger.
We have created our signer and wallet, but we still don’t have any balance, the last step is to acquire balance from another participant in the system. Usually this is done by an ACH or a similar system operator:
Transfers like the one we made above are usually done after a business agreement is made and required funds are transferred through a central bank or a similar trusted financial entity.
We now have funds in our wallet and can start making transactions with other participants of the system, we can check our balance the same way as we did earlier:
Video summary of CLI operations
Connecting a banking core
External transactional systems connect to ledger by building an integration layer called bridge. When a bridge is assigned to a wallet, ledger will require confirmation from the bridge for each balance movement on the wallet. This allows us to reliably mirror all balance movements to external ledgers like existing banking cores or similar.
Minka CLI has a feature that enables you to simulate a remote bridge implementation. You can run a local server that listens to requests sent by ledger and react to those requests by providing required signatures. This allows you to get detailed insights in how ledger and bridge interactions are supposed to work:
CLI starts a local server and registers it with ledger automatically by creating a bridge record.
Leave the bridge running and continue the tutorial in a new terminal.
After our bridge has been registered with ledger we can assign it to our wallet:
After making this change, ledger is going to contact our bridge to confirm any debit or credit from our bank wallet. We will see how this works in the following chapters. We can check that our wallet has a bridge set by getting it from ledger:
Response object now contains two new fields: bridge
and parent
. Parent is a pointer to the previous version of our wallet which allows us to keep track of all system changes.
Video summary of CLI operations
Processing core transactions
The next thing we will explore is how debits and credits are handled on the bridge. We can trigger a debit on our local bridge by sending funds from our wallet:
The account number in real world implementations would be verified by participating bank bridges. You can use any account numbers in these examples since we are not doing any validations in our bridge.
Ledger detects that a source wallet of this balance movement has a bridge assigned and it sends a prepare debit request to it. We can see this in the terminal window that is running our bridge:
As confirmation, bridge sends a signature to ledger that contains a unique coreId
(transaction reference) of the operation performed by the bridge.
The entire intent payload is delivered to the bridge as part of this request as well for verification purposes.
This request is part of a two phase commit protocol that ledger uses in order to ensure that all participants in a distributed transaction correctly perform their responsibilities. Confirmations sent to ledger must be the signatures made with a private key registered with the bridge and contain a transaction reference of the operation performed by the bridge as evidence.
Errors are handled in a similar way, a signature with error details must be sent to the ledger. In case the bridge is down, ledger will retry requests.
Each request sent by the ledger has a unique id which serves as an idempotency token to prevent double operations. This id is sent in the handle
field of the incoming request.
The prepare request should reserve funds on the bank side, but the entry isn’t considered completed until a commit or abort request is received from the ledger.
After the target bank processes the prepare credit request, ledger will deliver a commit request for debit to the bridge:
Returned coreId
for this request can be the same as in prepare, if no new transaction was performed by the bank, or a new coreId
in case another transaction was performed.
Commit phase starts after all intent preconditions are fulfilled. To summarize, two things are important to understand about two phase commit protocol:
- Prepare requests are not final, they can still be reverted by the ledger
- If a commit of a prepared request is later sent by the ledger, it must succeed. Commit after a successful prepare must not fail, ledger will retry it until it succeeds.
Banks can implement this step in various ways. It can be a reservation of funds in the banking core or a transaction that could be reversed in case the entry is aborted.
We can check that everything is completed successfully by checking the intent status and our balance:
Video summary of CLI operations
Alias directory and routing
Alias addresses are modeled in ledger using wallets as well. Alias wallets have a schema that defines the type of alias which the wallet represents. Another ledger feature that is often used for alias directories are wallet routes. Routes allow us to automatically configure routing of funds to linked accounts.
We will now create a phone alias account with a route to a bank account in our mint
bank:
Route filters can target any field of the incoming payment. In the example above, we are forwarding all incoming payments in usd
to account:1001001212@mint
.
With this in place, we can now send money to an alias address and ledger will automatically route the payment to a configured target:
If everything is configured correctly, we will now receive prepare credit and commit credit requests to our mint
bridge:
You may notice that intent handle is different from the original intent we created. Ledger automatically creates a new intent to route the funds to final destination. The reference to the original intent is stored in the origin
property of the newly created intent.
Summary of intents involved in this operation
We can again check that everything is completed successfully by checking the intent status and our balance:
Video summary of CLI operations
Conclusion
With this tutorial we have demonstrated interactions between remote systems, for example separate banking cores, and the ledger which is running an alias directory.
This tutorial shows only a successful flow, to learn more about building a production ready implementation which covers all the cases please see our how to guides and references on core ledger concepts.
For reference, here is a summary of intents performed as part of this tutorial:
Tutorial intents summary