title | draft |
---|---|
The Javascript Ledger Plugin Interface Version 2 |
3 |
The Interledger Protocol is a protocol suite for making payments across multiple different settlement systems.
This spec defines a JavaScript ledger abstraction interface for Interledger clients and connectors to communicate and route payments across different ledger protocols. While the exact methods and events defined here are specific to the JavaScript implementation, this may be used as a guide for ledger abstractions in other languages.
To send ILP payments through a new ledger, one must implement a ledger plugin that exposes the interface defined below. This can be used with the ILP Client and Connector and should work out of the box.
This spec depends on the ILP spec.
class LedgerPlugin
Name | |
---|---|
new |
LedgerPlugin ( opts, api ) |
connect ( options ) ⇒ Promise.<undefined> |
|
disconnect ( ) ⇒ Promise.<undefined> |
|
isConnected ( ) ⇒ Boolean |
|
sendData ( data ) ⇒ Promise.<Buffer> |
|
sendMoney ( amount ) ⇒ Promise.<undefined> |
|
registerDataHandler ( dataHandler ) ⇒ undefined |
|
deregisterDataHandler ( ) ⇒ undefined |
|
registerMoneyHandler ( moneyHandler ) ⇒ undefined |
|
deregisterMoneyHandler ( ) ⇒ undefined |
Name | |
---|---|
static |
version = 2 |
Name | Handler |
---|---|
connect | ( ) ⇒ |
disconnect | ( ) ⇒ |
error | ( ) ⇒ |
new LedgerPlugin( opts : object, api? : PluginServices )
Create a new instance of the plugin. Each instance typically corresponds to a different ledger. However, some plugins MAY deviate from a strict one-to-one relationship and MAY internally act as a virtual connector to more than one counterparty.
The first parameter opts
is a configuration object the shape of which is specific to each plugin. Plugins will often be configured through environment variables, so it is recommended that the opts
SHOULD be JSON serializable. However, plugins MAY use non-serializable values to offer advanced features.
The second parameter api
is optional and is used to pass additional environment services to the plugin, such as a logger or a key-value store. Most plugins SHOULD work even if this parameter is undefined
, but MAY offer less functionality in that case (e.g. no persistence.)
Throws InvalidFieldsError
if the constructor is given incorrect arguments in opts
. Throws TypeError
if opts
is not an object or api
is defined and not an object. Throws InvalidServicesError
if a service is required, but not provided via api
.
Name | Type | Description |
---|---|---|
opts | PluginOptions |
Object containing ledger-related settings. May contain plugin-specific fields. |
const ledgerPlugin = new LedgerPlugin({
// auth parameters are defined by the plugin
_store: {
// persistence may be required for internal use by some ledger plugins
// (e.g. when the ledger has reduced memo capability and we can only put an ID in the memo)
// Store a value under a key
put: (key, value) => {
// Returns Promise.<undefined>
},
// Fetch a value by key
get: (key) => {
// Returns Promise.<Object>
},
// Delete a value by key
del: (key) => {
// Returns Promise.<undefined>
}
}
})
For a detailed description of these properties, please see PluginOptions
.
LedgerPlugin.version:Number
Always 2
for this version of the Ledger Plugin Interface.
ledgerPlugin.connect( options:ConnectOptions ⇒ Promise.<undefined>
options
is optional.
Initiate ledger event subscriptions. Once connect
is called the ledger plugin MUST attempt to subscribe to and report ledger events. Once the connection is established, the ledger plugin should emit the connect
event. If the connection is lost, the ledger plugin SHOULD emit the disconnect
event.
Rejects with InvalidFieldsError
if credentials are missing, and NotAcceptedError
if credentials are rejected.
Rejects with TypeError
if options.timeout
is passed but is not a Number
.
ledgerPlugin.disconnect() ⇒ Promise.<undefined>
Unsubscribe from ledger events.
ledgerPlugin.isConnected() ⇒ Boolean
Query whether the plugin is currently connected.
ledgerPlugin.on('connect', () ⇒ )
Emitted whenever a connection is successfully established.
ledgerPlugin.on('disconnect', () ⇒ )
Emitted when the connection has been terminated or lost.
ledgerPlugin.on('error', ( err:Error ) ⇒ )
General event for fatal exceptions. Emitted when the plugin experienced an unexpected unrecoverable condition. Once triggered, this instance of the plugin MUST NOT be used anymore.
ledgerPlugin.sendData( data:Buffer ) ⇒ Promise.<Buffer>
Sends data to the counterparty of the account and returns a response asynchronously. Request data is passed in as a Buffer
and response data is returned as a Buffer
.
Name | Type | Description |
---|---|---|
data | Buffer |
Binary request data |
Promise.<Buffer>
A promise which resolves when the response has been received.
This method MAY reject with any arbitrary JavaScript error.
const responseBuffer = await p.sendData(requestBuffer)
ledgerPlugin.sendMoney( amount:string ) ⇒ Promise.<undefined>
Transfer amount
units of money from the caller to the counterparty of the account.
All plugins MUST support amounts in a range from one to some maximum.
ledgerPlugin.registerDataHandler( dataHandler: ( data: Buffer ) ⇒ Promise<Buffer> ) ⇒ undefined
Set the callback which is used to handle incoming prepared data packets. The callback should expect one parameter (the data as a Buffer)) and return a promise for the resulting response data packet (as a Buffer.) If an error occurs, the callback MAY throw an exception. In general, the callback should behave as sendData
does.
If a data handler is already set, this method throws a DataHandlerAlreadyRegisteredError
. In order to change the data handler, the old handler must first be removed via deregisterDataHandler
. This is to ensure that handlers are not overwritten by accident.
If an incoming packet is received by the plugin, but no handler is registered, the plugin SHOULD respond with an error.
ledgerPlugin.deregisterDataHandler( ) ⇒ undefined
Removes the currently used data handler. This has the same effect as if registerDataHandler
had never been called.
If no data handler is currently set, this method does nothing.
ledgerPlugin.registerMoneyHandler( moneyHandler: ( amount: string ) ⇒ Promise<undefined> ) ⇒ undefined
Set the callback which is used to handle incoming money. The callback should expect one parameter (the amount) and return a promise. If an error occurs, the callback MAY throw an exception. In general, the callback should behave as sendMoney
does.
If a money handler is already set, this method throws a MoneyHandlerAlreadyRegisteredError
. In order to change the money handler, the old handler must first be removed via deregisterMoneyHandler
. This is to ensure that handlers are not overwritten by accident.
If incoming money is received by the plugin, but no handler is registered, the plugin SHOULD return an error (and MAY return the money.)
ledgerPlugin.deregisterMoneyHandler( ) ⇒ undefined
Removes the currently used money handler. This has the same effect as if registerMoneyHandler
had never been called.
If no money handler is currently set, this method does nothing.
class PluginServices
Plugin services are optionally passed in to the LedgerPlugin
constructor when a plugin is being instantiated. Which services are provided
MAY vary based on the host environment or none MAY be available at all.
Type | Name | Description |
---|---|---|
Object |
store | Simple key-value store object |
Object |
log | Simple logger object |
store:Object
Provides callback hooks to the host's persistence layer.
Most plugins SHOULD work (possibly with higher trust or degraded experience) without a store
. However, if a plugin is not able to function without a store and none is provided, the constructor MUST throw an InvalidServicesError
.
Method names are based on the popular LevelUP/LevelDOWN packages.
{
// Store a value under a key
put: (key, value) => {
// Returns Promise.<undefined>
},
// Fetch a value by key
get: (key) => {
// Returns Promise.<Object>
},
// Delete a value by key
del: (key) => {
// Returns Promise.<undefined>
}
}
log:Object
Provides logging hooks to the host. Hosts MAY use this feature to prefix log lines with the identifier of the plugin instance.
If this parameter is not provided, the plugin SHOULD use a suitable default logging mechanism.
The logging methods support printf-style formatting. The following formatters are available:
Formatter | Representation |
---|---|
%O |
Pretty-print an Object on multiple lines. |
%o |
Pretty-print an Object all on a single line. |
%s |
String. |
%d |
Number (both integer and float). |
%j |
JSON. Replaced with the string '[Circular]' if the argument contains circular references. |
%% |
Single percent sign ('%'). This does not consume an argument. |
Log messages MUST NOT contain private keys or other credentials.
{
// Extremely verbose debug information
debug: (message, ...params) => { }
// Notable events that may happen during normal operation
info: (message, ...params) => { }
// Warnings indicate unusual events that call for the user's attention
warn: (message, ...params) => { }
// Errors indicate something went wrong
error: (message, ...params) => { }
}
class ConnectOptions
Type | Name | Description |
---|---|---|
Number |
timeout | Amount of time before the client SHOULD give up trying to connect (in milliseconds) |
timeout:Number
The number of milliseconds that the plugin should spend trying to connect before giving up.
If falsy, use the plugin's default timeout.
If Infinity
, there is no timeout.
Various methods defined in the LPI throw errors; others can reject the Promise they return with errors. In both cases, these errors need to be derived from JavaScript's Error.prototype
, and need to have the .name
field set to their own name, as a String. Example:
function InvalidFieldsError(message) {
this.name = 'InvalidFieldsError'
this.message = (message || '')
}
InvalidFieldsError.prototype = Error.prototype