├── .eslintrc ├── .npmignore ├── .gitignore ├── .npmrc ├── docs └── assets │ ├── cheque.jpg │ ├── screencast.gif │ └── screenshot.png ├── src ├── services │ ├── demo.js │ └── config.js └── lib │ └── demo.js ├── index.js ├── data └── README.md ├── CONTRIBUTING.md ├── LICENSE ├── package.json └── README.md /.eslintrc: -------------------------------------------------------------------------------- 1 | extends: standard 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | 3 | .npmrc 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /data/*.sqlite 3 | /data/*.sqlite-* 4 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | //registry.npmjs.org/:_authToken=4e5ad48e-80fd-4170-995c-1a2019c919a5 2 | -------------------------------------------------------------------------------- /docs/assets/cheque.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interledger-deprecated/five-bells-demo/HEAD/docs/assets/cheque.jpg -------------------------------------------------------------------------------- /docs/assets/screencast.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interledger-deprecated/five-bells-demo/HEAD/docs/assets/screencast.gif -------------------------------------------------------------------------------- /docs/assets/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interledger-deprecated/five-bells-demo/HEAD/docs/assets/screenshot.png -------------------------------------------------------------------------------- /src/services/demo.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Demo = require('../lib/demo').Demo 4 | const config = require('./config') 5 | 6 | module.exports = new Demo(config.graph) 7 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const demo = require('./src/services/demo') 4 | 5 | demo.start().catch(function (err) { 6 | console.error(err.stack) 7 | process.exit(1) 8 | }) 9 | -------------------------------------------------------------------------------- /data/README.md: -------------------------------------------------------------------------------- 1 | # Data 2 | 3 | This folder is reserved for data files created by the running demo. These files will automatically be deleted on every run. 4 | 5 | Currently, if these files are not deleted between runs, ledgers will have connector accounts from previous runs which are no longer active. These can impair or slow down the pathfinding. 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Interledger.js Repositories 2 | 3 | ## Getting Involved 4 | 5 | Detailed contribution guidelines are coming soon... 6 | 7 | ## Contributor License Agreement 8 | 9 | This project uses the [JS Foundation](https://js.foundation) CLA for licensing contributions. 10 | 11 | Please submit your pull request as you would with any other open source project on GitHub and our CLA Assistant bot will guide you through the signing process. 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016 JS Foundation and contributors 2 | Copyright 2015-2016 Ripple, Inc. and contributors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 5 | this file except in compliance with the License. You may obtain a copy of the 6 | License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 11 | -------------------------------------------------------------------------------- /src/services/config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | exports.graph = { 4 | numLedgers: 8, 5 | numConnectors: 7, 6 | barabasiAlbertConnectedCore: 2, 7 | barabasiAlbertConnectionsPerNewNode: 2, 8 | adminUser: process.env.ADMIN_USER || 'admin', 9 | adminPass: process.env.ADMIN_PASS || 'admin' 10 | } 11 | 12 | if (process.env.DEMO_NUM_LEDGERS) { 13 | exports.graph.numLedgers = parseInt(process.env.DEMO_NUM_LEDGERS, 10) 14 | } 15 | 16 | if (process.env.DEMO_NUM_CONNECTORS) { 17 | exports.graph.numConnectors = parseInt(process.env.DEMO_NUM_CONNECTORS, 10) 18 | } 19 | 20 | // A higher number here will result in more highly connected central ledgers 21 | if (process.env.DEMO_CONNECTED_CORE) { 22 | exports.graph.barabasiAlbertConnectedCore = parseInt(process.env.DEMO_CONNECTED_CORE, 10) 23 | } 24 | 25 | // A higher number here will result in more connections between all ledgers 26 | if (process.env.DEMO_CONNECTIONS_PER_NEW_NODE) { 27 | exports.graph.barabasiAlbertConnectionsPerNewNode = parseInt(process.env.DEMO_CONNECTIONS_PER_NEW_NODE, 10) 28 | } 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "five-bells-demo", 3 | "version": "1.4.1", 4 | "author": "Ripple ", 5 | "description": "Demo of Five Bells payments", 6 | "keywords": [ 7 | "interledger", 8 | "five-bells", 9 | "ilp" 10 | ], 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/interledgerjs/five-bells-demo.git" 14 | }, 15 | "scripts": { 16 | "lint": "eslint .", 17 | "start": "rm -f data/*.sqlite ; node index.js" 18 | }, 19 | "license": "Apache-2.0", 20 | "engines": { 21 | "node": ">=4.0.0" 22 | }, 23 | "dependencies": { 24 | "co": "^4.6.0", 25 | "co-request": "^1.0.0", 26 | "five-bells-ledger": "~19.4.0", 27 | "five-bells-service-manager": "~5.3.0", 28 | "five-bells-visualization": "~7.1.0", 29 | "ilp": "~7.1.0", 30 | "ilp-connector": "~12.4.3", 31 | "ilp-plugin-bells": "^9.2.1", 32 | "lodash": "^4.11.1", 33 | "multiplexer": "^1.4.1", 34 | "randomgraph": "^0.1.3", 35 | "sqlite3": "^3.1.3" 36 | }, 37 | "devDependencies": { 38 | "eslint": "^3.11.1", 39 | "eslint-config-standard": "^6.2.1", 40 | "eslint-plugin-promise": "^3.4.0", 41 | "eslint-plugin-standard": "^2.0.1" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Five Bells Demo 2 | 3 | > This is a demo of the [Interledger](https://interledger.org) protocol. It allows you to very quickly spin up a complete Interledger network and do test transactions. 4 | > 5 | > Please note that this reference code is intended for TESTING USE ONLY. Do not use with real funds. 6 | 7 | ![Screen recording of the demo in action](docs/assets/screencast.gif) 8 | 9 | ## Running the Demo 10 | 11 | ### Prerequisites 12 | 13 | In order to try the Five Bells Demo, you need to make sure you have the following: 14 | 15 | * [Git](https://git-scm.com/) 16 | * [Node.js](https://nodejs.org) (Version 6.x.x recommended) 17 | 18 | ### Step 1: Clone demo 19 | 20 | ``` sh 21 | git clone https://github.com/interledgerjs/five-bells-demo.git 22 | cd five-bells-demo 23 | ``` 24 | 25 | ### Step 2: Install dependencies 26 | 27 | ``` sh 28 | npm install --only=prod --no-optional 29 | ``` 30 | 31 | ### Step 3: Run it! 32 | 33 | ``` sh 34 | npm start 35 | ``` 36 | 37 | Visit [`http://localhost:5000`](http://localhost:5000) to see it in action! It should look something like this: 38 | 39 | ![Demo Screenshot showing circles representing ledgers connected by lines between them representing connectors](docs/assets/screenshot.png) 40 | 41 | When you click a ledger and then click another ledger, you'll see a transaction executing. 42 | 43 | ### What am I looking at? 44 | 45 | [Interledger Protocol (ILP)](https://interledger.org) is a protocol for making payments across different ledgers. This demo will create a bunch of ledgers (each one running in a separate Node.js process) on your local machine and then generate connect them using a bunch of randomly generated connectors. The connectors will use ILP routing to discover each other. 46 | 47 | The demo also creates test sender accounts ("alice") and test receiver accounts ("bob") on each ledger. When you click two ledgers, an ILP sender will be created, which authenticates as Alice on the first ledger. This sender will then send to "Bob" on the second ledger you clicked. 48 | 49 | When sending a payment on Interledger, the sender will first query the receiver using the SPSP protocol to determine the properties of the receiver and what types of payments are possible. They will then request an Interactive Payment Request (IPR) which describes the payment they are about to do. Next, they will ask their connector (think: router) for a quote. The connector may recurse and ask other connectors. Finally, the connector will return a description of a local transfer. This is a local transfer of funds from the sender to the connector on the sender's ledger. All of this happens invisibly within a split second. However, you can see most of these steps in the (fairly verbose) log of the demo. 50 | 51 | If the sender is happy with the quoted rate (and the demo sender is always happy), she simply prepares the described local transfer. This means that her money is now on hold and the connector is notified. The connector then prepares a transfer (puts money on hold) on the next ledger and so on. This is shown in the demo by an orange bubble with the text "prepared" hovering over each ledger. When this chain reaches the recipient, they produce a receipt which triggers the execution in reverse order. This is shown in the demo by a green bubble with the text "executed" hovering over each ledger. 52 | 53 | When an error occurs, transfers may be rolled back after a timeout. This is shown in the demo by a red bubble with the text "rejected" hovering over each ledger. 54 | 55 | ### Configuration 56 | 57 | * `DEMO_NUM_LEDGERS` - Number of [`five-bells-ledger`](https://github.com/interledgerjs/five-bells-ledger) processes to start 58 | * `DEMO_NUM_CONNECTORS` - Number of [`five-bells-connector`](https://github.com/interledgerjs/five-bells-connector) processes to start 59 | * `DEMO_CONNECTED_CORE` - How connected the core ledgers in the generated graph should be (default: 2) 60 | * `DEMO_CONNECTIONS_PER_NEW_NODE` - How many connections each ledger will be added with (default: 2, must be <= `DEMO_CONNECTED_CORE`) 61 | 62 | ### Warnings 63 | 64 | When running the demo you will get a few warnings because components start up one by one and are trying to find each other. 65 | 66 | ## Components 67 | 68 | This demo uses the following modules: 69 | 70 | * [`five-bells-ledger`](https://github.com/interledgerjs/five-bells-ledger): Used as a ledger 71 | * [`ilp-connector`](https://github.com/interledgerjs/five-bells-connector): Used for routing payments across ledgers 72 | * [`ilp`](https://github.com/interledgerjs/ilp): Used as a sender and receiver component 73 | * [`ilp-plugin-bells`](https://github.com/interledgerjs/ilp-plugin-bells): Plugin for talking to five-bells-ledger 74 | * [`five-bells-visualization`](https://github.com/interledgerjs/five-bells-visualization): UI component to visualize what is happening 75 | 76 | ## Why "Five Bells"? 77 | 78 | Legend (i.e. [Wikipedia](https://en.wikipedia.org/wiki/Bankers_clearing_house)) has it that before 1770, checks/cheques were cleared by clerks running between banks exchanging checks for cash. 79 | 80 | ![Cheque from 1659](docs/assets/cheque.jpg) 81 | 82 | One day, two of the clerks from London banks recognized one another in the Five Bells Tavern on Lombard Street. The clerks started meeting daily at the Five Bells to clear checks, determine the banks' net positions and settle the remaining balances. 83 | 84 | In the early 1800s, the group of clerks outgrew their space at the Five Bells and moved across the street to a dedicated building where check clearing would take place until the early 2000s. 85 | -------------------------------------------------------------------------------- /src/lib/demo.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const co = require('co') 4 | const path = require('path') 5 | const randomgraph = require('randomgraph') 6 | const ServiceManager = require('five-bells-service-manager') 7 | 8 | const connectorNames = [ 9 | 'mark', 'mary', 'martin', 'millie', 10 | 'mia', 'mike', 'mesrop', 'michelle', 11 | 'milo', 'miles', 'michael', 'micah', 'max' 12 | ] 13 | 14 | const currencies = [ 15 | { code: 'AUD', symbol: 'A$' }, 16 | { code: 'BGN', symbol: 'лв' }, 17 | { code: 'BRL', symbol: 'R$' }, 18 | { code: 'CAD', symbol: 'C$' }, 19 | { code: 'CHF', symbol: 'Fr.' }, 20 | { code: 'CNY', symbol: '¥' }, 21 | { code: 'CZK', symbol: 'Kč' }, 22 | { code: 'DKK', symbol: 'kr.' }, 23 | { code: 'EUR', symbol: '€' }, 24 | { code: 'GBP', symbol: '£' }, 25 | { code: 'HKD', symbol: 'HK$' }, 26 | { code: 'HRK', symbol: 'kn' }, 27 | { code: 'HUF', symbol: 'Ft' }, 28 | { code: 'IDR', symbol: 'Rp' }, 29 | { code: 'ILS', symbol: '₪' }, 30 | { code: 'INR', symbol: '₹' }, 31 | { code: 'JPY', symbol: '¥' }, 32 | { code: 'KRW', symbol: '₩' }, 33 | { code: 'MXN', symbol: 'Mex$' }, 34 | { code: 'MYR', symbol: 'RM' }, 35 | { code: 'NOK', symbol: 'kr' }, 36 | { code: 'NZD', symbol: 'NZ$' }, 37 | { code: 'PHP', symbol: '₱' }, 38 | { code: 'PLN', symbol: 'zł' }, 39 | { code: 'RON', symbol: 'lei' }, 40 | { code: 'RUB', symbol: '₽' }, 41 | { code: 'SEK', symbol: 'kr' }, 42 | { code: 'SGD', symbol: 'S$' }, 43 | { code: 'THB', symbol: '฿' }, 44 | { code: 'TRY', symbol: '₺' }, 45 | { code: 'USD', symbol: '$' }, 46 | { code: 'ZAR', symbol: 'R' } 47 | ] 48 | 49 | class Demo { 50 | constructor (opts) { 51 | const _this = this 52 | 53 | this.services = new ServiceManager( 54 | path.resolve(__dirname, '../../node_modules'), 55 | path.resolve(__dirname, '../../data')) 56 | this.adminUser = opts.adminUser 57 | this.adminPass = opts.adminPass 58 | 59 | this.numLedgers = opts.numLedgers 60 | this.numConnectors = opts.numConnectors 61 | this.barabasiAlbertConnectedCore = opts.barabasiAlbertConnectedCore || 2 62 | this.barabasiAlbertConnectionsPerNewNode = opts.barabasiAlbertConnectionsPerNewNode || 2 63 | 64 | if (process.env.npm_node_execpath && process.env.npm_execpath) { 65 | this.npmPrefix = process.env.npm_node_execpath + ' ' + process.env.npm_execpath 66 | } else { 67 | this.npmPrefix = 'npm' 68 | } 69 | 70 | // Connector graph 71 | // Barabási–Albert (N, m0, M) 72 | // 73 | // N .. number of nodes 74 | // m0 .. size of connected core (m0 <= N) 75 | // M .. (M <= m0) 76 | this.graph = randomgraph.BarabasiAlbert( 77 | this.numLedgers, 78 | this.barabasiAlbertConnectedCore, 79 | this.barabasiAlbertConnectionsPerNewNode) 80 | this.connectorEdges = new Array(this.numConnectors) 81 | this.connectorNames = new Array(this.numConnectors) 82 | for (let i = 0; i < this.numConnectors; i++) { 83 | this.connectorEdges[i] = [] 84 | this.connectorNames[i] = connectorNames[i] || 'connector' + i 85 | } 86 | this.ledgerHosts = {} 87 | // Connector usernames per ledger 88 | // { ledgerPrefix → [ connectorIndex ] } 89 | this.ledgerConnectors = {} 90 | this.graph.edges.forEach(function (edge, i) { 91 | const source = edge.source 92 | const target = edge.target 93 | edge.source_currency = currencies[source % currencies.length].code 94 | edge.target_currency = currencies[target % currencies.length].code 95 | edge.source = 'demo.ledger' + source + '.' 96 | edge.target = 'demo.ledger' + target + '.' 97 | this.ledgerHosts[edge.source] = 'http://localhost:' + (3000 + source) 98 | this.ledgerHosts[edge.target] = 'http://localhost:' + (3000 + target) 99 | _this.connectorEdges[i % _this.numConnectors].push(edge) 100 | if (!this.ledgerConnectors[edge.source]) { 101 | this.ledgerConnectors[edge.source] = [] 102 | } 103 | this.ledgerConnectors[edge.source].push(this.connectorNames[i % _this.numConnectors]) 104 | if (!this.ledgerConnectors[edge.target]) { 105 | this.ledgerConnectors[edge.target] = [] 106 | } 107 | this.ledgerConnectors[edge.target].push(this.connectorNames[i % _this.numConnectors]) 108 | }, this) 109 | } 110 | 111 | start () { 112 | return co.wrap(this._start).call(this) 113 | } 114 | 115 | * _start () { 116 | for (let i = 0; i < this.numLedgers; i++) { 117 | yield this.startLedger('demo.ledger' + i + '.', 3000 + i) 118 | } 119 | 120 | for (let i = 0; i < this.numConnectors; i++) { 121 | yield this.setupConnectorAccounts(this.connectorNames[i], this.connectorEdges[i]) 122 | } 123 | for (let i = 0; i < this.numConnectors; i++) { 124 | yield this.startConnector(this.connectorNames[i], this.connectorEdges[i]) 125 | } 126 | 127 | yield this.services.startVisualization(5000) 128 | } 129 | 130 | * startLedger (ledger, port) { 131 | yield this.services.startLedger(ledger, port, { 132 | recommendedConnectors: this.ledgerConnectors[ledger] 133 | }) 134 | yield this.services.updateAccount(ledger, 'alice', {balance: '1000000000'}) 135 | yield this.services.updateAccount(ledger, 'bob', {balance: '1000000000'}) 136 | } 137 | 138 | * startConnector (connector, edges) { 139 | yield this.services.startConnector(connector, { 140 | pairs: this.edgesToPairs(edges), 141 | credentials: this.edgesToCredentials(edges, connector), 142 | backend: 'fixerio' 143 | }) 144 | } 145 | 146 | * setupConnectorAccounts (connector, edges) { 147 | for (const edge of edges) { 148 | yield this.services.updateAccount(edge.source, connector, {balance: '1000000000', connector: edge.source + connector}) 149 | yield this.services.updateAccount(edge.target, connector, {balance: '1000000000', connector: edge.target + connector}) 150 | } 151 | } 152 | 153 | edgesToPairs (edges) { 154 | const pairs = [] 155 | for (const edge of edges) { 156 | pairs.push([ 157 | edge.source_currency + '@' + edge.source, 158 | edge.target_currency + '@' + edge.target 159 | ]) 160 | pairs.push([ 161 | edge.target_currency + '@' + edge.target, 162 | edge.source_currency + '@' + edge.source 163 | ]) 164 | } 165 | return pairs 166 | } 167 | 168 | edgesToCredentials (edges, connectorName) { 169 | const creds = {} 170 | for (const edge of edges) { 171 | creds[edge.source] = this.makeCredentials(edge.source, edge.source_currency, connectorName) 172 | creds[edge.target] = this.makeCredentials(edge.target, edge.target_currency, connectorName) 173 | } 174 | return creds 175 | } 176 | 177 | makeCredentials (ledger, currency, name) { 178 | return { 179 | currency: currency, 180 | plugin: 'ilp-plugin-bells', 181 | options: { 182 | account: this.ledgerHosts[ledger] + '/accounts/' + encodeURIComponent(name), 183 | username: name, 184 | password: name 185 | } 186 | } 187 | } 188 | } 189 | 190 | exports.Demo = Demo 191 | --------------------------------------------------------------------------------