Skip to content

Latest commit

 

History

History
695 lines (497 loc) · 28.5 KB

0004-ledger-plugin-interface.md

File metadata and controls

695 lines (497 loc) · 28.5 KB

Ledger Plugin Interface

The Interledger Protocol is a protocol suite for connecting blockchains and other ledgers.

This spec defines a ledger abstraction interface for Interledger clients and connectors to communicate and route payments across different ledger protocols.

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

class LedgerPlugin

Methods
Name
new LedgerPlugin ( opts )
connect ( options ) ⇒ Promise.<null>
disconnect ( ) ⇒ Promise.<null>
isConnected ( ) ⇒ Boolean
getInfo ( ) LedgerInfo
getAccount ( ) ⇒ String
getBalance ( ) ⇒ Promise.<String>
getFulfillment ( transferId ) ⇒ Promise.<String>
sendTransfer ( transfer ) ⇒ Promise.<null>
sendMessage ( message ) ⇒ Promise.<null>
fulfillCondition ( transferId, fulfillment ) ⇒ Promise.<null>
rejectIncomingTransfer ( transferId, rejectMessage ) ⇒ Promise.<null>
Events
Name Handler
connect ( ) ⇒
disconnect ( ) ⇒
error ( ) ⇒
incoming_transfer ( transfer:IncomingTransfer ) ⇒
incoming_prepare ( transfer:IncomingTransfer ) ⇒
incoming_fulfill ( transfer:IncomingTransfer, fulfillment:String ) ⇒
incoming_reject ( transfer:IncomingTransfer, rejectionReason:Buffer ) ⇒
incoming_cancel ( transfer:IncomingTransfer, cancellationReason:Buffer ) ⇒
incoming_message ( message:IncomingMessage ) ⇒
outgoing_transfer ( transfer:outgoingTransfer ) ⇒
outgoing_prepare ( transfer:outgoingTransfer ) ⇒
outgoing_fulfill ( transfer:outgoingTransfer, fulfillment:String ) ⇒
outgoing_reject ( transfer:outgoingTransfer, rejectionReason:Buffer ) ⇒
outgoing_cancel ( transfer:outgoingTransfer, cancellationReason:Buffer ) ⇒
info_change ( info:LedgerInfo ) ⇒
Errors
Name Description
InvalidFieldsError Arguments or configuration were invalidated client-side
UnreachableError An error occured due to connection failure
TransferNotFoundError A requested transfer does not exist and cannot be fetched
MissingFulfillmentError A transfer has not yet been fulfilled, so the fulfillment cannot be fetched
DuplicateIdError A transfer with the same ID and different fields has been sent
AlreadyRolledBackError A requested transfer has already been timed out or rejected and cannot be modified
AlreadyFulfilledError A requested transfer has already been fulfilled and cannot be modified
TransferNotConditionalError A requested transfer is not conditional and cannot be rejected/fulfilled/etc.
NotAcceptedError An operation has been rejected due to ledger-side logic
NoSubscriptionsError A transfer or message cannot be delivered because there are no active websockets

Instance Management

new LedgerPlugin

new LedgerPlugin( opts : PluginOptions )

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 use one instance for multiple (similar) ledgers or multiple instances to talk to the same ledger.

Throws InvalidFieldsError if the constructor is given incorrect arguments.

Parameters
Name Type Description
opts PluginOptions Object containing ledger-related settings. May contain plugin-specific fields.
Example
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.<null>
    },
    // Fetch a value by key
    get: (key) => {
      // Returns Promise.<Object>
    },
    // Delete a value by key
    del: (key) => {
      // Returns Promise.<null>
    }
  }
})

For a detailed description of these properties, please see PluginOptions.

Connection Management

connect

ledgerPlugin.connect( options:ConnectOptions ⇒ Promise.<null>

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. The plugin should ensure that the information returned by getInfo and getAccount is available and cached before emitting the connect event.

Throws InvalidFieldsError if credentials are missing, and NotAcceptedError if credentials are rejected. Throws TypeError if options.timeout is passed but is not a Number.

disconnect

ledgerPlugin.disconnect() ⇒ Promise.<null>

Unsubscribe from ledger events.

isConnected

ledgerPlugin.isConnected() ⇒ Boolean

Query whether the plugin is currently connected.

getInfo

ledgerPlugin.getInfo() ⇒ LedgerInfo

Retrieve some metadata about the ledger. Plugin must be connected, otherwise the function should throw.

Example Return Value
{
  "prefix": "us.fed.some-bank.",
  "precision": 10,
  "scale": 4,
  "currencyCode": "USD",
  "currencySymbol": "$",
  "connectors": [ "us.fed.some-bank.chloe" ]
}

For a detailed description of these properties, please see LedgerInfo.

getAccount

ledgerPlugin.getAccount() ⇒ String

Get the ledger plugin's ILP address. This is given to senders to receive transfers to this account. Plugin must be connected, otherwise the function should throw.

The mapping from the ILP address to the local ledger address is dependent on the ledger / ledger plugin. An ILP address could be the <ledger prefix>.<account name or number>, or a token could be used in place of the actual account name or number.

Example Return Value

us.fed.some-bank.my-account

getBalance

ledgerPlugin.getBalance() ⇒ Promise.<String>

Return a decimal string representing the current balance. Plugin must be connected, otherwise the promise should reject.

getFulfillment

ledgerPlugin.getFulfillment( transferId ) ⇒ Promise.<String>

Return the fulfillment of a transfer if it has already been executed.

Throws MissingFulfillmentError if the transfer exists but is not yet fulfilled. Throws TransferNotFoundError if no conditional transfer is found with the given ID. Throws AlreadyRolledBackError if the transfer has been rolled back and will not be fulfilled. Throws TransferNotConditionalError if transfer is not conditional.

Event: connect

ledgerPlugin.on('connect', () ⇒ )

Emitted whenever a connection is successfully established.

Event: disconnect

ledgerPlugin.on('disconnect', () ⇒ )

Emitted when the connection has been terminated or lost.

Event: error

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.

Ledger Transfers

Note that all transfers will have transferId's to allow the plugin user to correlate actions related to a single transfer. The transferId will be the same as the ID used by the underlying ledger wherever possible or applicable. If the ledger does not have transfer IDs, the plugin may generate one and use the store passed in to the constructor to persist them.

sendTransfer

ledgerPlugin.sendTransfer( transfer:OutgoingTransfer ) ⇒ Promise.<null>

Plugin must be connected, otherwise the promise should reject. Initiates a ledger-local transfer. A transfer can contain money and/or information. If there is a problem with the structure or validity of the transfer, then sendTransfer should throw an error in the form of a rejected promise. If the transfer is accepted by the ledger, however, then further errors will be in the form of "reject" events.

All plugins MUST implement zero-amount transfers, but some ledger plugins MAY implement zero-amount transfers differently than other transfers.

Parameters
Name Type Description
transfer OutgoingTransfer Properties of the transfer to be created

When sending transfers, the id, amount and to fields are required.

Returns

Promise.<null> A promise which resolves when the transfer has been submitted (but not necessarily accepted.)

Throws InvalidFieldsError if required fields are missing from the transfer or malformed. Throws DuplicateIdError if a transfer with the given ID and different already exists. Throws NotAcceptedError if the transfer is rejected by the ledger due to insufficient balance or a nonexistant destination account.

Example
p.sendTransfer({
  id: 'd86b0299-e2fa-4713-833a-96a6a75271b8',
  to: 'example.ledger.connector',
  amount: '10',
  data: new Buffer('...', 'base64'),
  noteToSelf: {},
  executionCondition: '47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU',
  expiresAt: '2016-05-18T12:00:00.000Z'
})

For a detailed description of these properties, please see OutgoingTransfer.

sendMessage

ledgerPlugin.sendMessage( message:OutgoingMessage ) ⇒ Promise.<null>

Plugin must be connected, otherwise the promise should reject. Send a ledger-local message. If there is a problem with the structure or validity of the message, then sendMessage should throw an error in the form of a rejected promise.

Messaging is used by connectors for quoting and broadcasting routes.

Parameters
Name Type Description
message OutgoingMessage Properties of the message to be created

When sending messages, the to and data fields are required.

Returns

Promise.<null> A promise which resolves when the message has been submitted (but not necessarily delivered). To ensure delivery, you must build a mechanism for that on top.

Throws InvalidFieldsError if required fields are missing from the message or malformed. Throws NotAcceptedError if the message is rejected by the ledger. Throws NoSubscriptionsError if the message cannot be delivered because there is nobody listening to messages addressed to the given account.

Example
p.sendMessage({
  to: 'example.ledger.connector',
  data: { foo: 'bar' }
})

For a detailed description of these properties, please see OutgoingMessage.

fulfillCondition

ledgerPlugin.fulfillCondition( transferId:String, fulfillment:String ) ⇒ Promise.<null>

Submit a fulfillment to a ledger. Plugin must be connected, otherwise the promise should reject.

The fulfillment is an arbitrary 32-byte buffer and is provided as a base64url-encoded string.

Throws InvalidFieldsError if the fulfillment is malformed. Throws TransferNotFoundError if the fulfillment if no conditional transfer with the given ID exists. Throws AlreadyRolledBackError if the transfer has already been rolled back. Throws NotAcceptedError if the fulfillment is formatted correctly, but does not match the condition of the specified transfer. Throws TransferNotConditionalError if transfer is not conditional.

rejectIncomingTransfer

ledgerPlugin.rejectIncomingTransfer( transferId:String, rejectMessage:Buffer ) ⇒ Promise.<null>

Reject an incoming transfer that is held pending the fulfillment of its executionCondition before the expiresAt time. rejectMessage MAY be supplied to provide details on why the transfer was rejected.

Throws TransferNotFoundError if there is no conditional transfer with the given ID. Throws AlreadyFulfilledError if the specified transfer has already been fulfilled. Throws NotAcceptedError if you are not authorized to reject the transfer (e.g. if you are the sender). Throws TransferNotConditionalError if transfer is not conditional.

This MAY be used by receivers or connectors to reject incoming funds if they will not fulfill the condition or are unable to forward the payment. Previous hops in an Interledger transfer would have their money returned before the expiry and the sender or previous connectors MAY retry and reroute the transfer through an alternate path.

Event: *_transfer

ledgerPlugin.on('incoming_transfer', ( transfer:Transfer, ) ⇒ )

Emitted after an outgoing/incoming transfer which does not have a condition is executed on the ledger.

This indicates that the funds have already been transferred. In order to prevent unexpected incoming funds, a ledger MAY allow users to forbid incoming transfers without conditions.

If the event is outgoing_transfer, then it means you sent the transfer. incoming_transfer means somebody sent funds to you.

Event: *_prepare

ledgerPlugin.on('incoming_prepare', ( transfer:Transfer, ) ⇒ )

Emitted when an outgoing/incoming transfer containing a condition is prepared.

Note that the *_prepare event DOES NOT indicate that money has been transferred. The final status will only be known when either the *_fulfill or *_cancel events are emitted.

The ledger plugin MUST authenticate the source for all incoming transfers, whether they include money or not.

If the event is outgoing_prepare, then it means you prepared the transfer. incoming_prepare means someone prepared a transfer to you.

Event: *_fulfill

ledgerPlugin.on('outgoing_fulfill', ( transfer:Transfer, fulfillment:String ) ⇒ )

Emitted when an outgoing/incoming transfer with a condition is fulfilled.

This indicates that funds have been transferred. In order to prevent unexpected incoming funds, a ledger MAY forbid accounts from fulfilling a transfer who are not the transfer's receiver.

If the event is incoming_fulfill, then it means you fulfilled the transfer. outgoing_fulfill means the receiver of your outgoing transfer has fulfilled the condition.

Event: *_reject

ledgerPlugin.on('outgoing_reject', ( transfer:Transfer, reason:Buffer ) ⇒ )

Emitted when an outgoing/incoming transfer is rejected by the receiver.

This indicates that a transfer has been manually cancelled before the timeout by the receiver. A message can be passed along with the rejection.

If the event is incoming_reject, then it means you rejected the transfer. outgoing_reject means that the receiver of your outgoing transfer has rejected it.

Event: *_cancel

ledgerPlugin.on('outgoing_cancel', ( transfer:Transfer, reason:Buffer ) ⇒ )

Emitted when an outgoing/incoming transfer is rejected by the ledger.

This will happen on a timeout, triggered by the ledger and not by the receiver.

If the event is incoming_cancel, an incoming transfer was timed out by the ledger. outgoing_cancel means that a transfer you created has timed out.

Event: incoming_message

ledgerPlugin.on('incoming_message', ( message:IncomingMessage, ) ⇒ )

Emitted when an incoming message arrives from the ledger.

Event: info_change

ledgerPlugin.on('info_change', ( info:LedgerInfo ) ⇒ )

Emitted any time the plugin's LedgerInfo cache changes.

Class: Transfer

class Transfer

The Transfer class is used to describe local ledger transfers. Fields can be left undefined (but not any other false-y value) if unused.

Fields
Type Name Description
String id UUID used as an external identifier
String account ILP Address of the source or destination account (deprecated)
String from ILP Address of the source account
String to ILP Address of the destination account
String ledger ILP Address prefix of the ledger
String amount Decimal transfer amount
Object data Data packet or memo to be sent with the transfer, starts with an ILP header
Object noteToSelf Host-provided memo that should be stored with the transfer
String executionCondition Cryptographic hold condition
String expiresAt Expiry time of the cryptographic hold
Object custom Object containing ledger plugin specific options

IncomingTransfer

class IncomingTransfer extends Transfer

IncomingTransfer objects describe transfers which are received by the ledger plugin. The account field refers to the local source account that the transfer originated from.

See Transfer for more information.

OutgoingTransfer

class OutgoingTransfer extends Transfer

OutgoingTransfer objects describe transfers which have been sent by the ledger plugin. The account field refers to the local destination account on the underlying ledger.

See Transfer for more information.

Fields

id

id:String

External unique identifier used by the host.

For OutgoingTransfers, the ID is chosen by the host. The ledger plugin MAY use a different identifier internally, but MUST fail if the external ID has already been used. In the case of a connector, the ID will be deterministically chosen from the hash of the ledger and transfer IDs of the inbound transfer that triggered this outbound transfer.

For IncomingTransfers, the ID is chosen by the ledger plugin. The ledger plugin MAY use any type of UUID, but MUST ensure that the same transfer is always referred to by the same ID and that IDs are unique per ledger.

Ledger plugins that support scalability (e.g. running multiple instances of a connector using the same settings) MUST ensure that external transfer IDs are unique globally, i.e. across all machines and instances. Otherwise a connector could accidentally process two outgoing payments for one incoming payment.

account

account:String

The ILP Address of a local account.

Deprecated: Use from/to instead.

from

from:String

The ILP Address of the source or debit account.

to

to:String

The ILP Address of the destination or credit account.

ledger

ledger:String

ILP Address prefix of the ledger that this transfer is going through on.

amount

amount:String

A decimal amount, represented as a string. MUST be positive. The supported precision is defined by each ledger plugin and can be queried by the host via getInfo. The ledger plugin MUST throw an InsufficientPrecisionError if the given amount exceeds the supported level of precision.

data

data:Object

An arbitrary plain JavaScript object containing the data to be sent. The object MUST be serializable to JSON. Ledger plugins SHOULD treat this data as opaque. Typically, it will contain an ILP header.

If the data is too large, the ledger plugin MUST throw a MaximumDataSizeExceededError. If the data is too large only because the amount is insufficient, the ledger plugin MUST throw an InsufficientAmountError.

noteToSelf

noteToSelf:Object

An arbitrary plain JavaScript object containing details the host needs to persist with the transfer in order to be able to react to transfer events like condition fulfillment later.

Ledger plugins MAY attach the noteToSelf to the transfer and let the ledger store it. Otherwise it MAY use the store in order to persist this field. Regardless of the implementation, the ledger plugin MUST ensure that all instances of the transfer carry the same noteToSelf, even across different machines.

Ledger plugins MUST ensure that the data in the noteToSelf either isn't shared with any untrusted party or encrypted before it is shared.

executionCondition

executionCondition:String

A cryptographic challenge used for implementing holds. The underlying ledger MUST hold the transfer until the condition has been fulfilled or the expiresAt time has been reached.

Conditions are the base64url-encoded SHA-256 hash of a random, pseudo-random or deterministically generated 32-byte preimage called the fulfillment.

Ledger plugins that do not support holds MUST throw an HoldsNotSupportedError if this parameter is provided.

expiresAt

expiresAt:String

An ISO 8601 timestamp representing the expiry date for the transfer.

Ledger plugins that do not support holds or do not support expiries MUST throw an ExpiryNotSupportedError if this parameter is provided.

custom

custom:Object

Ledger plugins MAY use this object to accept and/or set additional fields for other features they support. The object MUST be serializable, i.e. only plain JSON types are allowed anywhere in the object or sub-objects.

Example
{
  id: '94adc29e-26cd-471b-987e-8d41e8773864',
  account: 'example.ledger.bob',
  from: 'example.ledger.bob',
  to: 'example.ledger.alice',
  ledger: 'example.ledger.',
  amount: '100',
  data: /* ... */,
  noteToSelf: /* ... */,
  custom: {
    alternateAccount: 'bob-savings',
    executionPriority: 9
  }
}

Class: Message

class Message

The Message class is used to describe local ledger message. All fields are required.

Fields
Type Name Description
String account ILP Address of the source or destination account (deprecated)
String from ILP Address of the source account
String to ILP Address of the destination account
String ledger ILP Address prefix of the ledger
Object data Data packet to be sent with the message

IncomingMessage

class IncomingMessage extends Message

IncomingMessage objects describe messages which are received by the ledger plugin. The account field refers to the local source account that the message originated from.

See Message for more information.

OutgoingMessage

class OutgoingMessage extends Message

OutgoingMessage objects describe messages which have been sent by the ledger plugin. The account field refers to the local destination account on the underlying ledger.

See Message for more information.

account

account:String

The ILP Address of a local account.

Deprecated: Use from/to instead.

from

from:String

The ILP Address of the source or debit account.

to

to:String

The ILP Address of the destination or credit account.

ledger

to:String

The ILP Prefix of the ledger being used to transfer the message.

data

data:Object

An arbitrary plain JavaScript object containing the data to be sent. The object MUST be serializable to JSON. Ledger plugins SHOULD treat this data as opaque.

If the data is too large, the ledger plugin MUST throw a MaximumDataSizeExceededError.

Example
{
  account: 'example.ledger.bob',
  from: 'example.ledger.alice',
  to: 'example.ledger.bob',
  ledger: 'example.ledger.',
  data: { /* ... */ }
}

Class: LedgerInfo

class LedgerInfo

Metadata describing the ledger. This data is returned by the getInfo method.

Fields
Type Name Description
String prefix The plugin's ILP address prefix
Number precision Total number of digits allowed
Number scale Digits allowed after decimal
String currencyCode ISO three-letter currency code
String currencySymbol UTF8 currency symbol
String[] connectors ILP addresses of recommended connectors

Fields

prefix

prefix:String

The ledger plugin's ILP address prefix. This is used to determine whether a given ILP address is local to this ledger plugin and thus can be reached using this plugin's sendTransfer method.

The prefix may be configured, automatically detected, or hard-coded, depending on the ledger. For example, a Bitcoin ledger plugin may have the address hard-coded, while a five-bells-ledger would use an API call to get the prefix.

precision

precision:Number

The total number of digits (base 10) of precision allowed by the ledger.

scale

scale:Number

The number of digits allowed after the decimal point.

currencyCode

currencyCode:String

The ISO 4217 currency code (if any) used by the ledger.

currencySymbol

currencySymbol:String

The currency symbol as one or more UTF8 characters.

connectors

connectors:String[]

The ILP addresses of recommended connectors.

Class: PluginOptions

class PluginOptions

Plugin options are passed in to the LedgerPlugin constructor when a plugin is being instantiated. The fields are ledger specific. Any fields which cannot be represented as strings are preceded with an underscore, and listed in the table below.

Special Fields
Type Name Description
Object _store Persistence layer callbacks

Fields

_store

_store:Object

Provides callback hooks to the host's persistence layer.

Persistence MAY be required for internal use by some ledger plugins. For this purpose hosts MAY be configured with a persistence layer.

Method names are based on the popular LevelUP/LevelDOWN packages.

Example
{
  // Store a value under a key
  put: (key, value) => {
    // Returns Promise.<null>
  },
  // Fetch a value by key
  get: (key) => {
    // Returns Promise.<Object>
  },
  // Delete a value by key
  del: (key) => {
    // Returns Promise.<null>
  }
}

Class: ConnectOptions

class ConnectOptions

Fields
Type Name Description
Number timeout milliseconds

Fields

id

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.