A demo that sends payments between 3 Interledger.rs nodes and settles using Ethereum transactions and XRP transactions.
This example shows how to configure Interledger.rs nodes and use an Ethereum network (testnet or mainnet) and the XRP Ledger testnet as settlement ledgers for payments sent between the nodes. If you are new to Ethereum, you can learn about it here. You'll find many useful resources about the XRP Ledger (XRPL) here. To learn about settlement in Interledger, refer to Peering, Clearing and Settling.
To run the full example, you can use run-md.sh
as described here. Otherwise, you can walk through each step below.
Each of the services write their logs to files found under the logs
directory. You can run tail -f logs/node_a.log
, for example, to watch the logs of Node A.
- Rust
- An Ethereum network to connect to
- XRP Settlement Engine
- Redis
Because Interledger.rs is written in the Rust language, you need the Rust environment. Refer to the Getting started page or just curl https://sh.rustup.rs -sSf | sh
and follow the instructions.
First, you need an Ethereum network. You can either use a local testnet, a remote testnet, or the mainnet.
For this example, we'll use ganache-cli which deploys a local Ethereum testnet at localhost:8545
. To install ganache-cli
, run npm install -g ganache-cli
. If you do not already have Node.js installed on your computer, you can follow the instructions below to install it.
Advanced: You can run this against the Rinkeby Testnet by running a node that connects to Rinkeby (e.g. geth --rinkeby --syncmode "light"
) or use a third-party node provider such as Infura. You must also create a wallet and then obtain funds via the Rinkeby Faucet.
(In case you don't have Node.js) There are a few ways to install Node.js. If you work on multiple JavaScript or TypeScript projects which require different node
versions, using nvm
may be suitable.
nvm
(node version manager)- Install independently
- macOS: If you use Homebrew, run
brew install node
- Ubuntu:
sudo apt-get install nodejs npm
- macOS: If you use Homebrew, run
Then you should be able to use npm
. To install ganache-cli
, run npm install -g ganache-cli
.
Interledger.rs and settlement engines written in other languages are fully interoperable. Here, we'll use the XRP Ledger Settlement Engine, which is written in TypeScript. We'll need node
and npm
to install and run the settlement engine. If you don't have it already, refer to Install Node.js.
Install the settlement engine as follows:
npm i -g ilp-settlement-xrp
(This makes the ilp-settlement-xrp
command available to your PATH.)
The Interledger.rs nodes and settlement engines currently use Redis to store their data (SQL database support coming soon!). Nodes and settlement engines can use different Redis instances.
- Compile and install from the source code
- Install using package managers
- Ubuntu: run
sudo apt-get install redis-server
- macOS: If you use Homebrew, run
brew install redis
- Ubuntu: run
Make sure your Redis is empty. You could run redis-cli flushall
to clear all the data.
First of all, we have to prepare interledger.rs
. You can either:
- Download compiled binaries
- Compile from the source code
Compiling the source code is relatively slow, so we recommend downloading the pre-built binaries unless you want to modify some part of the code.
We provide compiled binaries for
- Linux based OSs
- macOS
First, let's make a directory to install binaries.
mkdir -p ~/.interledger/bin
# Also write this line in .bash_profile etc if needed
export PATH=~/.interledger/bin:$PATH
pushd ~/.interledger/bin &>/dev/null
# install ilp-node
if [ ! -e "ilp-node" ]; then
curl -L https://github.com/interledger-rs/interledger-rs/releases/download/ilp-node-latest/ilp-node-x86_64-unknown-linux-musl.tar.gz | tar xzv
fi
# install ilp-cli
if [ ! -e "ilp-cli" ]; then
curl -L https://github.com/interledger-rs/interledger-rs/releases/download/ilp-cli-latest/ilp-cli-x86_64-unknown-linux-musl.tar.gz | tar xzv
fi
popd &>/dev/null
pushd ~/.interledger/bin &>/dev/null
# install ilp-node
if [ ! -e "ilp-node" ]; then
curl -L https://github.com/interledger-rs/interledger-rs/releases/download/ilp-node-latest/ilp-node-x86_64-apple-darwin.tar.gz | tar xzv -
fi
# install ilp-cli
if [ ! -e "ilp-cli" ]; then
curl -L https://github.com/interledger-rs/interledger-rs/releases/download/ilp-cli-latest/ilp-cli-x86_64-apple-darwin.tar.gz | tar xzv -
fi
popd &>/dev/null
If you would prefer compiling from the source code, compile interledger.rs and CLI as follows.
# These aliases make our command invocations more natural
# Be aware that we are using `--` to differentiate arguments for `cargo` from `ilp-node` or `ilp-cli`.
# Arguments before `--` are used for `cargo`, after are used for `ilp-node`.
alias ilp-node="cargo run --quiet --bin ilp-node --"
alias ilp-cli="cargo run --quiet --bin ilp-cli --"
cargo build --bin ilp-node --bin ilp-cli
# Create the logs directory if it doesn't already exist
mkdir -p logs
# Start Redis
redis-server --port 6379 &> logs/redis-a-node.log &
redis-server --port 6380 &> logs/redis-a-se-eth.log &
redis-server --port 6381 &> logs/redis-b-node.log &
redis-server --port 6382 &> logs/redis-b-se-eth.log &
redis-server --port 6383 &> logs/redis-b-se-xrp.log &
redis-server --port 6384 &> logs/redis-c-node.log &
redis-server --port 6385 &> logs/redis-c-se-xrp.log &
To remove all the data in Redis, you might additionally perform:
for port in `seq 6379 6385`; do
redis-cli -p $port flushall
done
When you want to watch logs, use the tail
command. You can use the command like: tail -f logs/redis-alice.log
This will launch an Ethereum testnet with 10 prefunded accounts. The mnemonic is used because we want to know the keys we'll use for Alice and Bob (otherwise they are randomized).
ganache-cli -m "abstract vacuum mammal awkward pudding scene penalty purchase dinner depart evoke puzzle" -i 1 &> logs/ganache.log &
In this example, we'll connect 3 Interledger nodes and each node needs its own settlement engine for each settlement ledger; We'll launch 4 settlement engines in total.
- A settlement engine for Alice to Bob on Ethereum
- To settle the balance of Bob's account on Alice's node (Port 3000)
- A settlement engine for Bob to Alice on Ethereum
- To settle the balance of Alice's account on Bob's node (Port 3001)
- A settlement engine for Bob to Charlie on XRPL
- To settle the balance of Charlie's account on Bob's node (Port 3002)
- A settlement engine for Charlie to Bob on XRPL
- To settle the balance of Bob's account on Charlie's node (Port 3003)
By default, the XRP settlement engine generates new testnet XRPL accounts prefunded with 1,000 testnet XRP (a new account is generated each run). Alternatively, you may supply an XRP_SECRET
environment variable by generating your own testnet credentials from the official faucet.
The engines are part of a separate repository so you have to install another binary or compile from the source code.
We also provide compiled binaries of settlement-engines
for
- Linux based OSs
- macOS
pushd ~/.interledger/bin &>/dev/null
if [ ! -e "ilp-settlement-ethereum" ]; then
curl -L https://github.com/interledger-rs/settlement-engines/releases/download/ilp-settlement-ethereum-latest/ilp-settlement-ethereum-x86_64-unknown-linux-musl.tar.gz | tar xzv
fi
popd &>/dev/null
pushd ~/.interledger/bin &>/dev/null
if [ ! -e "ilp-settlement-ethereum" ]; then
curl -L https://github.com/interledger-rs/settlement-engines/releases/download/ilp-settlement-ethereum-latest/ilp-settlement-ethereum-x86_64-apple-darwin.tar.gz | tar xzv -
fi
popd &>/dev/null
If you would prefer compiling from the source code and you've never cloned settlement-engines
, the first step would be to clone the repository.
# This should be done outside of the interledger-rs directory, otherwise it will cause an error.
git clone https://github.com/interledger-rs/settlement-engines
cd settlement-engines
Then install settlement-engines
as follows.
# This alias makes our command invocations more natural
alias ilp-settlement-ethereum="cargo run --quiet --bin ilp-settlement-ethereum --"
cargo build --bin ilp-settlement-ethereum
Then, spin up your settlement engines.
# Turn on debug logging for all of the interledger.rs components
export RUST_LOG=interledger=debug
mkdir -p logs
# Start Alice's settlement engine (ETH)
ilp-settlement-ethereum \
--private_key 380eb0f3d505f087e438eca80bc4df9a7faa24f868e69fc0440261a0fc0567dc \
--confirmations 0 \
--poll_frequency 1000 \
--ethereum_url http://127.0.0.1:8545 \
--connector_url http://127.0.0.1:7771 \
--redis_url redis://127.0.0.1:6380/ \
--asset_scale 6 \
--settlement_api_bind_address 127.0.0.1:3000 \
&> logs/node-alice-settlement-engine-eth.log &
# Start Bob's settlement engine (ETH, XRPL)
ilp-settlement-ethereum \
--private_key cc96601bc52293b53c4736a12af9130abf347669b3813f9ec4cafdf6991b087e \
--confirmations 0 \
--poll_frequency 1000 \
--ethereum_url http://127.0.0.1:8545 \
--connector_url http://127.0.0.1:8771 \
--redis_url redis://127.0.0.1:6382/ \
--asset_scale 6 \
--settlement_api_bind_address 127.0.0.1:3001 \
&> logs/node-bob-settlement-engine-eth.log &
DEBUG="settlement*" \
CONNECTOR_URL="http://localhost:8771" \
REDIS_URI=127.0.0.1:6383 \
ENGINE_PORT=3002 \
ilp-settlement-xrp \
&> logs/node-bob-settlement-engine-xrpl.log &
# Start Charlie's settlement engine (XRPL)
DEBUG="settlement*" \
CONNECTOR_URL="http://localhost:9771" \
REDIS_URI=127.0.0.1:6385 \
ENGINE_PORT=3003 \
ilp-settlement-xrp \
&> logs/node-charlie-settlement-engine-xrpl.log &
# Start nodes.
# Note that the configuration options can be passed as environment variables
# or saved to a YAML, JSON or TOML file and passed to the node as a positional argument.
# You can also pass it from STDIN.
# Start Alice's node
ilp-node \
--ilp_address example.alice \
--secret_seed 8852500887504328225458511465394229327394647958135038836332350604 \
--admin_auth_token hi_alice \
--redis_url redis://127.0.0.1:6379/ \
--http_bind_address 127.0.0.1:7770 \
--settlement_api_bind_address 127.0.0.1:7771 \
--exchange_rate.provider CoinCap \
&> logs/node-alice.log &
# Start Bob's node
ilp-node \
--ilp_address example.bob \
--secret_seed 1604966725982139900555208458637022875563691455429373719368053354 \
--admin_auth_token hi_bob \
--redis_url redis://127.0.0.1:6381/ \
--http_bind_address 127.0.0.1:8770 \
--settlement_api_bind_address 127.0.0.1:8771 \
--exchange_rate.provider CoinCap \
&> logs/node-bob.log &
# Start Charlie's node. The --ilp_address field is omitted, so the node's address will
# be `local.host`. When a parent account is added, Charlie's node will send an ILDCP request
# to them, which they will respond to with an address that they have assigned to Charlie.
# Charlie will then proceed to set that as their node's address.
# ILDCP documentation: https://interledger.org/rfcs/0031-dynamic-configuration-protocol/
ilp-node \
--secret_seed 1232362131122139900555208458637022875563691455429373719368053354 \
--admin_auth_token hi_charlie \
--redis_url redis://127.0.0.1:6384/ \
--http_bind_address 127.0.0.1:9770 \
--settlement_api_bind_address 127.0.0.1:9771 \
--exchange_rate.provider CoinCap \
&> logs/node-charlie.log &
# For authenticating to nodes, we can set credentials as an environment variable or a CLI argument
export ILP_CLI_API_AUTH=hi_alice
printf "Adding Alice's account...\n"
ilp-cli accounts create alice \
--ilp-address example.alice \
--asset-code ETH \
--asset-scale 6 \
--max-packet-amount 100 \
--ilp-over-http-incoming-token alice_password \
--settle-to 0 &> logs/account-alice-alice.log
printf "Adding Bob's account on Alice's node (ETH Peer relation)...\n"
ilp-cli accounts create bob \
--ilp-address example.bob \
--asset-code ETH \
--asset-scale 6 \
--max-packet-amount 100 \
--settlement-engine-url http://localhost:3000 \
--ilp-over-http-incoming-token bob_password \
--ilp-over-http-outgoing-token alice_password \
--ilp-over-http-url http://localhost:8770/accounts/alice/ilp \
--settle-threshold 500 \
--min-balance -1000 \
--settle-to 0 \
--routing-relation Peer &> logs/account-alice-bob.log &
printf "Adding Alice's account on Bob's node (ETH Peer relation)...\n"
ilp-cli --node http://localhost:8770 accounts create alice \
--auth hi_bob \
--ilp-address example.alice \
--asset-code ETH \
--asset-scale 6 \
--max-packet-amount 100 \
--settlement-engine-url http://localhost:3001 \
--ilp-over-http-incoming-token alice_password \
--ilp-over-http-outgoing-token bob_password \
--ilp-over-http-url http://localhost:7770/accounts/bob/ilp \
--settle-threshold 500 \
--min-balance -1000 \
--settle-to 0 \
--routing-relation Peer &> logs/account-bob-alice.log
printf "Adding Charlie's account on Bob's node (XRP Child relation)...\n"
# you can optionally provide --ilp-address example.bob.charlie as an argument,
# but the node is smart enough to figure it by itself
# Prefunds up to 1 XRP from Bob to Charlie, topped up after every packet is fulfilled
ilp-cli --node http://localhost:8770 accounts create charlie \
--auth hi_bob \
--asset-code XRP \
--asset-scale 6 \
--settlement-engine-url http://localhost:3002 \
--ilp-over-http-incoming-token charlie_password \
--ilp-over-http-outgoing-token bob_other_password \
--ilp-over-http-url http://localhost:9770/accounts/bob/ilp \
--settle-threshold 0 \
--settle-to -1000000 \
--min-balance -10000000 \
--routing-relation Child &> logs/account-bob-charlie.log &
printf "Adding Charlie's Account...\n"
# initially, Charlie gets added as local.host.charlie
ilp-cli --node http://localhost:9770 accounts create charlie \
--auth hi_charlie \
--asset-code XRP \
--asset-scale 6 \
--ilp-over-http-incoming-token charlie_password \
--settle-to 0 &> logs/account-charlie-charlie.log
printf "Adding Bob's account on Charlie's node (XRP Parent relation)...\n"
# Once a parent is added, Charlie's node address is updated to `example.bob.charlie,
# and then subsequently the addresses of all NonRoutingAccount and Child accounts get
# updated to ${NODE_ADDRESS}.${CHILD_USERNAME}, with the exception of the account whose
# username matches the suffix of the node's address.
# So in this case, Charlie's account address gets updated from local.host.charlie to example.bob.charlie
# If Charlie had another Child account saved, say `alice`, Alice's address would become
# `example.bob.charlie.alice`
ilp-cli --node http://localhost:9770 accounts create bob \
--auth hi_charlie \
--ilp-address example.bob \
--asset-code XRP \
--asset-scale 6 \
--settlement-engine-url http://localhost:3003 \
--ilp-over-http-incoming-token bob_other_password \
--ilp-over-http-outgoing-token charlie_password \
--ilp-over-http-url http://localhost:8770/accounts/charlie/ilp \
--settle-threshold 200000 \
--settle-to -1000000 \
--min-balance -10000000 \
--routing-relation Parent &> logs/account-charlie-bob.log
sleep 2
Now three nodes and its settlement engines are set and accounts for each node are also set up.
Notice how we use Alice's settlement engine endpoint while registering Bob. This means that whenever Alice interacts with Bob's account, she'll use that Settlement Engine. This could be also said for the other accounts on the other nodes.
The settle_threshold
and settle_to
parameters control when settlements are triggered. The node will send a settlement when an account's balance reaches the settle_threshold
, and it will settle for balance - settle_to
.
The following script sends a payment from Alice to Charlie through Bob.
ilp-cli pay alice --auth alice_password \
--amount 500 \
--to http://localhost:9770/accounts/charlie/spsp
You may see unsettled balances before the settlement engines exactly work. Wait a few seconds and try later.
If Bob's balance on Charlie's node is greater than 0, then Bob sent an XRP settlement to Charlie!
printf "\nAlice's balance on Alice's node: "
ilp-cli accounts balance alice
printf "Bob's balance on Alice's node: "
ilp-cli accounts balance bob
printf "Alice's balance on Bob's node: "
ilp-cli --node http://localhost:8770 accounts balance alice --auth hi_bob
printf "Charlie's balance on Bob's node: "
ilp-cli --node http://localhost:8770 accounts balance charlie --auth hi_bob
printf "Bob's balance on Charlie's node: "
ilp-cli --node http://localhost:9770 accounts balance bob --auth hi_charlie
printf "Charlie's balance on Charlie's node: "
ilp-cli --node http://localhost:9770 accounts balance charlie --auth hi_charlie
Finally, you can stop all the services as follows:
for port in `seq 6379 6385`; do
if lsof -Pi :${port} -sTCP:LISTEN -t >/dev/null ; then
redis-cli -p ${port} shutdown
fi
done
if [ -f dump.rdb ] ; then
rm -f dump.rdb
fi
for port in 8545 7770 8770 9770 3000 3001 3002 3003; do
if lsof -tPi :${port} >/dev/null ; then
kill `lsof -tPi :${port}`
fi
done
To check whether the settlement block is generated, we use geth
. geth
is the abbreviation of go-ethereum
which is an Ethereum client written in the go language. If you don't already have geth
, refer to the following.
- Compile and install from the source code
- Refer to Building Ethereum page.
- Install using package managers
- Ubuntu: Follow the instructions here.
- macOS: If you use Homebrew, run
brew tap ethereum/ethereum
andbrew install ethereum
. Details are found here. - others: Refer to Building Ethereum page.
Then dump transaction logs as follows. You will see generated block information. Be aware that ganache takes 10 to 20 seconds to generate a block. So you will have to wait for it before you check with geth
.
printf "Last block: "
geth --exec "eth.getTransaction(eth.getBlock(eth.blockNumber-1).transactions[0])" attach http://localhost:8545 2>/dev/null
printf "\nCurrent block: "
geth --exec "eth.getTransaction(eth.getBlock(eth.blockNumber).transactions[0])" attach http://localhost:8545 2>/dev/null
If you inspect ganache-cli
's output, you will notice that the block number has increased as a result of the settlement executions as well.
You'll find incoming settlement logs in your settlement engine logs. Try:
cat logs/node-charlie-settlement-engine-xrpl.log | grep "Received incoming XRP payment"
# When installing ganache-cli
gyp ERR! find Python Python is not set from command line or npm configuration
gyp ERR! find Python Python is not set from environment variable PYTHON
gyp ERR! find Python checking if "python" can be used
gyp ERR! find Python - executable path is "/Users/xxxx/anaconda3/bin/python"
gyp ERR! find Python - version is "3.6.2"
gyp ERR! find Python - version is 3.6.2 - should be >=2.6.0 <3.0.0
gyp ERR! find Python - THIS VERSION OF PYTHON IS NOT SUPPORTED
gyp ERR! find Python checking if "python2" can be used
gyp ERR! find Python - "python2" is not in PATH or produced an error
If you see an error like the above, you have to install Python 2.7.
# When installing Node.js with apt-get
E: Unable to locate package nodejs
E: Unable to locate package npm
Try sudo apt-get update
.
# When you try run-md.sh
Fatal: Failed to start the JavaScript console: api modules: Post http://localhost:8545: context deadline exceeded
It seems that you failed to install ganache-cli
. Try to install it.
This example showed an SPSP payment sent between three Interledger.rs nodes that settled using on-ledger Ethereum and XRPL transactions.
More examples that enhance your integration with ILP are coming soon!