Skip to content
Framework for creating Interledger settlement engines in JS
TypeScript JavaScript
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.circleci feat: add core abstraction Sep 28, 2019
src
.gitignore
.prettierrc feat: add core abstraction Sep 28, 2019
LICENSE
README.md
jest.config.js feat: add core abstraction Sep 28, 2019
package-lock.json 0.1.1 Sep 28, 2019
package.json
settlement-architecture.svg docs: add settlement diagram Oct 3, 2019
tsconfig.json feat: add core abstraction Sep 28, 2019
tslint.json feat: add core abstraction Sep 28, 2019

README.md

Settlement Core

Framework for creating Interledger settlement engines in JS

NPM Package CircleCI Codecov Prettier Apache 2.0 License

Get Started

If you're looking to operate a settlement engine with your connector or integrate one into your app or service, check out these awesome implementations!

Settlement Engine Supported Assets Status Language Authors
Ethereum (on-ledger) ETH, ERC-20s Beta Rust Georgios Konstantopoulos
XRP (on-ledger) XRP Beta TypeScript Matt de Haast, Kincaid O'Neil
Lightning BTC Under development TypeScript Kincaid O'Neil

If you want learn more about settlement engines or develop a new one, keep reading!

Overview

Settlement in Interledger

In the Interledger protocol, connectors maintain peers, or counterparties whom they transact with. Connectors clear and fulfill ILP packets with their peers, which represent conditional IOUs that affect financial accounting balances betwen them.

A connector may extend a given peer a limited line of credit, or none at all, depending upon their trustworthiness. As the connector receives incoming ILP Prepare packets from a peer, forwards them, and returns corresponding ILP Fulfill packets, that peer's liabilities to the connector accrue. If the peer's liabilities exceed the credit limit assigned to it, the connector may reject and decline to forward additional packets.

In order to continue transacting, the peer must settle their liabilities. In most cases, this is accomplished through sending a payment on a settlement system that both peers have agreed to use. The connector should credit the incoming payment, irrevocably discharging a sum the peer owed to it, which enables clearing subsequent Interledger packets from the peer.

Settlement systems transfer value from one participant to another, and include:

  • Cryptocurrencies and distributed ledgers (Bitcoin, Ethereum, XRP...)
  • Payment channels and layer 2 networks (Lightning, state channels...)
  • Traditional banking infrastructure (ACH, SWIFT, wire transfers, card processors...)
  • Money transfer services (PayPal, Venmo, Square Cash...)
  • Mobile money (WeChat, Alipay, M-PESA...)
  • Cash or physically delivering assets

Settlement Engines

Settlement engines are services that integrate with a settlement system to send and receive settlements. Two Interledger peers each operate compatible settlement engines. Since an Interledger connector may have many peers using the same asset, one settlement engine may manage multiple accounts, to settle with many different peers.

The Settlement Engine specification defines a standardized HTTP API for Interledger connectors to interface with their settlement engines, and vice versa. Connectors trigger the settlement engine to perform settlements, and settlement engines trigger the connector to adjust accounting balances when incoming settlements are received, like so:

Settlement architecture

Settlement engines may also use the same HTTP API to send and receive messages with a peer's settlement engine. Settlement Core manages all this communication with the connector in the background, exposing a simple interface.

Design Goals

  • Intuitive. Provide the essential primitives to quickly develop safe, reliable settlement engine implementations.
  • Scalable. Support standalone clients all the way up to high-volume, low-latency service providers.
  • Interoperable. Settlement engines should fully support each next-generation connector, including Interledger.rs, Rafiki, and the Java connector.
  • Isomorphic. JavaScript settlement engines should operate seamlessly across Node.js, desktop & mobile browsers, and Electron apps.

API

🚨 Since this tech is hot off the press, note that the APIs here are beta and subject to change!

First, create a new Node.js module and add the ilp-settlement-core NPM module as a dependency.

Next, create an index.js (or index.ts, if using TypeScript) file to define the settlement engine. Settlement engines can be defined as a factory function: given account services, the function returns a Promise with a constructed settlement engine:

export const connectEngine = async services => {
  // Async tasks to connect engine ...

  return {
    // Settlement engine instance ...
  }
}

The services parameter includes these callback functions, provided by Settlement Core, to each settlement engine:

interface AccountServices {
  /**
   * Send a message to the given account and return their response
   *
   * @param accountId Unique account identifier to send message to
   * @param message Object to be serialized as JSON
   */
  sendMessage(accountId: string, message: any): Promise<any>

  /**
   * Send a notification to the connector to credit the given incoming settlement
   *
   * @param accountId Unique account identifier (recipient of settlement)
   * @param amount Amount received as an incoming settlement
   * @param settlementId Unique identifier for this settlement
   */
  creditSettlement(accountId: string, amount: BigNumber, settlementId?: string): void

  /**
   * Retry failed or queued outgoing settlements
   * - Automatically called after the settlement engine is instantiated
   *
   * @param accountId Unique account identifier
   */
  trySettlement(accountId: string): void
}

Then, the function should a return a contructed settlement engine instance that implements this interface:

interface SettlementEngine {
  /**
   * Setup the given account and perform tasks as a pre-requisite to send settlements
   * - For example, send a message to the peer to exchange ledger identifiers
   *
   * @param accountId Unique account identifier
   */
  setupAccount?(accountId: string): Promise<void>

  /**
   * Send a settlement to the peer for up to the given amount
   * - Since the amount is provided in arbitrary precision, round to the correct
   *   precision first
   * - The leftover, unsettled amount will automatically be tracked and retried later
   *   based on the amount returned
   * - If Promise is rejected, for safety, the full amount will assumed to be settled
   *
   * @param accountId Unique account identifier
   * @param amount Maximum amount to settle, in standard unit of asset (arbitrary precision)
   * @return Amount settled, in standard unit of asset (arbitrary precision)
   */
  settle(accountId: string, amount: BigNumber): Promise<BigNumber>

  /**
   * Handle and respond to an incoming message from the given peer
   *
   * @param accountId Unique account identifier
   * @param message Parsed JSON message from peer
   * @return Response message, to be serialized as JSON
   */
  handleMessage?(accountId: string, message: any): Promise<any>

  /**
   * Delete or close the given account
   * - For example, clean up database records associated with the account
   *
   * @param accountId Unique account identifier
   */
  closeAccount?(accountId: string): Promise<void>

  /**
   * Disconnect the settlement engine
   * - For example, gracefully closes connections to the ledger and/or databases
   */
  disconnect?(): Promise<void>
}

Running settlement engines

The factory to connect a settlement engine can be provided to Settlement Core, which will start an HTTP server to communicate with the connector using its startServer function. For example:

import { connectEngine } from '.'
import { startServer, connectRedis } from 'ilp-settlement-core'

async function run() {
  const store = await connectRedis()
  await startServer(connectEngine, store)
}

run().catch(err => console.error(err))

The startServer function should also be provided a database (Settlement Core currently supports Redis and a simple in-memory store) and configuration options to connect to the connector. Here's the type signature of the startServer function:

type StartServer = (
  /** Factory to connect and instantiate a settlement engine */
  createEngine: ConnectSettlementEngine,

  /** Database for balance logic and basic account handling (default: memory store) */
  store?: SettlementStore,

  /** Configuration for the server with the connector */
  config?: {
    /** URL of the connector's server for this settlement engine (default: http://localhost:7771) */
    connectorUrl?: string

    /** Port to operate the settlement server on (default: 3000) */
    port?: string | number
  }
) => Promise<{
  /** Stop the server interacting with the connector and disconnect the settlement engine */
  shutdown(): Promise<void>
}>

Configuration

How settlement engines are configured is up to implementations: they may use environment variables, config files, or their own method. Settlement engines can take in configuration options using a higher-order function, like so:

export const createEngine = config => async services => {
  // Construct settlement engine...
}

Then, to instantiate a settlement engine, first pass in the configuration options, and then pass the factory to Settlement Core when starting the settlement server:

async function run() {
  // Config using environment variables
  const connectorUrl = process.env.CONNECTOR_URL
  const ledgerCredential = process.env.LEDGER_CREDENTIAL

  const store = await connectRedis()

  // Inject config options, which returns factory function to connect settlement engine
  const connectEngine = createEngine({ ledgerCredential })

  // Pass factory function when starting settlement server
  await startServer(connectEngine, store, { connectorUrl })
}

Amounts

Settlement Core using BigNumber.js to pass around arbitrary precision numbers. For example, in Bitcoin, transaction amounts may denominated in precision of satoshis, which are 8 decimal places. The BigNumber type enables these amounts to be represented precisely without losing precision compared to JavaScript numbers.

When interfacing with Settlement Core, settlement engines should always handle amounts in the standard unit of the asset, such as USD or BTC. For example, 1 satoshi would be passed as a BigNumber of 0.00000001, denominanted in BTC.

Due to the way connectors track balances, Settlement Core may pass amounts to the settlement engine more precise than can actually be settled on the underlying ledger. In this case of Bitcoin, this would be the equivalent passing a BigNumber representing 0.05000003769, since there ar more than 8 decimal places. To handle this case, settlement engines should truncate this amount by using calling the decimalPlaces method on each amount with the maximum number of decimal places they can settle (always round down). Settlement engines should then return this truncated amount from the settle method. Then, Settlement Core will automatically calculate and track this "leftover" amount, which will accrue and be retried later.

Advanced

Coming soon: recommendations for an admin API, database/pub-subs, and a more step-by-step guide!

You can’t perform that action at this time.