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
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> |
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 ) ⇒ |
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 |
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.
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.<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
.
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
.
ledgerPlugin.disconnect() ⇒ Promise.<null>
Unsubscribe from ledger events.
ledgerPlugin.isConnected() ⇒ Boolean
Query whether the plugin is currently connected.
ledgerPlugin.getInfo() ⇒ LedgerInfo
Retrieve some metadata about the ledger. Plugin must be connected, otherwise the function should throw.
{
"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
.
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.
us.fed.some-bank.my-account
ledgerPlugin.getBalance() ⇒ Promise.<String>
Return a decimal string representing the current balance. Plugin must be connected, otherwise the promise should reject.
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.
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.
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.
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.
Name | Type | Description |
---|---|---|
transfer | OutgoingTransfer |
Properties of the transfer to be created |
When sending transfers, the id, amount and to fields are required.
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.
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
.
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.
Name | Type | Description |
---|---|---|
message | OutgoingMessage |
Properties of the message to be created |
When sending messages, the to and data fields are required.
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.
p.sendMessage({
to: 'example.ledger.connector',
data: { foo: 'bar' }
})
For a detailed description of these properties, please see OutgoingMessage
.
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.
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.
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.
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.
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.
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.
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.
ledgerPlugin.on('incoming_message',
(
message:IncomingMessage,
) ⇒
)
Emitted when an incoming message arrives from the ledger.
ledgerPlugin.on('info_change',
(
info:LedgerInfo
) ⇒
)
Emitted any time the plugin's LedgerInfo
cache changes.
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.
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 |
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.
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.
id:String
External unique identifier used by the host.
For OutgoingTransfer
s, 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 IncomingTransfer
s, 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:String
The ILP Address of a local account.
Deprecated: Use from
/to
instead.
from:String
The ILP Address of the source or debit account.
to:String
The ILP Address of the destination or credit account.
ledger:String
ILP Address prefix of the ledger that this transfer is going through on.
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: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: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: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: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: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.
{
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
The Message
class is used to describe local ledger message. All fields are required.
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 |
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.
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:String
The ILP Address of a local account.
Deprecated: Use from
/to
instead.
from:String
The ILP Address of the source or debit account.
to:String
The ILP Address of the destination or credit account.
to:String
The ILP Prefix of the ledger being used to transfer the message.
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
.
{
account: 'example.ledger.bob',
from: 'example.ledger.alice',
to: 'example.ledger.bob',
ledger: 'example.ledger.',
data: { /* ... */ }
}
class LedgerInfo
Metadata describing the ledger. This data is returned by the getInfo
method.
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 |
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:Number
The total number of digits (base 10) of precision allowed by the ledger.
scale:Number
The number of digits allowed after the decimal point.
currencyCode:String
The ISO 4217 currency code (if any) used by the ledger.
currencySymbol:String
The currency symbol as one or more UTF8 characters.
connectors:String[]
The ILP addresses of recommended connectors.
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.
Type | Name | Description |
---|---|---|
Object |
_store | Persistence layer callbacks |
_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.
{
// 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
Type | Name | Description |
---|---|---|
Number |
timeout | 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.