From 97178e36936c96c97607b5c505ddb6173dc52c82 Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Mon, 13 Nov 2017 06:30:03 -0800 Subject: [PATCH 01/11] docs: start lpi2 on the basis of lpi1 --- .../xxxx-ledger-plugin-interface-2.md | 771 ++++++++++++++++++ 1 file changed, 771 insertions(+) create mode 100644 xxxx-ledger-plugin-interface-2/xxxx-ledger-plugin-interface-2.md diff --git a/xxxx-ledger-plugin-interface-2/xxxx-ledger-plugin-interface-2.md b/xxxx-ledger-plugin-interface-2/xxxx-ledger-plugin-interface-2.md new file mode 100644 index 00000000..c1c88669 --- /dev/null +++ b/xxxx-ledger-plugin-interface-2/xxxx-ledger-plugin-interface-2.md @@ -0,0 +1,771 @@ +--- +title: The Javascript Ledger Plugin Interface +draft: 2 +--- +# Javascript Ledger Plugin Interface + +The Interledger Protocol is a protocol suite for connecting blockchains and other ledgers. + +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](../0003-interledger-protocol/). + +## Class: LedgerPlugin +`class LedgerPlugin` + +###### Methods +| | Name | +|:--|:--| +| `new` | [**LedgerPlugin**](#new-ledgerplugin) ( opts ) | +| | [**connect**](#connect) ( options ) `⇒ Promise.` | +| | [**disconnect**](#disconnect) ( ) `⇒ Promise.` | +| | [**isConnected**](#isconnected) ( ) `⇒ Boolean` | +| | [**getInfo**](#getinfo) ( ) ⇒ [LedgerInfo](#class-ledgerinfo) | +| | [**getAccount**](#getaccount) ( ) `⇒ String` | +| | [**getBalance**](#getbalance) ( ) ⇒ Promise.<String> | +| | [**getFulfillment**](#getfulfillment) ( transferId ) ⇒ Promise.<String> | +| | [**sendTransfer**](#sendtransfer) ( transfer ) ⇒ Promise.<null> | +| | [**sendRequest**](#sendrequest) ( message ) ⇒ Promise.<[Message](#class-message)> | +| | [**fulfillCondition**](#fulfillcondition) ( transferId, fulfillment ) ⇒ Promise.<null> | +| | [**rejectIncomingTransfer**](#rejectincomingtransfer) ( transferId, reason ) ⇒ Promise.<null> | +| | [**registerRequestHandler**](#registerrequesthandler) ( requestHandler ) ⇒ null | +| | [**deregisterRequestHandler**](#deregisterrequesthandler) ( ) ⇒ null | + +###### Events +| Name | Handler | +|:--|:--| +| [**connect**](#event-connect) | `( ) ⇒` | +| [**disconnect**](#event-disconnect) | `( ) ⇒` | +| [**error**](#event-error) | `( ) ⇒` | +| [**incoming_transfer**](#event-_transfer) | ( transfer:[IncomingTransfer](#incomingtransfer) ) ⇒ | +| [**incoming_prepare**](#event-_prepare) | ( transfer:[IncomingTransfer](#incomingtransfer) ) ⇒ | +| [**incoming_fulfill**](#event-_fulfill) | ( transfer:[IncomingTransfer](#incomingtransfer), fulfillment:String ) ⇒ | +| [**incoming_reject**](#event-_reject) | ( transfer:[IncomingTransfer](#incomingtransfer), rejectionReason:[RejectionMessage](#class-rejectionmessage) ) ⇒ | +| [**incoming_cancel**](#event-_cancel) | ( transfer:[IncomingTransfer](#incomingtransfer), cancellationReason:[RejectionMessage](#class-rejectionmessage) ) ⇒ | +| [**incoming_request**](#event-_request) | ( message:[Message](#class-message) ) ⇒ | +| [**incoming_response**](#event-_response) | ( message:[Message](#class-message) ) ⇒ | +| [**outgoing_transfer**](#event-_transfer) | ( transfer:[outgoingTransfer](#outgoingtransfer) ) ⇒ | +| [**outgoing_prepare**](#event-_prepare) | ( transfer:[outgoingTransfer](#outgoingtransfer) ) ⇒ | +| [**outgoing_fulfill**](#event-_fulfill) | ( transfer:[outgoingTransfer](#outgoingtransfer), fulfillment:String ) ⇒ | +| [**outgoing_reject**](#event-_reject) | ( transfer:[outgoingTransfer](#outgoingtransfer), rejectionReason:[RejectionMessage](#class-rejectionmessage) ) ⇒ | +| [**outgoing_cancel**](#event-_cancel) | ( transfer:[outgoingTransfer](#outgoingtransfer), cancellationReason:[RejectionMessage](#class-rejectionmessage) ) ⇒ | +| [**outgoing_request**](#event-_request) | ( message:[Message](#class-message) ) ⇒ | +| [**outgoing_response**](#event-_response) | ( message:[Message](#class-message) ) ⇒ | +| [**info_change**](#event-info_change) | ( info:[LedgerInfo](#class-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](#class-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](#class-pluginoptions) | Object containing ledger-related settings. May contain plugin-specific fields. | + +###### Example +```js +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. + }, + // Fetch a value by key + get: (key) => { + // Returns Promise. + }, + // Delete a value by key + del: (key) => { + // Returns Promise. + } + } +}) +``` + +For a detailed description of these properties, please see [`PluginOptions`](#class-pluginoptions). + +### Connection Management + +#### connect +ledgerPlugin.connect( options:[ConnectOptions](#class-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-connect-) event. If the connection is lost, the ledger plugin SHOULD emit the [`disconnect`](#event-disconnect-) event. The plugin should ensure that the information returned by `getInfo` and `getAccount` is available and cached before emitting the [`connect`](#event-connect-) 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`. + +#### disconnect +ledgerPlugin.disconnect() ⇒ Promise.<null> + +Unsubscribe from ledger events. + +#### isConnected +ledgerPlugin.isConnected() ⇒ Boolean + +Query whether the plugin is currently connected. + +#### getInfo +ledgerPlugin.getInfo() ⇒ [LedgerInfo](#class-ledgerinfo) + +Retrieve some metadata about the ledger. Plugin must be connected, otherwise the function should throw. + +###### Example Return Value +```json +{ + "prefix": "us.fed.some-bank.", + "currencyCode": "USD", + "currencyScale": 4, + "connectors": [ "us.fed.some-bank.chloe" ] +} +``` + +For a detailed description of these properties, please see [`LedgerInfo`](#class-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 `.`, 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 (base-ten) integer string (`..., '-3', '-2', '-1', '0', '1', '2', '3', ...`) representing the current balance, in the ledger's base unit. For example, on a ledger with `currencyCode` 'USD' and `currencyScale` 6, +the base unit would be micro-dollars. +A balance of '1230000' should then be interpreted as equivalent to 1.23 US dollars. The maximum and minimum balance are up to the ledger to determine. 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. + +Rejects with `MissingFulfillmentError` if the transfer exists but is not yet +fulfilled. Rejects with `TransferNotFoundError` if no conditional transfer is found +with the given ID. Rejects with `AlreadyRolledBackError` if the transfer has been rolled back +and will not be fulfilled. Rejects with `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**:[Transfer](#class-transfer) ) ⇒ 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 reject with an error. +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 | [Transfer](#class-transfer) | Properties of the transfer to be created | + +When sending transfers, the [id](#id), [amount](#amount) and [to](#to) fields +are required. + +###### Returns +**`Promise.`** A promise which resolves when the transfer has been submitted (but not necessarily accepted.) + +Rejects with `InvalidFieldsError` if required fields are missing from the transfer or malformed. Rejects with `DuplicateIdError` if a transfer with +the given ID and different already exists. Rejects with `NotAcceptedError` if the transfer is rejected by the ledger due to insufficient balance or +a nonexistant destination account. + +###### Example +```js +p.sendTransfer({ + id: 'd86b0299-e2fa-4713-833a-96a6a75271b8', + to: 'example.ledger.connector', + amount: '10', + noteToSelf: {}, + executionCondition: '47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU', + expiresAt: '2016-05-18T12:00:00.000Z' +}) +``` + +For a detailed description of these properties, please see [`Class: Transfer`](#class-transfer). + +#### sendRequest +ledgerPlugin.sendRequest( **message**:[Message](#class-message) ) ⇒ Promise.<[Message](#class-message)> + +Plugin must be connected, otherwise the promise should reject. Sends a ledger-local message. Returns a promise for a response message from the other side. +If there is a problem with the structure or validity of the message, then `sendRequest` should reject with an error. + +Messaging is used by connectors for [quoting](../0008-interledger-quoting-protocol/) and broadcasting routes. + +###### Parameters +| Name | Type | Description | +|:--|:--|:--| +| message | [Message](#class-message) | Properties of the message to be created | + +When sending messages, the [to](#messageto) and [ilp](#messageilp) fields are required. + +###### Returns +**Promise.<[Message](#class-message)>** A promise which resolves when a response message has been received. + +Rejects with `InvalidFieldsError` if required fields are missing from the message or malformed. +Rejects with `NotAcceptedError` if the message is rejected by the ledger. +Rejects with `NoSubscriptionsError` if the message cannot be delivered because there is nobody listening to messages addressed to the given account. + + +###### Example +```js +p.sendRequest({ + to: 'example.ledger.connector', + ilp: '...base64url-encoded data...', + custom: { foo: 'bar' } +}) +``` + +For a detailed description of these properties, please see [`Message`](#class-message). + + +#### 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. + +Rejects with `InvalidFieldsError` if the fulfillment is malformed. Rejects with `TransferNotFoundError` if the fulfillment +if no conditional transfer with the given ID exists. Rejects with `AlreadyRolledBackError` if the transfer has already been +rolled back. Rejects with `NotAcceptedError` if the fulfillment is formatted correctly, but does not match the condition +of the specified transfer. Rejects with `TransferNotConditionalError` if transfer is not conditional. + +#### rejectIncomingTransfer +ledgerPlugin.rejectIncomingTransfer( **transferId**:String, **reason**:[RejectionMessage](#class-rejectionmessage) ) ⇒ Promise.<null> + +Reject an incoming transfer that is held pending the fulfillment of its `executionCondition` before the `expiresAt` time. `reason` MAY be supplied to provide details on why the transfer was rejected. + +Rejects with `TransferNotFoundError` if there is no conditional transfer with the +given ID. Rejects with `AlreadyFulfilledError` if the specified transfer has already been +fulfilled. Rejects with `NotAcceptedError` if you are not authorized +to reject the transfer (e.g. if you are the sender). Rejects with `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. + +#### registerRequestHandler +ledgerPlugin.registerRequestHandler( **requestHandler**: ( request: [Message](#class-message) ) ⇒ Promise<[Message](#class-message)> ) ⇒ null + +Set the callback which is used to handle incoming request messages. The callback expects one parameter (the request [Message](#class-message)) and returns a promise for the response [Message](#class-message). + +If a request handler is already set, this method throws a `RequestHandlerAlreadyRegisteredError`. In order to change the request handler, the old handler must first be removed via [`deregisterRequestHandler`](#deregisterRequestHandler). This is to ensure that handler are not overwritten by accident. + +#### deregisterRequestHandler +ledgerPlugin.deregisterRequestHandler( ) ⇒ null + +Removes the currently used request handler. This has the same effect as if [`registerRequestHandler`](#registerrequesthandler) had never been called. + +If not request handler is currently set, this method does nothing. + +### Event: `*_transfer` +ledgerPlugin.on('incoming_transfer', + ( + **transfer**:[Transfer](#class-transfer), + ) ⇒ +) +ledgerPlugin.on('outgoing_transfer', + ( + **transfer**:[Transfer](#class-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](#class-transfer), + ) ⇒ +) +ledgerPlugin.on('outgoing_prepare', + ( + **transfer**:[Transfer](#class-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](#event-_fulfill) or [*_cancel](#event-_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('incoming_fulfill', + ( + **transfer**:[Transfer](#class-transfer), + **fulfillment**:String + ) ⇒ +) +ledgerPlugin.on('outgoing_fulfill', + ( + **transfer**:[Transfer](#class-transfer), + **fulfillment**:String + ) ⇒ +) + +Emitted when an outgoing/incoming transfer with a condition is fulfilled. The `fulfillment` is provided as a base64url-encoded string. + +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('incoming_reject', + ( + **transfer**:[Transfer](#class-transfer), + **reason**:[RejectionMessage](#class-rejectionmessage) + ) ⇒ +) +ledgerPlugin.on('outgoing_reject', + ( + **transfer**:[Transfer](#class-transfer), + **reason**:[RejectionMessage](#class-rejectionmessage) + ) ⇒ +) + +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('incoming_cancel', + ( + **transfer**:[Transfer](#class-transfer), + **reason**:[RejectionMessage](#class-rejectionmessage) + ) ⇒ +) +ledgerPlugin.on('outgoing_cancel', + ( + **transfer**:[Transfer](#class-transfer), + **reason**:[RejectionMessage](#class-rejectionmessage) + ) ⇒ +) + +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: `*_request` +ledgerPlugin.on('incoming_request', + ( + **message**:[Message](#class-message), + ) ⇒ +) +ledgerPlugin.on('outgoing_request', + ( + **message**:[Message](#class-message), + ) ⇒ +) + +Emitted when an incoming request message arrives from another ledger participant (`incoming_request`) or one is sent (`outgoing_request`). + +Hosts MUST NOT use these events to respond to requests. In order to provide responses, provide a request handler via [`registerRequestHandler`](#registerRequestHandler). Note that there can only be one request handler active for a plugin at a time, but an unlimited number of (passive) event listeners. + +### Event: `*_response` +ledgerPlugin.on('incoming_response', + ( + **message**:[Message](#class-message), + ) ⇒ +) +ledgerPlugin.on('outgoing_response', + ( + **message**:[Message](#class-message), + ) ⇒ +) + +Emitted when a response message is sent (`outgoing_response`) or received (`incoming_response`). + +### Event: `info_change` +ledgerPlugin.on('info_change', + ( + **info**:[LedgerInfo](#class-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](#transferid) | UUID used as an external identifier | +| `String` | [from](#transferfrom) | ILP Address of the source account | +| `String` | [to](#transferto) | ILP Address of the destination account | +| `String` | [ledger](#transferledger) | ILP Address prefix of the ledger | +| `String` | [amount](#transferamount) | Integer transfer amount, in the ledger's base unit | +| `String` | [ilp](#transferilp) | Base64-encoded ILP packet | +| `Object` | [noteToSelf](#transfernotetoself) | Host-provided memo that should be stored with the transfer | +| `String` | [executionCondition](#transferexecutioncondition) | Cryptographic hold condition | +| `String` | [expiresAt](#transferexpiresat) | Expiry time of the cryptographic hold | +| `Object` | [custom](#transfercustom) | Object containing ledger plugin specific options | + +### Fields + +#### Transfer#id +**id**:String + +External unique identifier used by the host. + +The ID is always chosen by the sending 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. + +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. + +#### Transfer#account +**account**:String + +The ILP Address of a local account. + +**Deprecated:** Use [`from`](#from)/[`to`](#to) instead. + +#### Transfer#from +**from**:String + +The ILP Address of the source or debit account. + +#### Transfer#to +**to**:String + +The ILP Address of the destination or credit account. + +#### Transfer#ledger + **ledger**:String + +ILP Address prefix of the ledger that this transfer is going through on. + +#### Transfer#amount +**amount**:String + +An integer amount, represented as a string of base-ten digits. MUST be `>= 0` and `< 2^64`. + +#### Transfer#ilp +**ilp**:String + +An [ILP packet](../0003-interledger-protocol/), denoting the payment's final destination. + +If the `ilp` data is too large, the ledger plugin MUST reject with a `MaximumIlpDataSizeExceededError`. + +#### Transfer#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`](#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. + +#### Transfer#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 reject with an `HoldsNotSupportedError` if this parameter is provided. + +#### Transfer#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 reject with an `ExpiryNotSupportedError` if this parameter is provided. + +#### Transfer#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. + +If the `custom` data is too large, the ledger plugin MUST reject with a `MaximumCustomDataSizeExceededError`. + +###### Example +``` js +{ + id: '94adc29e-26cd-471b-987e-8d41e8773864', + account: 'example.ledger.bob', + from: 'example.ledger.bob', + to: 'example.ledger.alice', + ledger: 'example.ledger.', + amount: '100', + 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` | [id](#messageid) | Unique message identifier | +| `String` | [from](#messagefrom) | ILP Address of the source account | +| `String` | [to](#messageto) | ILP Address of the destination account | +| `String` | [ledger](#messageledger) | ILP Address prefix of the ledger | +| `String` | [ilp](#messageilp) | Base64-encoded ILP packet | +| `Object` | [custom](#messagecustom) | Object containing ledger plugin specific options | + +#### Message#id +**id**:String + +Unique message identifier chosen by the sending host. Request and response messages contain the same ID. + +#### Message#from +**from**:String + +The ILP Address of the source or debit account. + +#### Message#to +**to**:String + +The ILP Address of the destination or credit account. + +#### Message#ledger +**to**:String + +The ILP Prefix of the ledger being used to transfer the message. + +#### Message#ilp +**ilp**:String + +An [ILP packet](../0003-interledger-protocol/), used for communication among ledger participants. + +If the `ilp` data is too large, the ledger plugin MUST reject with a `MaximumIlpDataSizeExceededError`. + +#### Message#custom +**custom**:Object + +An arbitrary plain JavaScript object containing additional custom data to be sent. The object MUST be serializable to JSON. Ledger plugins SHOULD treat this data as opaque. + +If the `custom` data is too large, the ledger plugin MUST reject with a `MaximumCustomDataSizeExceededError`. + +###### Example +``` js +{ + account: 'example.ledger.bob', + from: 'example.ledger.alice', + to: 'example.ledger.bob', + ledger: 'example.ledger.', + ilp: '', + custom: { /* ... */ } +} +``` + +## Class: LedgerInfo +class LedgerInfo + +Metadata describing the ledger. This data is returned by the [`getInfo`](#getinfo) method. + +###### Fields +| Type | Name | Description | +|:--|:--|:--| +| `String` | [prefix](#ledgerinfoprefix) | The plugin's ILP address prefix | +| `String` | [currencyCode](#ledgerinfocurrencycode) | [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) three-letter currency code | +| `Number` | [currencyScale](#ledgerinfocurrencyscale) | Integer `(..., -2, -1, 0, 1, 2, ...)`, such that one of the ledger's base units equals `10^- ` | +| `String[]` | [connectors](#ledgerinfoconnectors) | ILP addresses of recommended connectors | +| `String` | [minBalance](#ledgerinfominbalance-optional) | Integer String, for instance `"0"`, indicating the minimum balance. Optional, defaults to zero. | +| `String` | [maxBalance](#ledgerinfomaxbalance-optional) | Integer String, for instance `"1000000000000"`, indicating the maximum balance. Optional, defaults to plus infinity. | + +### Fields + +#### LedgerInfo#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`](https://github.com/interledger/five-bells-ledger) would use an API call to get the prefix. + +#### LedgerInfo#currencyCode +**currencyCode**:String + +The [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) currency code (if any) used by the ledger. A custom all-caps three-letter code, not used by ISO 4217, otherwise. +Ledger administrators who choose a custom currency code MAY request a custom currency symbol for their chosen currency code be listed by software modules that map currency codes to currency symbols, +for instance on node package manager (npm) in the case of JavaScript. +To translate an integer amount or balance from the ledger, the currencyCode by itself is not enough. It has to be used in combination with the currencyScale (below) to determine how many +of the ledger's base units correspond to one currency unit. + +#### LedgerInfo#currencyScale +**currencyScale**:String + +The order of magnitude to express one full currency unit in ledger's base units. For instance, if the integer values represented on the ledger are to be interpreted as +dollar-cents (for the purpose of settling a user's account balance, for instance), then the ledger's +currencyCode is `USD` and its currencyScale is `2`. + +#### LedgerInfo#connectors +**connectors**:String[] + +The ILP addresses of recommended connectors. + +#### LedgerInfo#minBalance (OPTIONAL) +**minBalance**:String + +A minimum balance limits how much the ledger trusts the account holder. +This field is optional; when not present, the minimum balance should be assumed to be `0`. +When a plugin does return a `minBalance` field, it should be an Integer String, measured in the ledger's base unit, +comparable to the `balance` Integer Strings for which the `getBalance` method returns a Promise. +Applications using the plugin can expect transfers to fail if they would make the balance go below the minimum. + +#### LedgerInfo#maxBalance (OPTIONAL) +**maxBalance**:String + +A maximum balance limits how much the account holder trusts the ledger. +This field is optional; when not present, the maximum balance should be assumed to be `+Infinity`. +When a plugin does return a `maxBalance` field, it should be an Integer String, measured in the ledger's base unit, +comparable to the `balance` Integer Strings for which the `getBalance` method returns a Promise. +Applications using the plugin can expect transfers to fail if they would make the balance exceed the maximum. + +## Class: PluginOptions +class PluginOptions + +Plugin options are passed in to the [`LedgerPlugin`](#class-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](#pluginoptions-_store) | Persistence layer callbacks | + +### Fields + +#### PluginOptions#_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 +```js +{ + // Store a value under a key + put: (key, value) => { + // Returns Promise. + }, + // Fetch a value by key + get: (key) => { + // Returns Promise. + }, + // Delete a value by key + del: (key) => { + // Returns Promise. + } +} +``` + +## Class: ConnectOptions +class ConnectOptions + +###### Fields +| Type | Name | Description | +|:--|:--|:--| +| `Number` | [timeout](#connectoptions-timeout) | milliseconds | + +### Fields + +#### ConnectOptions#timeout +**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. + +## Class: RejectionMessage +class RejectionMessage + +###### Fields +| Field | Type | Description | +|:------------------|:------------|:------------| +| `code` | String | Machine-readable error code | +| `name` | String | Human-readable description of the error code | +| `message` | String | Description of the error | +| `triggered_by` | ILP Address or ILP Prefix | ILP address or ledger prefix from which the rejection originates | +| `forwarded_by` | ILP Address | (optional) The address of the last connector to forward the rejection | +| `triggered_at` | Timestamp | (optional) The time the rejection occurred. | +| `additional_info` | Object | Additional details about the error | From 843b56d48b53bb46596636d77ce9f78ce5dbc7d5 Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Mon, 13 Nov 2017 22:33:17 -0800 Subject: [PATCH 02/11] docs: lpi2 changes --- .../xxxx-ledger-plugin-interface-2.md | 629 +++++------------- 1 file changed, 180 insertions(+), 449 deletions(-) diff --git a/xxxx-ledger-plugin-interface-2/xxxx-ledger-plugin-interface-2.md b/xxxx-ledger-plugin-interface-2/xxxx-ledger-plugin-interface-2.md index c1c88669..8e2965b7 100644 --- a/xxxx-ledger-plugin-interface-2/xxxx-ledger-plugin-interface-2.md +++ b/xxxx-ledger-plugin-interface-2/xxxx-ledger-plugin-interface-2.md @@ -1,10 +1,10 @@ --- -title: The Javascript Ledger Plugin Interface -draft: 2 +title: The Javascript Ledger Plugin Interface Version 2 +draft: 1 --- -# Javascript Ledger Plugin Interface +# Javascript Ledger Plugin Interface Version 2 -The Interledger Protocol is a protocol suite for connecting blockchains and other ledgers. +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. @@ -19,19 +19,19 @@ This spec depends on the [ILP spec](../0003-interledger-protocol/). | | Name | |:--|:--| | `new` | [**LedgerPlugin**](#new-ledgerplugin) ( opts ) | -| | [**connect**](#connect) ( options ) `⇒ Promise.` | -| | [**disconnect**](#disconnect) ( ) `⇒ Promise.` | -| | [**isConnected**](#isconnected) ( ) `⇒ Boolean` | -| | [**getInfo**](#getinfo) ( ) ⇒ [LedgerInfo](#class-ledgerinfo) | -| | [**getAccount**](#getaccount) ( ) `⇒ String` | -| | [**getBalance**](#getbalance) ( ) ⇒ Promise.<String> | -| | [**getFulfillment**](#getfulfillment) ( transferId ) ⇒ Promise.<String> | -| | [**sendTransfer**](#sendtransfer) ( transfer ) ⇒ Promise.<null> | -| | [**sendRequest**](#sendrequest) ( message ) ⇒ Promise.<[Message](#class-message)> | -| | [**fulfillCondition**](#fulfillcondition) ( transferId, fulfillment ) ⇒ Promise.<null> | -| | [**rejectIncomingTransfer**](#rejectincomingtransfer) ( transferId, reason ) ⇒ Promise.<null> | -| | [**registerRequestHandler**](#registerrequesthandler) ( requestHandler ) ⇒ null | -| | [**deregisterRequestHandler**](#deregisterrequesthandler) ( ) ⇒ null | +| | [**connect**](#ledgerpluginconnect) ( options ) `⇒ Promise.` | +| | [**disconnect**](#ledgerplugindisconnect) ( ) `⇒ Promise.` | +| | [**isConnected**](#ledgerpluginisconnected) ( ) `⇒ Boolean` | +| | [**getInfo**](#ledgerplugingetinfo) ( ) ⇒ [LedgerInfo](#class-ledgerinfo) | +| | [**sendTransfer**](#ledgerpluginsendtransfer) ( transfer ) ⇒ Promise.<[FulfillmentInfo](#class-fulfillmentinfo)> | +| | [**registerTransferHandler**](#ledgerpluginregistertransferhandler) ( transferHandler ) ⇒ null | +| | [**deregisterTransferHandler**](#ledgerpluginderegistertransferhandler) ( ) ⇒ null | + +###### Constants + +| | Name | +|:--|:--| +| `static` | [**lpiVersion**](#version) = 2 | ###### Events | Name | Handler | @@ -39,20 +39,6 @@ This spec depends on the [ILP spec](../0003-interledger-protocol/). | [**connect**](#event-connect) | `( ) ⇒` | | [**disconnect**](#event-disconnect) | `( ) ⇒` | | [**error**](#event-error) | `( ) ⇒` | -| [**incoming_transfer**](#event-_transfer) | ( transfer:[IncomingTransfer](#incomingtransfer) ) ⇒ | -| [**incoming_prepare**](#event-_prepare) | ( transfer:[IncomingTransfer](#incomingtransfer) ) ⇒ | -| [**incoming_fulfill**](#event-_fulfill) | ( transfer:[IncomingTransfer](#incomingtransfer), fulfillment:String ) ⇒ | -| [**incoming_reject**](#event-_reject) | ( transfer:[IncomingTransfer](#incomingtransfer), rejectionReason:[RejectionMessage](#class-rejectionmessage) ) ⇒ | -| [**incoming_cancel**](#event-_cancel) | ( transfer:[IncomingTransfer](#incomingtransfer), cancellationReason:[RejectionMessage](#class-rejectionmessage) ) ⇒ | -| [**incoming_request**](#event-_request) | ( message:[Message](#class-message) ) ⇒ | -| [**incoming_response**](#event-_response) | ( message:[Message](#class-message) ) ⇒ | -| [**outgoing_transfer**](#event-_transfer) | ( transfer:[outgoingTransfer](#outgoingtransfer) ) ⇒ | -| [**outgoing_prepare**](#event-_prepare) | ( transfer:[outgoingTransfer](#outgoingtransfer) ) ⇒ | -| [**outgoing_fulfill**](#event-_fulfill) | ( transfer:[outgoingTransfer](#outgoingtransfer), fulfillment:String ) ⇒ | -| [**outgoing_reject**](#event-_reject) | ( transfer:[outgoingTransfer](#outgoingtransfer), rejectionReason:[RejectionMessage](#class-rejectionmessage) ) ⇒ | -| [**outgoing_cancel**](#event-_cancel) | ( transfer:[outgoingTransfer](#outgoingtransfer), cancellationReason:[RejectionMessage](#class-rejectionmessage) ) ⇒ | -| [**outgoing_request**](#event-_request) | ( message:[Message](#class-message) ) ⇒ | -| [**outgoing_response**](#event-_response) | ( message:[Message](#class-message) ) ⇒ | | [**info_change**](#event-info_change) | ( info:[LedgerInfo](#class-ledgerinfo) ) ⇒ | ###### Errors @@ -60,14 +46,8 @@ This spec depends on the [ILP spec](../0003-interledger-protocol/). |:--|:--| | [**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 | +| [**NoSubscriptionsError**]() | A transfer cannot be delivered because there are no active websockets | ### Instance Management @@ -112,71 +92,41 @@ For a detailed description of these properties, please see [`PluginOptions`](#cl ### Connection Management -#### connect +#### LedgerPlugin#connect ledgerPlugin.connect( options:[ConnectOptions](#class-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-connect-) event. If the connection is lost, the ledger plugin SHOULD emit the [`disconnect`](#event-disconnect-) event. The plugin should ensure that the information returned by `getInfo` and `getAccount` is available and cached before emitting the [`connect`](#event-connect-) event. +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-connect-) event. If the connection is lost, the ledger plugin SHOULD emit the [`disconnect`](#event-disconnect-) event. The plugin should ensure that the information returned by `getInfo` is available and cached before emitting the [`connect`](#event-connect-) 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`. -#### disconnect +#### LedgerPlugin#disconnect ledgerPlugin.disconnect() ⇒ Promise.<null> Unsubscribe from ledger events. -#### isConnected +#### LedgerPlugin#isConnected ledgerPlugin.isConnected() ⇒ Boolean Query whether the plugin is currently connected. -#### getInfo +#### LedgerPlugin#getInfo ledgerPlugin.getInfo() ⇒ [LedgerInfo](#class-ledgerinfo) -Retrieve some metadata about the ledger. Plugin must be connected, otherwise the function should throw. +Retrieve some metadata about the ledger. Throws `NotConnectedError` if the plugin is not connected to the ledger. ###### Example Return Value ```json { - "prefix": "us.fed.some-bank.", "currencyCode": "USD", - "currencyScale": 4, - "connectors": [ "us.fed.some-bank.chloe" ] + "currencyScale": 4 } ``` For a detailed description of these properties, please see [`LedgerInfo`](#class-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 `.`, 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 (base-ten) integer string (`..., '-3', '-2', '-1', '0', '1', '2', '3', ...`) representing the current balance, in the ledger's base unit. For example, on a ledger with `currencyCode` 'USD' and `currencyScale` 6, -the base unit would be micro-dollars. -A balance of '1230000' should then be interpreted as equivalent to 1.23 US dollars. The maximum and minimum balance are up to the ledger to determine. 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. - -Rejects with `MissingFulfillmentError` if the transfer exists but is not yet -fulfilled. Rejects with `TransferNotFoundError` if no conditional transfer is found -with the given ID. Rejects with `AlreadyRolledBackError` if the transfer has been rolled back -and will not be fulfilled. Rejects with `TransferNotConditionalError` if transfer is not -conditional. - #### Event: `connect` ledgerPlugin.on('connect', () ⇒ ) @@ -194,260 +144,58 @@ General event for fatal exceptions. Emitted when the plugin experienced an unexp ### 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**:[Transfer](#class-transfer) ) ⇒ Promise.<null> +#### LedgerPlugin#sendTransfer +ledgerPlugin.sendTransfer( **transfer**:[Transfer](#class-transfer) ) ⇒ Promise.<[FulfillmentInfo](#class-fulfillmentinfo)> -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 reject with an error. -If the transfer is accepted by the ledger, however, then -further errors will be in the form of `"reject"` events. +Initiates an account-local transfer. A transfer MUST contain an `amount` of zero or more and MAY have attached `data`. See the description of the [Transfer](#class-transfer) class below. -All plugins MUST implement zero-amount transfers, but some ledger plugins MAY -implement zero-amount transfers differently than other transfers. +All plugins MUST support amounts in a range from zero to some maximum. Plugins MAY implement zero-amount transfers differently than other transfers. ###### Parameters | Name | Type | Description | |:--|:--|:--| | transfer | [Transfer](#class-transfer) | Properties of the transfer to be created | -When sending transfers, the [id](#id), [amount](#amount) and [to](#to) fields -are required. +When sending transfers, the [amount](#transferamount), [destination](#transferdestination), [executionCondition](#transferexecutioncondition) and [expiresAt](#transferexpiresat) fields are required. [data](#transferdata) and [custom](#transfercusto) MAY be left undefined. ###### Returns -**`Promise.`** A promise which resolves when the transfer has been submitted (but not necessarily accepted.) +**`Promise.`** A promise which resolves when the transfer has been fulfilled/executed. + +Rejects with `InvalidFieldsError` if required fields are missing from the transfer or malformed. Rejects with `NotAcceptedError` if the transfer is rejected by the ledger due to insufficient balance or any other reason. Rejects with `NotConnectedError` if the plugin is not connected to the ledger. -Rejects with `InvalidFieldsError` if required fields are missing from the transfer or malformed. Rejects with `DuplicateIdError` if a transfer with -the given ID and different already exists. Rejects with `NotAcceptedError` if the transfer is rejected by the ledger due to insufficient balance or -a nonexistant destination account. +Rejects with [`InterledgerRejectionError`](#class-interledgerrejectionerror) if the other side rejects the transfer and attaches ILP rejection data. Rejects with `GenericRejectionError` if the other side rejects the transfer, but does not attach valid ILP rejection data. + +This method MAY reject with any arbitrary JavaScript error. ###### Example ```js p.sendTransfer({ - id: 'd86b0299-e2fa-4713-833a-96a6a75271b8', - to: 'example.ledger.connector', amount: '10', - noteToSelf: {}, + destination: 'example.us.acmebank.bob', executionCondition: '47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU', - expiresAt: '2016-05-18T12:00:00.000Z' + expiresAt: '2016-05-18T12:00:00.000Z', + data: Buffer.alloc(0), + custom: {} }) ``` For a detailed description of these properties, please see [`Class: Transfer`](#class-transfer). -#### sendRequest -ledgerPlugin.sendRequest( **message**:[Message](#class-message) ) ⇒ Promise.<[Message](#class-message)> - -Plugin must be connected, otherwise the promise should reject. Sends a ledger-local message. Returns a promise for a response message from the other side. -If there is a problem with the structure or validity of the message, then `sendRequest` should reject with an error. - -Messaging is used by connectors for [quoting](../0008-interledger-quoting-protocol/) and broadcasting routes. - -###### Parameters -| Name | Type | Description | -|:--|:--|:--| -| message | [Message](#class-message) | Properties of the message to be created | - -When sending messages, the [to](#messageto) and [ilp](#messageilp) fields are required. - -###### Returns -**Promise.<[Message](#class-message)>** A promise which resolves when a response message has been received. - -Rejects with `InvalidFieldsError` if required fields are missing from the message or malformed. -Rejects with `NotAcceptedError` if the message is rejected by the ledger. -Rejects with `NoSubscriptionsError` if the message cannot be delivered because there is nobody listening to messages addressed to the given account. - - -###### Example -```js -p.sendRequest({ - to: 'example.ledger.connector', - ilp: '...base64url-encoded data...', - custom: { foo: 'bar' } -}) -``` - -For a detailed description of these properties, please see [`Message`](#class-message). - - -#### 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. - -Rejects with `InvalidFieldsError` if the fulfillment is malformed. Rejects with `TransferNotFoundError` if the fulfillment -if no conditional transfer with the given ID exists. Rejects with `AlreadyRolledBackError` if the transfer has already been -rolled back. Rejects with `NotAcceptedError` if the fulfillment is formatted correctly, but does not match the condition -of the specified transfer. Rejects with `TransferNotConditionalError` if transfer is not conditional. - -#### rejectIncomingTransfer -ledgerPlugin.rejectIncomingTransfer( **transferId**:String, **reason**:[RejectionMessage](#class-rejectionmessage) ) ⇒ Promise.<null> - -Reject an incoming transfer that is held pending the fulfillment of its `executionCondition` before the `expiresAt` time. `reason` MAY be supplied to provide details on why the transfer was rejected. - -Rejects with `TransferNotFoundError` if there is no conditional transfer with the -given ID. Rejects with `AlreadyFulfilledError` if the specified transfer has already been -fulfilled. Rejects with `NotAcceptedError` if you are not authorized -to reject the transfer (e.g. if you are the sender). Rejects with `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. - -#### registerRequestHandler -ledgerPlugin.registerRequestHandler( **requestHandler**: ( request: [Message](#class-message) ) ⇒ Promise<[Message](#class-message)> ) ⇒ null - -Set the callback which is used to handle incoming request messages. The callback expects one parameter (the request [Message](#class-message)) and returns a promise for the response [Message](#class-message). - -If a request handler is already set, this method throws a `RequestHandlerAlreadyRegisteredError`. In order to change the request handler, the old handler must first be removed via [`deregisterRequestHandler`](#deregisterRequestHandler). This is to ensure that handler are not overwritten by accident. - -#### deregisterRequestHandler -ledgerPlugin.deregisterRequestHandler( ) ⇒ null - -Removes the currently used request handler. This has the same effect as if [`registerRequestHandler`](#registerrequesthandler) had never been called. - -If not request handler is currently set, this method does nothing. - -### Event: `*_transfer` -ledgerPlugin.on('incoming_transfer', - ( - **transfer**:[Transfer](#class-transfer), - ) ⇒ -) -ledgerPlugin.on('outgoing_transfer', - ( - **transfer**:[Transfer](#class-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](#class-transfer), - ) ⇒ -) -ledgerPlugin.on('outgoing_prepare', - ( - **transfer**:[Transfer](#class-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](#event-_fulfill) or [*_cancel](#event-_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('incoming_fulfill', - ( - **transfer**:[Transfer](#class-transfer), - **fulfillment**:String - ) ⇒ -) -ledgerPlugin.on('outgoing_fulfill', - ( - **transfer**:[Transfer](#class-transfer), - **fulfillment**:String - ) ⇒ -) - -Emitted when an outgoing/incoming transfer with a condition is fulfilled. The `fulfillment` is provided as a base64url-encoded string. - -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('incoming_reject', - ( - **transfer**:[Transfer](#class-transfer), - **reason**:[RejectionMessage](#class-rejectionmessage) - ) ⇒ -) -ledgerPlugin.on('outgoing_reject', - ( - **transfer**:[Transfer](#class-transfer), - **reason**:[RejectionMessage](#class-rejectionmessage) - ) ⇒ -) - -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('incoming_cancel', - ( - **transfer**:[Transfer](#class-transfer), - **reason**:[RejectionMessage](#class-rejectionmessage) - ) ⇒ -) -ledgerPlugin.on('outgoing_cancel', - ( - **transfer**:[Transfer](#class-transfer), - **reason**:[RejectionMessage](#class-rejectionmessage) - ) ⇒ -) - -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. +#### LedgerPlugin#registerTransferHandler +ledgerPlugin.registerTransferHandler( **transferHandler**: ( transfer: [Transfer](#class-transfer) ) ⇒ Promise<[Message](#class-message)> ) ⇒ null -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. +Set the callback which is used to handle incoming prepared transfers. The callback should expect one parameter (the [Transfer](#class-transfer)) and return a promise for the resulting [FulfillmentInfo](#class-fulfillmentinfo). If the transfer is rejected or an error occurs, the callback should reject the transfer. In general, the callback should behave as [`sendTransfer`](#ledgerpluginsendtransfer) does. -### Event: `*_request` -ledgerPlugin.on('incoming_request', - ( - **message**:[Message](#class-message), - ) ⇒ -) -ledgerPlugin.on('outgoing_request', - ( - **message**:[Message](#class-message), - ) ⇒ -) +If a transfer handler is already set, this method throws a `TransferHandlerAlreadyRegisteredError`. In order to change the transfer handler, the old handler must first be removed via [`deregisterTransferHandler`](#ledgerpluginderegistertransferhandler). This is to ensure that handlers are not overwritten by accident. -Emitted when an incoming request message arrives from another ledger participant (`incoming_request`) or one is sent (`outgoing_request`). +If an incoming transfer is received by the plugin, but no handler is registered, the plugin should reject the transfer. -Hosts MUST NOT use these events to respond to requests. In order to provide responses, provide a request handler via [`registerRequestHandler`](#registerRequestHandler). Note that there can only be one request handler active for a plugin at a time, but an unlimited number of (passive) event listeners. +#### LedgerPlugin#deregisterTransferHandler +ledgerPlugin.deregisterTransferHandler( ) ⇒ null -### Event: `*_response` -ledgerPlugin.on('incoming_response', - ( - **message**:[Message](#class-message), - ) ⇒ -) -ledgerPlugin.on('outgoing_response', - ( - **message**:[Message](#class-message), - ) ⇒ -) +Removes the currently used transfer handler. This has the same effect as if [`registerTransferHandler`](#ledgerpluginregistertransferhandler) had never been called. -Emitted when a response message is sent (`outgoing_response`) or received (`incoming_response`). +If no transfer handler is currently set, this method does nothing. ### Event: `info_change` ledgerPlugin.on('info_change', @@ -461,92 +209,91 @@ 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. +The `Transfer` class is used to describe transfers from the originator of the sendTransfer call towards some `destination`. All fields are required and MUST NOT be undefined. However, `amount` MAY be the value `'0'`, `data` MAY be an empty buffer and `custom` MAY be an empty object. ###### Fields | Type | Name | Description | |:--|:--|:--| -| `String` | [id](#transferid) | UUID used as an external identifier | -| `String` | [from](#transferfrom) | ILP Address of the source account | -| `String` | [to](#transferto) | ILP Address of the destination account | -| `String` | [ledger](#transferledger) | ILP Address prefix of the ledger | | `String` | [amount](#transferamount) | Integer transfer amount, in the ledger's base unit | -| `String` | [ilp](#transferilp) | Base64-encoded ILP packet | -| `Object` | [noteToSelf](#transfernotetoself) | Host-provided memo that should be stored with the transfer | +| `String` | [destination](#transferdestination) | ILP destination address | +| `Buffer` | [data](#transferdata) | Transport-layer data | | `String` | [executionCondition](#transferexecutioncondition) | Cryptographic hold condition | | `String` | [expiresAt](#transferexpiresat) | Expiry time of the cryptographic hold | | `Object` | [custom](#transfercustom) | Object containing ledger plugin specific options | -### Fields - -#### Transfer#id -**id**:String - -External unique identifier used by the host. - -The ID is always chosen by the sending 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. - -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. - -#### Transfer#account -**account**:String - -The ILP Address of a local account. - -**Deprecated:** Use [`from`](#from)/[`to`](#to) instead. - -#### Transfer#from -**from**:String - -The ILP Address of the source or debit account. +###### Example +``` js +{ + amount: '100', + destination: 'example.us.acmebank.bob', + executionCondition: 'I3TZF5S3n0-07JWH0s8ArsxPmVP6s-0d0SqxR6C3Ifk', + expiresAt: '2017-12-02T11:51:26.627Z', + data: Buffer.alloc(0), + custom: { + _alternateAccount: 'bob-savings', + executionPriority: 9 + } +} +``` -#### Transfer#to -**to**:String +### Fields -The ILP Address of the destination or credit account. +#### Transfer#amount +**amount**:String -#### Transfer#ledger - **ledger**:String +An integer amount, represented as a string of base-ten digits. MUST be in the range `0..9223372036854775807` (`>= 0` and `< 2^64`). -ILP Address prefix of the ledger that this transfer is going through on. +###### Example -#### Transfer#amount -**amount**:String +``` js +'100' +``` -An integer amount, represented as a string of base-ten digits. MUST be `>= 0` and `< 2^64`. +#### Transfer#destination +**destination**:String -#### Transfer#ilp -**ilp**:String +An [ILP address](../0003-interledger-protocol/), denoting the payment's final destination. Length MUST be in the range `1..1023` (`> 0` and `< 2^10`). -An [ILP packet](../0003-interledger-protocol/), denoting the payment's final destination. +If the `destination` address is too long, the ledger plugin MUST reject with a `MaximumIlpAddressLengthExceededError`. -If the `ilp` data is too large, the ledger plugin MUST reject with a `MaximumIlpDataSizeExceededError`. +###### Example -#### Transfer#noteToSelf -**noteToSelf**:Object +``` js +'example.us.acmebank.bob' +``` -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. +#### Transfer#data +**data**:Buffer -Ledger plugins MAY attach the `noteToSelf` to the transfer and let the ledger store it. Otherwise it MAY use the [`store`](#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. +Arbitrary transport-layer data that travels with the payment. Size MUST be in the range `0..32767` (`>= 0` and `< 2^15`). -Ledger plugins MUST ensure that the data in the `noteToSelf` either isn't shared with any untrusted party or encrypted before it is shared. +If the data is too large, the ledger plugin MUST reject with a `MaximumDataSizeExceededError`. #### Transfer#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. +Conditions are the base64url-encoded SHA-256 hash of a random or pseudo-random 32-byte preimage called the fulfillment. -Ledger plugins that do not support holds MUST reject with an `HoldsNotSupportedError` if this parameter is provided. +###### Example + +``` js +'I3TZF5S3n0-07JWH0s8ArsxPmVP6s-0d0SqxR6C3Ifk' +``` #### Transfer#expiresAt **expiresAt**:String -An ISO 8601 timestamp representing the expiry date for the transfer. +An ISO 8601 datetime string representing the expiry date for the transfer. Must include the UTC timezone identifier `Z`. -Ledger plugins that do not support holds or do not support expiries MUST reject with an `ExpiryNotSupportedError` if this parameter is provided. +This date MUST be in the future, otherwise the plugin MUST reject with a `UnacceptableExpiryError`. + +###### Example + +``` js +'2017-12-02T11:51:26.627Z' +``` #### Transfer#custom **custom**:Object @@ -555,84 +302,116 @@ Ledger plugins MAY use this object to accept and/or set additional fields for ot If the `custom` data is too large, the ledger plugin MUST reject with a `MaximumCustomDataSizeExceededError`. +Note that connectors MAY forward some fields of `custom` data from plugin to plugin, but generally are not expected to. All `custom` fields that were passed to `sendTransfer` MUST be passed to the transfer handler by the plugin on the receiving side. The only exception are properties which start with the underscore character (`_`), which MAY be consumed by the plugin and not passed on. + ###### Example ``` js { - id: '94adc29e-26cd-471b-987e-8d41e8773864', - account: 'example.ledger.bob', - from: 'example.ledger.bob', - to: 'example.ledger.alice', - ledger: 'example.ledger.', - amount: '100', - noteToSelf: /* ... */, - custom: { - alternateAccount: 'bob-savings', - executionPriority: 9 - } + // Starts with an underscore, consumed by plugin + _alternateAccount: 'bob-savings', + // Other property, passed on to next connector/receiver + executionPriority: 9 } ``` -## Class: Message -class Message +## Class: FulfillmentInfo +class FulfillmentInfo -The `Message` class is used to describe local ledger message. All fields are required. +The `FulfillmentInfo` class is used to describe the fulfillment and associated data that is returned when a transfer successfully completes. ###### Fields | Type | Name | Description | |:--|:--|:--| -| `String` | [id](#messageid) | Unique message identifier | -| `String` | [from](#messagefrom) | ILP Address of the source account | -| `String` | [to](#messageto) | ILP Address of the destination account | -| `String` | [ledger](#messageledger) | ILP Address prefix of the ledger | -| `String` | [ilp](#messageilp) | Base64-encoded ILP packet | -| `Object` | [custom](#messagecustom) | Object containing ledger plugin specific options | +| `String` | [fulfillment](#fulfillmentinfofulfillment) | Cryptographic hold fulfillment | +| `Buffer` | [data](#fulfillmentinfodata) | Transport-layer data | +| `Object` | [custom](#fulfillmentinfocustom) | Object containing ledger plugin specific options | -#### Message#id -**id**:String - -Unique message identifier chosen by the sending host. Request and response messages contain the same ID. - -#### Message#from -**from**:String - -The ILP Address of the source or debit account. - -#### Message#to -**to**:String +### Fields -The ILP Address of the destination or credit account. +#### FulfillmentInfo#fulfillment +**fulfillment**:String -#### Message#ledger -**to**:String +A cryptographic fulfillment that is the SHA-256 preimage of the hash provided as the [`executionCondition`](#transferexecutioncondition) when the transfer was first prepared. -The ILP Prefix of the ledger being used to transfer the message. +Fulfillments are base64url-encoded values with a length of exactly 32 bytes. -#### Message#ilp -**ilp**:String +Ledger plugins that do not support holds MUST reject with an `HoldsNotSupportedError` if this parameter is provided. -An [ILP packet](../0003-interledger-protocol/), used for communication among ledger participants. +#### FulfillmentInfo#data +**data**:Buffer -If the `ilp` data is too large, the ledger plugin MUST reject with a `MaximumIlpDataSizeExceededError`. +Transport-layer data that travels with the fulfillment. -#### Message#custom +#### FulfillmentInfo#custom **custom**:Object -An arbitrary plain JavaScript object containing additional custom data to be sent. The object MUST be serializable to JSON. Ledger plugins SHOULD treat this data as opaque. +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. If the `custom` data is too large, the ledger plugin MUST reject with a `MaximumCustomDataSizeExceededError`. +Note that connectors MAY forward some fields of `custom` data from plugin to plugin, but generally are not expected to. All `custom` fields that were passed to `sendTransfer` MUST be passed to the transfer handler by the plugin on the receiving side. The only exception are properties which start with the underscore character (`_`), which MAY be consumed by the plugin and not passed on. + ###### Example ``` js { - account: 'example.ledger.bob', - from: 'example.ledger.alice', - to: 'example.ledger.bob', - ledger: 'example.ledger.', - ilp: '', - custom: { /* ... */ } + custom: { + claim: '...', + fulfillmentLatency: 29 + } } ``` +## Class: InterledgerRejectionError +class InterledgerRejectionError + +An `InterledgerRejectionError` is a throwable object representing a rejection of an Interledger transfer. Implementations SHOULD use a class named `InterledgerRejectionError` which derives from JavaScript's built-in `Error`. However, other implementations MUST NOT rely on this and SHOULD use the `name` property to distinguish Interledger rejections from other error types. + +Plugins SHOULD NOT generally trigger `InterledgerRejectionError`s. Instead, the plugin SHOULD trigger a local error, such as the ones specified in this document and the hosting connector SHOULD create a suitable `InterledgerRejectionError` from the local error. This is because plugins are not necessarily aware of their ILP address and therefore may not be able to set `triggeredBy` correctly. Some plugins may have elements of a connector built-in if they are used with ledgers that don't natively support ILP. In that case, the plugin MAY trigger an `InterledgerRejectionError` since it is in effect acting as a connector. + +All fields described below MUST be present, however they MAY be empty. + +###### Fields +| Type | Name | Description | +|:--|:--|:--| +| `String` | [name](#interledgerrejectionerrorname) | `'InterledgerRejectionError'` | +| `String` | [message](#interledgerrejectionerrormessage) | Error message for local use | +| `Object` | [ilpRejection](#interledgerrejectionerrorilprejection) | Information about the ILP rejection | +| `String` | [ilpRejection.code](#interledgerrejectionerrorilprejection) | Machine-readable error code | +| `String` | [ilpRejection.name](#interledgerrejectionerrorilprejection) | Human-readable description of the error code | +| `String` | [ilpRejection.triggeredBy](#interledgerrejectionerrorilprejection) | ILP address from which the rejection originates | +| `String[]` | [ilpRejection.forwardedBy](#interledgerrejectionerrorilprejection) | The ILP addresses of zero or more connectors who forwarded the rejection | +| `String` | [ilpRejection.triggeredAt](#interledgerrejectionerrorilprejection) | ISO 8601 datetime. Describes when the rejection originally occurred. | +| `Object` | [ilpRejection.additionalInfo](#interledgerrejectionerrorilprejection) | Additional details about the error | + +### Fields + +#### InterledgerRejectionError#name +**name**:String + +JavaScript error name, always `'InterledgerRejectionError'`. This property SHOULD be used to distinguish InterledgerRejectionErrors from other error types, e.g.: + +``` js +try { + await plugin.sendTransfer(transfer) +} catch (err) [ + if (err && err.name === 'InterledgerRejectionError') { + // This is an Interledger rejection + } else { + // This is some other type of error + } +] +``` + +#### InterledgerRejectionError#message +**message**:String + +JavaScript error message. This field is generally only used locally and not passed on to other hosts. However, implementations MAY include a `message` property in `additionalInfo` which matches the local error message. Implementers SHOULD take care not to disclose secret keys or other private information via `additionalInfo`. + +#### InterledgerRejectionError#ilpRejection +**ilpRejection**:Object + +An object that contains more information about the Interledger error. Corresponds to the data in the [Interledger Error packet](../0003-interledger-protocol/). + ## Class: LedgerInfo class LedgerInfo @@ -641,22 +420,11 @@ Metadata describing the ledger. This data is returned by the [`getInfo`](#getinf ###### Fields | Type | Name | Description | |:--|:--|:--| -| `String` | [prefix](#ledgerinfoprefix) | The plugin's ILP address prefix | | `String` | [currencyCode](#ledgerinfocurrencycode) | [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) three-letter currency code | | `Number` | [currencyScale](#ledgerinfocurrencyscale) | Integer `(..., -2, -1, 0, 1, 2, ...)`, such that one of the ledger's base units equals `10^- ` | -| `String[]` | [connectors](#ledgerinfoconnectors) | ILP addresses of recommended connectors | -| `String` | [minBalance](#ledgerinfominbalance-optional) | Integer String, for instance `"0"`, indicating the minimum balance. Optional, defaults to zero. | -| `String` | [maxBalance](#ledgerinfomaxbalance-optional) | Integer String, for instance `"1000000000000"`, indicating the maximum balance. Optional, defaults to plus infinity. | ### Fields -#### LedgerInfo#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`](https://github.com/interledger/five-bells-ledger) would use an API call to get the prefix. - #### LedgerInfo#currencyCode **currencyCode**:String @@ -673,29 +441,6 @@ The order of magnitude to express one full currency unit in ledger's base units. dollar-cents (for the purpose of settling a user's account balance, for instance), then the ledger's currencyCode is `USD` and its currencyScale is `2`. -#### LedgerInfo#connectors -**connectors**:String[] - -The ILP addresses of recommended connectors. - -#### LedgerInfo#minBalance (OPTIONAL) -**minBalance**:String - -A minimum balance limits how much the ledger trusts the account holder. -This field is optional; when not present, the minimum balance should be assumed to be `0`. -When a plugin does return a `minBalance` field, it should be an Integer String, measured in the ledger's base unit, -comparable to the `balance` Integer Strings for which the `getBalance` method returns a Promise. -Applications using the plugin can expect transfers to fail if they would make the balance go below the minimum. - -#### LedgerInfo#maxBalance (OPTIONAL) -**maxBalance**:String - -A maximum balance limits how much the account holder trusts the ledger. -This field is optional; when not present, the maximum balance should be assumed to be `+Infinity`. -When a plugin does return a `maxBalance` field, it should be an Integer String, measured in the ledger's base unit, -comparable to the `balance` Integer Strings for which the `getBalance` method returns a Promise. -Applications using the plugin can expect transfers to fail if they would make the balance exceed the maximum. - ## Class: PluginOptions class PluginOptions @@ -755,17 +500,3 @@ The number of milliseconds that the plugin should spend trying to connect before If falsy, use the plugin's default timeout. If `Infinity`, there is no timeout. - -## Class: RejectionMessage -class RejectionMessage - -###### Fields -| Field | Type | Description | -|:------------------|:------------|:------------| -| `code` | String | Machine-readable error code | -| `name` | String | Human-readable description of the error code | -| `message` | String | Description of the error | -| `triggered_by` | ILP Address or ILP Prefix | ILP address or ledger prefix from which the rejection originates | -| `forwarded_by` | ILP Address | (optional) The address of the last connector to forward the rejection | -| `triggered_at` | Timestamp | (optional) The time the rejection occurred. | -| `additional_info` | Object | Additional details about the error | From a2520d0f8fc5fbe96ae39d390fcb8ff3f812d8a4 Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Wed, 13 Dec 2017 13:45:58 -0800 Subject: [PATCH 03/11] docs(lpi2): re-add ilp packet --- .../0000-ledger-plugin-interface-2.md | 60 ++++++------------- 1 file changed, 18 insertions(+), 42 deletions(-) rename xxxx-ledger-plugin-interface-2/xxxx-ledger-plugin-interface-2.md => 0000-ledger-plugin-interface-2/0000-ledger-plugin-interface-2.md (86%) diff --git a/xxxx-ledger-plugin-interface-2/xxxx-ledger-plugin-interface-2.md b/0000-ledger-plugin-interface-2/0000-ledger-plugin-interface-2.md similarity index 86% rename from xxxx-ledger-plugin-interface-2/xxxx-ledger-plugin-interface-2.md rename to 0000-ledger-plugin-interface-2/0000-ledger-plugin-interface-2.md index 8e2965b7..0800d956 100644 --- a/xxxx-ledger-plugin-interface-2/xxxx-ledger-plugin-interface-2.md +++ b/0000-ledger-plugin-interface-2/0000-ledger-plugin-interface-2.md @@ -147,7 +147,7 @@ General event for fatal exceptions. Emitted when the plugin experienced an unexp #### LedgerPlugin#sendTransfer ledgerPlugin.sendTransfer( **transfer**:[Transfer](#class-transfer) ) ⇒ Promise.<[FulfillmentInfo](#class-fulfillmentinfo)> -Initiates an account-local transfer. A transfer MUST contain an `amount` of zero or more and MAY have attached `data`. See the description of the [Transfer](#class-transfer) class below. +Initiates an account-local transfer. A transfer MUST contain an `amount` of zero or more and MAY have attached `ilp` data. See the description of the [Transfer](#class-transfer) class below. All plugins MUST support amounts in a range from zero to some maximum. Plugins MAY implement zero-amount transfers differently than other transfers. @@ -156,7 +156,7 @@ All plugins MUST support amounts in a range from zero to some maximum. Plugins M |:--|:--|:--| | transfer | [Transfer](#class-transfer) | Properties of the transfer to be created | -When sending transfers, the [amount](#transferamount), [destination](#transferdestination), [executionCondition](#transferexecutioncondition) and [expiresAt](#transferexpiresat) fields are required. [data](#transferdata) and [custom](#transfercusto) MAY be left undefined. +When sending transfers, all fields are required and MUST be provided, even if they contain an empty value, such as an empty object in `custom`. ###### Returns **`Promise.`** A promise which resolves when the transfer has been fulfilled/executed. @@ -171,10 +171,9 @@ This method MAY reject with any arbitrary JavaScript error. ```js p.sendTransfer({ amount: '10', - destination: 'example.us.acmebank.bob', executionCondition: '47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU', expiresAt: '2016-05-18T12:00:00.000Z', - data: Buffer.alloc(0), + ilp: Buffer.alloc(0), custom: {} }) ``` @@ -209,14 +208,13 @@ Emitted any time the plugin's `LedgerInfo` cache changes. ## Class: Transfer class Transfer -The `Transfer` class is used to describe transfers from the originator of the sendTransfer call towards some `destination`. All fields are required and MUST NOT be undefined. However, `amount` MAY be the value `'0'`, `data` MAY be an empty buffer and `custom` MAY be an empty object. +The `Transfer` class is used to describe transfers from the originator of the sendTransfer call towards some destination via Interledger. All fields are required and MUST NOT be undefined. However, `amount` MAY be the value `'0'`, `ilp` MAY be an empty buffer and `custom` MAY be an empty object. ###### Fields | Type | Name | Description | |:--|:--|:--| | `String` | [amount](#transferamount) | Integer transfer amount, in the ledger's base unit | -| `String` | [destination](#transferdestination) | ILP destination address | -| `Buffer` | [data](#transferdata) | Transport-layer data | +| `Buffer` | [ilp](#transferilp) | Attached data (ILP packet) | | `String` | [executionCondition](#transferexecutioncondition) | Cryptographic hold condition | | `String` | [expiresAt](#transferexpiresat) | Expiry time of the cryptographic hold | | `Object` | [custom](#transfercustom) | Object containing ledger plugin specific options | @@ -225,10 +223,9 @@ The `Transfer` class is used to describe transfers from the originator of the se ``` js { amount: '100', - destination: 'example.us.acmebank.bob', executionCondition: 'I3TZF5S3n0-07JWH0s8ArsxPmVP6s-0d0SqxR6C3Ifk', expiresAt: '2017-12-02T11:51:26.627Z', - data: Buffer.alloc(0), + ilp: Buffer.alloc(0), custom: { _alternateAccount: 'bob-savings', executionPriority: 9 @@ -249,25 +246,12 @@ An integer amount, represented as a string of base-ten digits. MUST be in the ra '100' ``` -#### Transfer#destination -**destination**:String +#### Transfer#ilp +**ilp**:Buffer -An [ILP address](../0003-interledger-protocol/), denoting the payment's final destination. Length MUST be in the range `1..1023` (`> 0` and `< 2^10`). +An [ILP packet](https://interledger.org/rfcs/0003-interledger-protocol/draft-4.html#specification), denoting the payment's final destination. Plugins SHOULD support a size (in bytes) in the range `0..65535` (`>= 0` and `< 2^16`). Note that ILP packets are currently smaller than that, but larger packets may be used in the future due to extensions. -If the `destination` address is too long, the ledger plugin MUST reject with a `MaximumIlpAddressLengthExceededError`. - -###### Example - -``` js -'example.us.acmebank.bob' -``` - -#### Transfer#data -**data**:Buffer - -Arbitrary transport-layer data that travels with the payment. Size MUST be in the range `0..32767` (`>= 0` and `< 2^15`). - -If the data is too large, the ledger plugin MUST reject with a `MaximumDataSizeExceededError`. +If the `ilp` data is too large, the ledger plugin MUST reject with a `MaximumIlpDataSizeExceededError`. #### Transfer#executionCondition **executionCondition**:String @@ -323,7 +307,7 @@ The `FulfillmentInfo` class is used to describe the fulfillment and associated d | Type | Name | Description | |:--|:--|:--| | `String` | [fulfillment](#fulfillmentinfofulfillment) | Cryptographic hold fulfillment | -| `Buffer` | [data](#fulfillmentinfodata) | Transport-layer data | +| `Buffer` | [ilp](#fulfillmentinfoilp) | Attached data (ILP packet) | | `Object` | [custom](#fulfillmentinfocustom) | Object containing ledger plugin specific options | ### Fields @@ -337,18 +321,16 @@ Fulfillments are base64url-encoded values with a length of exactly 32 bytes. Ledger plugins that do not support holds MUST reject with an `HoldsNotSupportedError` if this parameter is provided. -#### FulfillmentInfo#data -**data**:Buffer +#### FulfillmentInfo#ilp +**ilp**:Buffer -Transport-layer data that travels with the fulfillment. +An [ILP packet](https://interledger.org/rfcs/0003-interledger-protocol/draft-4.html#specification), containing any data attached to the fulfillment. Plugins SHOULD support a size (in bytes) in the range `0..65535` (`>= 0` and `< 2^16`). Note that ILP packets are currently smaller than that, but larger packets may be used in the future due to extensions. #### FulfillmentInfo#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. -If the `custom` data is too large, the ledger plugin MUST reject with a `MaximumCustomDataSizeExceededError`. - Note that connectors MAY forward some fields of `custom` data from plugin to plugin, but generally are not expected to. All `custom` fields that were passed to `sendTransfer` MUST be passed to the transfer handler by the plugin on the receiving side. The only exception are properties which start with the underscore character (`_`), which MAY be consumed by the plugin and not passed on. ###### Example @@ -375,13 +357,7 @@ All fields described below MUST be present, however they MAY be empty. |:--|:--|:--| | `String` | [name](#interledgerrejectionerrorname) | `'InterledgerRejectionError'` | | `String` | [message](#interledgerrejectionerrormessage) | Error message for local use | -| `Object` | [ilpRejection](#interledgerrejectionerrorilprejection) | Information about the ILP rejection | -| `String` | [ilpRejection.code](#interledgerrejectionerrorilprejection) | Machine-readable error code | -| `String` | [ilpRejection.name](#interledgerrejectionerrorilprejection) | Human-readable description of the error code | -| `String` | [ilpRejection.triggeredBy](#interledgerrejectionerrorilprejection) | ILP address from which the rejection originates | -| `String[]` | [ilpRejection.forwardedBy](#interledgerrejectionerrorilprejection) | The ILP addresses of zero or more connectors who forwarded the rejection | -| `String` | [ilpRejection.triggeredAt](#interledgerrejectionerrorilprejection) | ISO 8601 datetime. Describes when the rejection originally occurred. | -| `Object` | [ilpRejection.additionalInfo](#interledgerrejectionerrorilprejection) | Additional details about the error | +| `Buffer` | [ilp](#interledgerrejectionerrorilp) | Attached data (ILP packet) | ### Fields @@ -407,10 +383,10 @@ try { JavaScript error message. This field is generally only used locally and not passed on to other hosts. However, implementations MAY include a `message` property in `additionalInfo` which matches the local error message. Implementers SHOULD take care not to disclose secret keys or other private information via `additionalInfo`. -#### InterledgerRejectionError#ilpRejection -**ilpRejection**:Object +#### InterledgerRejectionError#ilp +**ilp**:Buffer -An object that contains more information about the Interledger error. Corresponds to the data in the [Interledger Error packet](../0003-interledger-protocol/). +An [ILP packet](https://interledger.org/rfcs/0003-interledger-protocol/draft-4.html#specification), containing information on why the payment has been rejected and by whom. Plugins SHOULD support a size (in bytes) in the range `0..65535` (`>= 0` and `< 2^16`). Note that ILP packets are currently smaller than that, but larger packets may be used in the future due to extensions. ## Class: LedgerInfo class LedgerInfo From 02503018914b3729066e39d9ccdba372054eff4d Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Wed, 13 Dec 2017 14:26:59 -0800 Subject: [PATCH 04/11] docs(lpi2): address most review comments --- .../0000-ledger-plugin-interface-2.md | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/0000-ledger-plugin-interface-2/0000-ledger-plugin-interface-2.md b/0000-ledger-plugin-interface-2/0000-ledger-plugin-interface-2.md index 0800d956..270ee5d8 100644 --- a/0000-ledger-plugin-interface-2/0000-ledger-plugin-interface-2.md +++ b/0000-ledger-plugin-interface-2/0000-ledger-plugin-interface-2.md @@ -31,7 +31,7 @@ This spec depends on the [ILP spec](../0003-interledger-protocol/). | | Name | |:--|:--| -| `static` | [**lpiVersion**](#version) = 2 | +| `static` | [**version**](#ledgerpluginversion) = 2 | ###### Events | Name | Handler | @@ -39,7 +39,6 @@ This spec depends on the [ILP spec](../0003-interledger-protocol/). | [**connect**](#event-connect) | `( ) ⇒` | | [**disconnect**](#event-disconnect) | `( ) ⇒` | | [**error**](#event-error) | `( ) ⇒` | -| [**info_change**](#event-info_change) | ( info:[LedgerInfo](#class-ledgerinfo) ) ⇒ | ###### Errors | Name | Description | @@ -90,6 +89,11 @@ const ledgerPlugin = new LedgerPlugin({ For a detailed description of these properties, please see [`PluginOptions`](#class-pluginoptions). +#### LedgerPlugin.version +LedgerPlugin.**version**:Number + +Always `2` for this version of the Ledger Plugin Interface. + ### Connection Management #### LedgerPlugin#connect @@ -181,7 +185,7 @@ p.sendTransfer({ For a detailed description of these properties, please see [`Class: Transfer`](#class-transfer). #### LedgerPlugin#registerTransferHandler -ledgerPlugin.registerTransferHandler( **transferHandler**: ( transfer: [Transfer](#class-transfer) ) ⇒ Promise<[Message](#class-message)> ) ⇒ null +ledgerPlugin.registerTransferHandler( **transferHandler**: ( transfer: [Transfer](#class-transfer) ) ⇒ Promise<[FulfillmentInfo](#class-fulfillmentinfo)> ) ⇒ null Set the callback which is used to handle incoming prepared transfers. The callback should expect one parameter (the [Transfer](#class-transfer)) and return a promise for the resulting [FulfillmentInfo](#class-fulfillmentinfo). If the transfer is rejected or an error occurs, the callback should reject the transfer. In general, the callback should behave as [`sendTransfer`](#ledgerpluginsendtransfer) does. @@ -196,15 +200,6 @@ Removes the currently used transfer handler. This has the same effect as if [`re If no transfer handler is currently set, this method does nothing. -### Event: `info_change` -ledgerPlugin.on('info_change', - ( - **info**:[LedgerInfo](#class-ledgerinfo) - ) ⇒ -) - -Emitted any time the plugin's `LedgerInfo` cache changes. - ## Class: Transfer class Transfer @@ -319,8 +314,6 @@ A cryptographic fulfillment that is the SHA-256 preimage of the hash provided as Fulfillments are base64url-encoded values with a length of exactly 32 bytes. -Ledger plugins that do not support holds MUST reject with an `HoldsNotSupportedError` if this parameter is provided. - #### FulfillmentInfo#ilp **ilp**:Buffer @@ -331,7 +324,9 @@ An [ILP packet](https://interledger.org/rfcs/0003-interledger-protocol/draft-4.h 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. -Note that connectors MAY forward some fields of `custom` data from plugin to plugin, but generally are not expected to. All `custom` fields that were passed to `sendTransfer` MUST be passed to the transfer handler by the plugin on the receiving side. The only exception are properties which start with the underscore character (`_`), which MAY be consumed by the plugin and not passed on. +Note that connectors MAY forward some fields of `custom` data from plugin to plugin, but generally are not expected to. All `custom` fields that were set in `FulfillmentInfo#custom` on the receiving side MUST be set in `FulfillmentInfo#custom` when it is resolved by the promise on the sending side. The only exception are properties which start with the underscore character (`_`), which MAY be consumed by the plugin and not passed on. + +All custom fields that were set in FulfillmentInfo#custom on the receiving side MUST be set in FulfillmentInfo#custom when it is resolved by the promise on the sending side. ###### Example ``` js From 4a2a50a44974ae4d5058f7546873a0b4a99c5090 Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Wed, 13 Dec 2017 15:18:51 -0800 Subject: [PATCH 05/11] docs(lpi2): condition and fulfillment as buffers --- .../0000-ledger-plugin-interface-2.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/0000-ledger-plugin-interface-2/0000-ledger-plugin-interface-2.md b/0000-ledger-plugin-interface-2/0000-ledger-plugin-interface-2.md index 270ee5d8..eaaa3dde 100644 --- a/0000-ledger-plugin-interface-2/0000-ledger-plugin-interface-2.md +++ b/0000-ledger-plugin-interface-2/0000-ledger-plugin-interface-2.md @@ -175,7 +175,7 @@ This method MAY reject with any arbitrary JavaScript error. ```js p.sendTransfer({ amount: '10', - executionCondition: '47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU', + executionCondition: Buffer.from('47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU', 'base64'), expiresAt: '2016-05-18T12:00:00.000Z', ilp: Buffer.alloc(0), custom: {} @@ -210,7 +210,7 @@ The `Transfer` class is used to describe transfers from the originator of the se |:--|:--|:--| | `String` | [amount](#transferamount) | Integer transfer amount, in the ledger's base unit | | `Buffer` | [ilp](#transferilp) | Attached data (ILP packet) | -| `String` | [executionCondition](#transferexecutioncondition) | Cryptographic hold condition | +| `Buffer` | [executionCondition](#transferexecutioncondition) | Cryptographic hold condition | | `String` | [expiresAt](#transferexpiresat) | Expiry time of the cryptographic hold | | `Object` | [custom](#transfercustom) | Object containing ledger plugin specific options | @@ -218,7 +218,7 @@ The `Transfer` class is used to describe transfers from the originator of the se ``` js { amount: '100', - executionCondition: 'I3TZF5S3n0-07JWH0s8ArsxPmVP6s-0d0SqxR6C3Ifk', + executionCondition: Buffer.from('I3TZF5S3n0-07JWH0s8ArsxPmVP6s-0d0SqxR6C3Ifk', 'base64'), expiresAt: '2017-12-02T11:51:26.627Z', ilp: Buffer.alloc(0), custom: { @@ -249,16 +249,16 @@ An [ILP packet](https://interledger.org/rfcs/0003-interledger-protocol/draft-4.h If the `ilp` data is too large, the ledger plugin MUST reject with a `MaximumIlpDataSizeExceededError`. #### Transfer#executionCondition -**executionCondition**:String +**executionCondition**:Buffer 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 or pseudo-random 32-byte preimage called the fulfillment. +The `executionCondition` is a Node.js `Buffer` containing the 32-byte SHA-256 hash of a random or pseudo-random 32-byte preimage called the `fulfillment`. ###### Example ``` js -'I3TZF5S3n0-07JWH0s8ArsxPmVP6s-0d0SqxR6C3Ifk' +Buffer.from('I3TZF5S3n0-07JWH0s8ArsxPmVP6s-0d0SqxR6C3Ifk', 'base64') ``` #### Transfer#expiresAt @@ -308,11 +308,11 @@ The `FulfillmentInfo` class is used to describe the fulfillment and associated d ### Fields #### FulfillmentInfo#fulfillment -**fulfillment**:String +**fulfillment**:Buffer A cryptographic fulfillment that is the SHA-256 preimage of the hash provided as the [`executionCondition`](#transferexecutioncondition) when the transfer was first prepared. -Fulfillments are base64url-encoded values with a length of exactly 32 bytes. +The `fulfillment` is a Node.js `Buffer` with a length of exactly 32 bytes. #### FulfillmentInfo#ilp **ilp**:Buffer From 433081cc10f8f8b16ac7420255818d66f895e40e Mon Sep 17 00:00:00 2001 From: Ben Sharafian Date: Thu, 14 Dec 2017 14:59:17 -0800 Subject: [PATCH 06/11] docs: add genericrejectionerror --- .../0000-ledger-plugin-interface-2.md | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/0000-ledger-plugin-interface-2/0000-ledger-plugin-interface-2.md b/0000-ledger-plugin-interface-2/0000-ledger-plugin-interface-2.md index eaaa3dde..2428e99c 100644 --- a/0000-ledger-plugin-interface-2/0000-ledger-plugin-interface-2.md +++ b/0000-ledger-plugin-interface-2/0000-ledger-plugin-interface-2.md @@ -167,7 +167,7 @@ When sending transfers, all fields are required and MUST be provided, even if th Rejects with `InvalidFieldsError` if required fields are missing from the transfer or malformed. Rejects with `NotAcceptedError` if the transfer is rejected by the ledger due to insufficient balance or any other reason. Rejects with `NotConnectedError` if the plugin is not connected to the ledger. -Rejects with [`InterledgerRejectionError`](#class-interledgerrejectionerror) if the other side rejects the transfer and attaches ILP rejection data. Rejects with `GenericRejectionError` if the other side rejects the transfer, but does not attach valid ILP rejection data. +Rejects with [`InterledgerRejectionError`](#class-interledgerrejectionerror) if the other side rejects the transfer and attaches ILP rejection data. Rejects with [`GenericRejectionError`](#class-genericrejectionerror) if the other side rejects the transfer, but does not attach valid ILP rejection data. One example for when a GenericRejectionError may be used is if a transfer times out. This method MAY reject with any arbitrary JavaScript error. @@ -383,6 +383,43 @@ JavaScript error message. This field is generally only used locally and not pass An [ILP packet](https://interledger.org/rfcs/0003-interledger-protocol/draft-4.html#specification), containing information on why the payment has been rejected and by whom. Plugins SHOULD support a size (in bytes) in the range `0..65535` (`>= 0` and `< 2^16`). Note that ILP packets are currently smaller than that, but larger packets may be used in the future due to extensions. +## Class: GenericRejectionError +class GenericRejectionError + +A `GenericRejectionError` is a throwable object representing a non-Interledger rejection of an Interledger transfer. Implementations SHOULD use a class named `GenericRejectionError` which derives from JavaScript's built-in `Error`. However, other implementations MUST NOT rely on this and SHOULD use the `name` property to distinguish Interledger rejections from other error types. `GenericRejectionError`s SHOULD NOT generally be triggered by anything except for the plugin. It can be used for local errors, such as timeouts or insufficient liquidity. + +All fields described below MUST be present, however they MAY be empty. + +###### Fields +| Type | Name | Description | +|:--|:--|:--| +| `String` | [name](#interledgerrejectionerrorname) | `'GenericRejectionError'` | +| `String` | [message](#interledgerrejectionerrormessage) | Error message for local use | + +### Fields + +#### GenericRejectionError#name +**name**:String + +JavaScript error name, always `'GenericRejectionError'`. This property SHOULD be used to distinguish InterledgerRejectionErrors from other error types, e.g.: + +``` js +try { + await plugin.sendTransfer(transfer) +} catch (err) [ + if (err && err.name === 'GenericRejectionError') { + // This is a non-Interledger rejection + } else { + // This is an Interledger error + } +] +``` + +#### GenericRejectionError#message +**message**:String + +JavaScript error message. This field is generally only used locally and not passed on to other hosts. However, implementations MAY include a `message` property in `additionalInfo` which matches the local error message. Implementers SHOULD take care not to disclose secret keys or other private information via `additionalInfo`. + ## Class: LedgerInfo class LedgerInfo From 8b7649312f293c47f47633655a3b86a4c3c7b720 Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Thu, 14 Dec 2017 15:20:31 -0800 Subject: [PATCH 07/11] docs(lpi2): remove references to additionalInfo --- .../0000-ledger-plugin-interface-2.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/0000-ledger-plugin-interface-2/0000-ledger-plugin-interface-2.md b/0000-ledger-plugin-interface-2/0000-ledger-plugin-interface-2.md index 2428e99c..5034f073 100644 --- a/0000-ledger-plugin-interface-2/0000-ledger-plugin-interface-2.md +++ b/0000-ledger-plugin-interface-2/0000-ledger-plugin-interface-2.md @@ -376,7 +376,7 @@ try { #### InterledgerRejectionError#message **message**:String -JavaScript error message. This field is generally only used locally and not passed on to other hosts. However, implementations MAY include a `message` property in `additionalInfo` which matches the local error message. Implementers SHOULD take care not to disclose secret keys or other private information via `additionalInfo`. +JavaScript error message. This field is generally only used locally and not passed on to other hosts. #### InterledgerRejectionError#ilp **ilp**:Buffer @@ -418,7 +418,7 @@ try { #### GenericRejectionError#message **message**:String -JavaScript error message. This field is generally only used locally and not passed on to other hosts. However, implementations MAY include a `message` property in `additionalInfo` which matches the local error message. Implementers SHOULD take care not to disclose secret keys or other private information via `additionalInfo`. +JavaScript error message. This field is generally only used locally and not passed on to other hosts. ## Class: LedgerInfo class LedgerInfo From baf5f5c686482cbefd6e28f3af49297a75ff071f Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Fri, 22 Dec 2017 13:44:26 -0800 Subject: [PATCH 08/11] docs(lpi2): remove getInfo This information will now move to the connector configuration. If we want `ilp` client to auto-configure, we can create a participant-level protocol like IL-DCP. --- .../0000-ledger-plugin-interface-2.md | 47 +------------------ 1 file changed, 1 insertion(+), 46 deletions(-) diff --git a/0000-ledger-plugin-interface-2/0000-ledger-plugin-interface-2.md b/0000-ledger-plugin-interface-2/0000-ledger-plugin-interface-2.md index 5034f073..5dfb1bae 100644 --- a/0000-ledger-plugin-interface-2/0000-ledger-plugin-interface-2.md +++ b/0000-ledger-plugin-interface-2/0000-ledger-plugin-interface-2.md @@ -22,7 +22,6 @@ This spec depends on the [ILP spec](../0003-interledger-protocol/). | | [**connect**](#ledgerpluginconnect) ( options ) `⇒ Promise.` | | | [**disconnect**](#ledgerplugindisconnect) ( ) `⇒ Promise.` | | | [**isConnected**](#ledgerpluginisconnected) ( ) `⇒ Boolean` | -| | [**getInfo**](#ledgerplugingetinfo) ( ) ⇒ [LedgerInfo](#class-ledgerinfo) | | | [**sendTransfer**](#ledgerpluginsendtransfer) ( transfer ) ⇒ Promise.<[FulfillmentInfo](#class-fulfillmentinfo)> | | | [**registerTransferHandler**](#ledgerpluginregistertransferhandler) ( transferHandler ) ⇒ null | | | [**deregisterTransferHandler**](#ledgerpluginderegistertransferhandler) ( ) ⇒ null | @@ -101,7 +100,7 @@ Always `2` for this version of the Ledger Plugin Interface. `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-connect-) event. If the connection is lost, the ledger plugin SHOULD emit the [`disconnect`](#event-disconnect-) event. The plugin should ensure that the information returned by `getInfo` is available and cached before emitting the [`connect`](#event-connect-) event. +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-connect-) event. If the connection is lost, the ledger plugin SHOULD emit the [`disconnect`](#event-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`. @@ -116,21 +115,6 @@ Unsubscribe from ledger events. Query whether the plugin is currently connected. -#### LedgerPlugin#getInfo -ledgerPlugin.getInfo() ⇒ [LedgerInfo](#class-ledgerinfo) - -Retrieve some metadata about the ledger. Throws `NotConnectedError` if the plugin is not connected to the ledger. - -###### Example Return Value -```json -{ - "currencyCode": "USD", - "currencyScale": 4 -} -``` - -For a detailed description of these properties, please see [`LedgerInfo`](#class-ledgerinfo). - #### Event: `connect` ledgerPlugin.on('connect', () ⇒ ) @@ -420,35 +404,6 @@ try { JavaScript error message. This field is generally only used locally and not passed on to other hosts. -## Class: LedgerInfo -class LedgerInfo - -Metadata describing the ledger. This data is returned by the [`getInfo`](#getinfo) method. - -###### Fields -| Type | Name | Description | -|:--|:--|:--| -| `String` | [currencyCode](#ledgerinfocurrencycode) | [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) three-letter currency code | -| `Number` | [currencyScale](#ledgerinfocurrencyscale) | Integer `(..., -2, -1, 0, 1, 2, ...)`, such that one of the ledger's base units equals `10^- ` | - -### Fields - -#### LedgerInfo#currencyCode -**currencyCode**:String - -The [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) currency code (if any) used by the ledger. A custom all-caps three-letter code, not used by ISO 4217, otherwise. -Ledger administrators who choose a custom currency code MAY request a custom currency symbol for their chosen currency code be listed by software modules that map currency codes to currency symbols, -for instance on node package manager (npm) in the case of JavaScript. -To translate an integer amount or balance from the ledger, the currencyCode by itself is not enough. It has to be used in combination with the currencyScale (below) to determine how many -of the ledger's base units correspond to one currency unit. - -#### LedgerInfo#currencyScale -**currencyScale**:String - -The order of magnitude to express one full currency unit in ledger's base units. For instance, if the integer values represented on the ledger are to be interpreted as -dollar-cents (for the purpose of settling a user's account balance, for instance), then the ledger's -currencyCode is `USD` and its currencyScale is `2`. - ## Class: PluginOptions class PluginOptions From 5d84ef20fa073c880be7e8f2b15413dcddd0d7d1 Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Fri, 22 Dec 2017 14:34:53 -0800 Subject: [PATCH 09/11] docs(lpi2): change sendTransfer to sendData/sendMoney Removed all special error definitions. Interledger rejections are now just successful data responses. We no longer need to react to different exceptions differently, therefore the spec does not need to specify what errors are possible anymore. Removed Transfer, FulfillmentInfo classes. --- .../0000-ledger-plugin-interface-2.md | 294 +++--------------- 1 file changed, 39 insertions(+), 255 deletions(-) diff --git a/0000-ledger-plugin-interface-2/0000-ledger-plugin-interface-2.md b/0000-ledger-plugin-interface-2/0000-ledger-plugin-interface-2.md index 5dfb1bae..9a235443 100644 --- a/0000-ledger-plugin-interface-2/0000-ledger-plugin-interface-2.md +++ b/0000-ledger-plugin-interface-2/0000-ledger-plugin-interface-2.md @@ -6,11 +6,11 @@ draft: 1 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. +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](../0003-interledger-protocol/). +~This spec depends on the [ILP spec](../0003-interledger-protocol/).~ ## Class: LedgerPlugin `class LedgerPlugin` @@ -22,9 +22,12 @@ This spec depends on the [ILP spec](../0003-interledger-protocol/). | | [**connect**](#ledgerpluginconnect) ( options ) `⇒ Promise.` | | | [**disconnect**](#ledgerplugindisconnect) ( ) `⇒ Promise.` | | | [**isConnected**](#ledgerpluginisconnected) ( ) `⇒ Boolean` | -| | [**sendTransfer**](#ledgerpluginsendtransfer) ( transfer ) ⇒ Promise.<[FulfillmentInfo](#class-fulfillmentinfo)> | -| | [**registerTransferHandler**](#ledgerpluginregistertransferhandler) ( transferHandler ) ⇒ null | -| | [**deregisterTransferHandler**](#ledgerpluginderegistertransferhandler) ( ) ⇒ null | +| | [**sendData**](#ledgerpluginsenddata) ( data ) ⇒ Promise.<Buffer> | +| | [**sendMoney**](#ledgerpluginsendmoney) ( amount ) ⇒ Promise.<Buffer> | +| | [**registerDataHandler**](#ledgerpluginregisterdatahandler) ( dataHandler ) ⇒ null | +| | [**deregisterDataHandler**](#ledgerpluginderegisterdatahandler) ( ) ⇒ null | +| | [**registerMoneyHandler**](#ledgerpluginregistermoneyhandler) ( moneyHandler ) ⇒ null | +| | [**deregisterMoneyHandler**](#ledgerpluginderegistermoneyhandler) ( ) ⇒ null | ###### Constants @@ -39,20 +42,12 @@ This spec depends on the [ILP spec](../0003-interledger-protocol/). | [**disconnect**](#event-disconnect) | `( ) ⇒` | | [**error**](#event-error) | `( ) ⇒` | -###### Errors -| Name | Description | -|:--|:--| -| [**InvalidFieldsError**]() | Arguments or configuration were invalidated client-side | -| [**UnreachableError**]() | An error occured due to connection failure | -| [**NotAcceptedError**]() | An operation has been rejected due to ledger-side logic | -| [**NoSubscriptionsError**]() | A transfer cannot be delivered because there are no active websockets | - ### Instance Management #### new LedgerPlugin new LedgerPlugin( **opts** : [PluginOptions](#class-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. +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. Throws `InvalidFieldsError` if the constructor is given incorrect arguments. @@ -130,279 +125,68 @@ Emitted when the connection has been terminated or lost. 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 +### Sending -#### LedgerPlugin#sendTransfer -ledgerPlugin.sendTransfer( **transfer**:[Transfer](#class-transfer) ) ⇒ Promise.<[FulfillmentInfo](#class-fulfillmentinfo)> +#### LedgerPlugin#sendData +ledgerPlugin.sendData( **data**:Buffer ) ⇒ Promise.<Buffer> -Initiates an account-local transfer. A transfer MUST contain an `amount` of zero or more and MAY have attached `ilp` data. See the description of the [Transfer](#class-transfer) class below. - -All plugins MUST support amounts in a range from zero to some maximum. Plugins MAY implement zero-amount transfers differently than other transfers. +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`. ###### Parameters | Name | Type | Description | |:--|:--|:--| -| transfer | [Transfer](#class-transfer) | Properties of the transfer to be created | - -When sending transfers, all fields are required and MUST be provided, even if they contain an empty value, such as an empty object in `custom`. +| data | Buffer | Binary request data | ###### Returns -**`Promise.`** A promise which resolves when the transfer has been fulfilled/executed. - -Rejects with `InvalidFieldsError` if required fields are missing from the transfer or malformed. Rejects with `NotAcceptedError` if the transfer is rejected by the ledger due to insufficient balance or any other reason. Rejects with `NotConnectedError` if the plugin is not connected to the ledger. - -Rejects with [`InterledgerRejectionError`](#class-interledgerrejectionerror) if the other side rejects the transfer and attaches ILP rejection data. Rejects with [`GenericRejectionError`](#class-genericrejectionerror) if the other side rejects the transfer, but does not attach valid ILP rejection data. One example for when a GenericRejectionError may be used is if a transfer times out. +**`Promise.`** A promise which resolves when the response has been received. This method MAY reject with any arbitrary JavaScript error. ###### Example ```js -p.sendTransfer({ - amount: '10', - executionCondition: Buffer.from('47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU', 'base64'), - expiresAt: '2016-05-18T12:00:00.000Z', - ilp: Buffer.alloc(0), - custom: {} -}) +const responseBuffer = await p.sendData(requestBuffer) ``` -For a detailed description of these properties, please see [`Class: Transfer`](#class-transfer). - -#### LedgerPlugin#registerTransferHandler -ledgerPlugin.registerTransferHandler( **transferHandler**: ( transfer: [Transfer](#class-transfer) ) ⇒ Promise<[FulfillmentInfo](#class-fulfillmentinfo)> ) ⇒ null - -Set the callback which is used to handle incoming prepared transfers. The callback should expect one parameter (the [Transfer](#class-transfer)) and return a promise for the resulting [FulfillmentInfo](#class-fulfillmentinfo). If the transfer is rejected or an error occurs, the callback should reject the transfer. In general, the callback should behave as [`sendTransfer`](#ledgerpluginsendtransfer) does. - -If a transfer handler is already set, this method throws a `TransferHandlerAlreadyRegisteredError`. In order to change the transfer handler, the old handler must first be removed via [`deregisterTransferHandler`](#ledgerpluginderegistertransferhandler). This is to ensure that handlers are not overwritten by accident. - -If an incoming transfer is received by the plugin, but no handler is registered, the plugin should reject the transfer. - -#### LedgerPlugin#deregisterTransferHandler -ledgerPlugin.deregisterTransferHandler( ) ⇒ null - -Removes the currently used transfer handler. This has the same effect as if [`registerTransferHandler`](#ledgerpluginregistertransferhandler) had never been called. +#### LedgerPlugin#sendMoney +ledgerPlugin.sendMoney( **amount**:string ) ⇒ Promise.<null> -If no transfer handler is currently set, this method does nothing. +Transfer `amount` units of money from the caller to the counterparty of the account. -## Class: Transfer -class Transfer +All plugins MUST support amounts in a range from one to some maximum. -The `Transfer` class is used to describe transfers from the originator of the sendTransfer call towards some destination via Interledger. All fields are required and MUST NOT be undefined. However, `amount` MAY be the value `'0'`, `ilp` MAY be an empty buffer and `custom` MAY be an empty object. +### Receiving -###### Fields -| Type | Name | Description | -|:--|:--|:--| -| `String` | [amount](#transferamount) | Integer transfer amount, in the ledger's base unit | -| `Buffer` | [ilp](#transferilp) | Attached data (ILP packet) | -| `Buffer` | [executionCondition](#transferexecutioncondition) | Cryptographic hold condition | -| `String` | [expiresAt](#transferexpiresat) | Expiry time of the cryptographic hold | -| `Object` | [custom](#transfercustom) | Object containing ledger plugin specific options | +#### LedgerPlugin#registerDataHandler +ledgerPlugin.registerDataHandler( **dataHandler**: ( data: Buffer ) ⇒ Promise<Buffer> ) ⇒ null -###### Example -``` js -{ - amount: '100', - executionCondition: Buffer.from('I3TZF5S3n0-07JWH0s8ArsxPmVP6s-0d0SqxR6C3Ifk', 'base64'), - expiresAt: '2017-12-02T11:51:26.627Z', - ilp: Buffer.alloc(0), - custom: { - _alternateAccount: 'bob-savings', - executionPriority: 9 - } -} -``` - -### Fields +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`](#ledgerpluginsenddata) does. -#### Transfer#amount -**amount**:String +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`](#ledgerpluginderegisterdatahandler). This is to ensure that handlers are not overwritten by accident. -An integer amount, represented as a string of base-ten digits. MUST be in the range `0..9223372036854775807` (`>= 0` and `< 2^64`). +If an incoming packet is received by the plugin, but no handler is registered, the plugin SHOULD respond with an error. -###### Example - -``` js -'100' -``` +#### LedgerPlugin#deregisterDataHandler +ledgerPlugin.deregisterDataHandler( ) ⇒ null -#### Transfer#ilp -**ilp**:Buffer +Removes the currently used data handler. This has the same effect as if [`registerDataHandler`](#ledgerpluginregisterdatahandler) had never been called. -An [ILP packet](https://interledger.org/rfcs/0003-interledger-protocol/draft-4.html#specification), denoting the payment's final destination. Plugins SHOULD support a size (in bytes) in the range `0..65535` (`>= 0` and `< 2^16`). Note that ILP packets are currently smaller than that, but larger packets may be used in the future due to extensions. +If no data handler is currently set, this method does nothing. -If the `ilp` data is too large, the ledger plugin MUST reject with a `MaximumIlpDataSizeExceededError`. +#### LedgerPlugin#registerMoneyHandler +ledgerPlugin.registerMoneyHandler( **moneyHandler**: ( amount: string ) ⇒ Promise<null> ) ⇒ null -#### Transfer#executionCondition -**executionCondition**:Buffer +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`](#ledgerpluginsendmoney) does. -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. +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`](#ledgerpluginderegistermoneyhandler). This is to ensure that handlers are not overwritten by accident. -The `executionCondition` is a Node.js `Buffer` containing the 32-byte SHA-256 hash of a random or pseudo-random 32-byte preimage called the `fulfillment`. +If incoming money is received by the plugin, but no handler is registered, the plugin SHOULD return an error (and MAY return the money.) -###### Example - -``` js -Buffer.from('I3TZF5S3n0-07JWH0s8ArsxPmVP6s-0d0SqxR6C3Ifk', 'base64') -``` - -#### Transfer#expiresAt -**expiresAt**:String - -An ISO 8601 datetime string representing the expiry date for the transfer. Must include the UTC timezone identifier `Z`. - -This date MUST be in the future, otherwise the plugin MUST reject with a `UnacceptableExpiryError`. - -###### Example - -``` js -'2017-12-02T11:51:26.627Z' -``` - -#### Transfer#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. - -If the `custom` data is too large, the ledger plugin MUST reject with a `MaximumCustomDataSizeExceededError`. - -Note that connectors MAY forward some fields of `custom` data from plugin to plugin, but generally are not expected to. All `custom` fields that were passed to `sendTransfer` MUST be passed to the transfer handler by the plugin on the receiving side. The only exception are properties which start with the underscore character (`_`), which MAY be consumed by the plugin and not passed on. - -###### Example -``` js -{ - // Starts with an underscore, consumed by plugin - _alternateAccount: 'bob-savings', - // Other property, passed on to next connector/receiver - executionPriority: 9 -} -``` - -## Class: FulfillmentInfo -class FulfillmentInfo - -The `FulfillmentInfo` class is used to describe the fulfillment and associated data that is returned when a transfer successfully completes. - -###### Fields -| Type | Name | Description | -|:--|:--|:--| -| `String` | [fulfillment](#fulfillmentinfofulfillment) | Cryptographic hold fulfillment | -| `Buffer` | [ilp](#fulfillmentinfoilp) | Attached data (ILP packet) | -| `Object` | [custom](#fulfillmentinfocustom) | Object containing ledger plugin specific options | - -### Fields - -#### FulfillmentInfo#fulfillment -**fulfillment**:Buffer - -A cryptographic fulfillment that is the SHA-256 preimage of the hash provided as the [`executionCondition`](#transferexecutioncondition) when the transfer was first prepared. - -The `fulfillment` is a Node.js `Buffer` with a length of exactly 32 bytes. - -#### FulfillmentInfo#ilp -**ilp**:Buffer - -An [ILP packet](https://interledger.org/rfcs/0003-interledger-protocol/draft-4.html#specification), containing any data attached to the fulfillment. Plugins SHOULD support a size (in bytes) in the range `0..65535` (`>= 0` and `< 2^16`). Note that ILP packets are currently smaller than that, but larger packets may be used in the future due to extensions. - -#### FulfillmentInfo#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. - -Note that connectors MAY forward some fields of `custom` data from plugin to plugin, but generally are not expected to. All `custom` fields that were set in `FulfillmentInfo#custom` on the receiving side MUST be set in `FulfillmentInfo#custom` when it is resolved by the promise on the sending side. The only exception are properties which start with the underscore character (`_`), which MAY be consumed by the plugin and not passed on. - -All custom fields that were set in FulfillmentInfo#custom on the receiving side MUST be set in FulfillmentInfo#custom when it is resolved by the promise on the sending side. - -###### Example -``` js -{ - custom: { - claim: '...', - fulfillmentLatency: 29 - } -} -``` - -## Class: InterledgerRejectionError -class InterledgerRejectionError - -An `InterledgerRejectionError` is a throwable object representing a rejection of an Interledger transfer. Implementations SHOULD use a class named `InterledgerRejectionError` which derives from JavaScript's built-in `Error`. However, other implementations MUST NOT rely on this and SHOULD use the `name` property to distinguish Interledger rejections from other error types. - -Plugins SHOULD NOT generally trigger `InterledgerRejectionError`s. Instead, the plugin SHOULD trigger a local error, such as the ones specified in this document and the hosting connector SHOULD create a suitable `InterledgerRejectionError` from the local error. This is because plugins are not necessarily aware of their ILP address and therefore may not be able to set `triggeredBy` correctly. Some plugins may have elements of a connector built-in if they are used with ledgers that don't natively support ILP. In that case, the plugin MAY trigger an `InterledgerRejectionError` since it is in effect acting as a connector. - -All fields described below MUST be present, however they MAY be empty. - -###### Fields -| Type | Name | Description | -|:--|:--|:--| -| `String` | [name](#interledgerrejectionerrorname) | `'InterledgerRejectionError'` | -| `String` | [message](#interledgerrejectionerrormessage) | Error message for local use | -| `Buffer` | [ilp](#interledgerrejectionerrorilp) | Attached data (ILP packet) | - -### Fields - -#### InterledgerRejectionError#name -**name**:String - -JavaScript error name, always `'InterledgerRejectionError'`. This property SHOULD be used to distinguish InterledgerRejectionErrors from other error types, e.g.: - -``` js -try { - await plugin.sendTransfer(transfer) -} catch (err) [ - if (err && err.name === 'InterledgerRejectionError') { - // This is an Interledger rejection - } else { - // This is some other type of error - } -] -``` - -#### InterledgerRejectionError#message -**message**:String - -JavaScript error message. This field is generally only used locally and not passed on to other hosts. - -#### InterledgerRejectionError#ilp -**ilp**:Buffer - -An [ILP packet](https://interledger.org/rfcs/0003-interledger-protocol/draft-4.html#specification), containing information on why the payment has been rejected and by whom. Plugins SHOULD support a size (in bytes) in the range `0..65535` (`>= 0` and `< 2^16`). Note that ILP packets are currently smaller than that, but larger packets may be used in the future due to extensions. - -## Class: GenericRejectionError -class GenericRejectionError - -A `GenericRejectionError` is a throwable object representing a non-Interledger rejection of an Interledger transfer. Implementations SHOULD use a class named `GenericRejectionError` which derives from JavaScript's built-in `Error`. However, other implementations MUST NOT rely on this and SHOULD use the `name` property to distinguish Interledger rejections from other error types. `GenericRejectionError`s SHOULD NOT generally be triggered by anything except for the plugin. It can be used for local errors, such as timeouts or insufficient liquidity. - -All fields described below MUST be present, however they MAY be empty. - -###### Fields -| Type | Name | Description | -|:--|:--|:--| -| `String` | [name](#interledgerrejectionerrorname) | `'GenericRejectionError'` | -| `String` | [message](#interledgerrejectionerrormessage) | Error message for local use | - -### Fields - -#### GenericRejectionError#name -**name**:String - -JavaScript error name, always `'GenericRejectionError'`. This property SHOULD be used to distinguish InterledgerRejectionErrors from other error types, e.g.: - -``` js -try { - await plugin.sendTransfer(transfer) -} catch (err) [ - if (err && err.name === 'GenericRejectionError') { - // This is a non-Interledger rejection - } else { - // This is an Interledger error - } -] -``` +#### LedgerPlugin#deregisterMoneyHandler +ledgerPlugin.deregisterMoneyHandler( ) ⇒ null -#### GenericRejectionError#message -**message**:String +Removes the currently used money handler. This has the same effect as if [`registerMoneyHandler`](#ledgerpluginregistermoneyhandler) had never been called. -JavaScript error message. This field is generally only used locally and not passed on to other hosts. +If no money handler is currently set, this method does nothing. ## Class: PluginOptions class PluginOptions From 1d2cd726d2c17dc175d3d5eafb29cf93d7d35c4d Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Tue, 2 Jan 2018 14:19:13 -0800 Subject: [PATCH 10/11] chore: null -> undefined Undefined is more natural to use. E.g. `return` instead of `return null` and `Promise.resolve()` instead of `Promise.resolve(null)`. --- .../0000-ledger-plugin-interface-2.md | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/0000-ledger-plugin-interface-2/0000-ledger-plugin-interface-2.md b/0000-ledger-plugin-interface-2/0000-ledger-plugin-interface-2.md index 9a235443..647fe0f3 100644 --- a/0000-ledger-plugin-interface-2/0000-ledger-plugin-interface-2.md +++ b/0000-ledger-plugin-interface-2/0000-ledger-plugin-interface-2.md @@ -19,15 +19,15 @@ To send ILP payments through a new ledger, one must implement a ledger plugin th | | Name | |:--|:--| | `new` | [**LedgerPlugin**](#new-ledgerplugin) ( opts ) | -| | [**connect**](#ledgerpluginconnect) ( options ) `⇒ Promise.` | -| | [**disconnect**](#ledgerplugindisconnect) ( ) `⇒ Promise.` | +| | [**connect**](#ledgerpluginconnect) ( options ) `⇒ Promise.` | +| | [**disconnect**](#ledgerplugindisconnect) ( ) `⇒ Promise.` | | | [**isConnected**](#ledgerpluginisconnected) ( ) `⇒ Boolean` | | | [**sendData**](#ledgerpluginsenddata) ( data ) ⇒ Promise.<Buffer> | | | [**sendMoney**](#ledgerpluginsendmoney) ( amount ) ⇒ Promise.<Buffer> | -| | [**registerDataHandler**](#ledgerpluginregisterdatahandler) ( dataHandler ) ⇒ null | -| | [**deregisterDataHandler**](#ledgerpluginderegisterdatahandler) ( ) ⇒ null | -| | [**registerMoneyHandler**](#ledgerpluginregistermoneyhandler) ( moneyHandler ) ⇒ null | -| | [**deregisterMoneyHandler**](#ledgerpluginderegistermoneyhandler) ( ) ⇒ null | +| | [**registerDataHandler**](#ledgerpluginregisterdatahandler) ( dataHandler ) ⇒ undefined | +| | [**deregisterDataHandler**](#ledgerpluginderegisterdatahandler) ( ) ⇒ undefined | +| | [**registerMoneyHandler**](#ledgerpluginregistermoneyhandler) ( moneyHandler ) ⇒ undefined | +| | [**deregisterMoneyHandler**](#ledgerpluginderegistermoneyhandler) ( ) ⇒ undefined | ###### Constants @@ -67,7 +67,7 @@ const ledgerPlugin = new LedgerPlugin({ // (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. + // Returns Promise. }, // Fetch a value by key get: (key) => { @@ -75,7 +75,7 @@ const ledgerPlugin = new LedgerPlugin({ }, // Delete a value by key del: (key) => { - // Returns Promise. + // Returns Promise. } } }) @@ -91,7 +91,7 @@ Always `2` for this version of the Ledger Plugin Interface. ### Connection Management #### LedgerPlugin#connect -ledgerPlugin.connect( options:[ConnectOptions](#class-connectoptions ) ⇒ Promise.<null> +ledgerPlugin.connect( options:[ConnectOptions](#class-connectoptions ) ⇒ Promise.<undefined> `options` is optional. @@ -101,7 +101,7 @@ Rejects with `InvalidFieldsError` if credentials are missing, and `NotAcceptedEr Rejects with `TypeError` if `options.timeout` is passed but is not a `Number`. #### LedgerPlugin#disconnect -ledgerPlugin.disconnect() ⇒ Promise.<null> +ledgerPlugin.disconnect() ⇒ Promise.<undefined> Unsubscribe from ledger events. @@ -148,7 +148,7 @@ const responseBuffer = await p.sendData(requestBuffer) ``` #### LedgerPlugin#sendMoney -ledgerPlugin.sendMoney( **amount**:string ) ⇒ Promise.<null> +ledgerPlugin.sendMoney( **amount**:string ) ⇒ Promise.<undefined> Transfer `amount` units of money from the caller to the counterparty of the account. @@ -157,7 +157,7 @@ All plugins MUST support amounts in a range from one to some maximum. ### Receiving #### LedgerPlugin#registerDataHandler -ledgerPlugin.registerDataHandler( **dataHandler**: ( data: Buffer ) ⇒ Promise<Buffer> ) ⇒ null +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`](#ledgerpluginsenddata) does. @@ -166,14 +166,14 @@ If a data handler is already set, this method throws a `DataHandlerAlreadyRegist If an incoming packet is received by the plugin, but no handler is registered, the plugin SHOULD respond with an error. #### LedgerPlugin#deregisterDataHandler -ledgerPlugin.deregisterDataHandler( ) ⇒ null +ledgerPlugin.deregisterDataHandler( ) ⇒ undefined Removes the currently used data handler. This has the same effect as if [`registerDataHandler`](#ledgerpluginregisterdatahandler) had never been called. If no data handler is currently set, this method does nothing. #### LedgerPlugin#registerMoneyHandler -ledgerPlugin.registerMoneyHandler( **moneyHandler**: ( amount: string ) ⇒ Promise<null> ) ⇒ null +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`](#ledgerpluginsendmoney) does. @@ -182,7 +182,7 @@ If a money handler is already set, this method throws a `MoneyHandlerAlreadyRegi 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 -ledgerPlugin.deregisterMoneyHandler( ) ⇒ null +ledgerPlugin.deregisterMoneyHandler( ) ⇒ undefined Removes the currently used money handler. This has the same effect as if [`registerMoneyHandler`](#ledgerpluginregistermoneyhandler) had never been called. @@ -217,7 +217,7 @@ Method names are based on the popular LevelUP/LevelDOWN packages. { // Store a value under a key put: (key, value) => { - // Returns Promise. + // Returns Promise. }, // Fetch a value by key get: (key) => { @@ -225,7 +225,7 @@ Method names are based on the popular LevelUP/LevelDOWN packages. }, // Delete a value by key del: (key) => { - // Returns Promise. + // Returns Promise. } } ``` From 0eb6b3d5880e88645b6eaf012de0a6a7d4d4422f Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Sat, 20 Jan 2018 08:21:11 -0800 Subject: [PATCH 11/11] docs(lpi2): add PluginServices and assign final RFC number --- .../0024-ledger-plugin-interface-2.md | 66 +++++++++++++++---- 1 file changed, 52 insertions(+), 14 deletions(-) rename 0000-ledger-plugin-interface-2/0000-ledger-plugin-interface-2.md => 0024-ledger-plugin-interface-2/0024-ledger-plugin-interface-2.md (74%) diff --git a/0000-ledger-plugin-interface-2/0000-ledger-plugin-interface-2.md b/0024-ledger-plugin-interface-2/0024-ledger-plugin-interface-2.md similarity index 74% rename from 0000-ledger-plugin-interface-2/0000-ledger-plugin-interface-2.md rename to 0024-ledger-plugin-interface-2/0024-ledger-plugin-interface-2.md index 647fe0f3..daac6987 100644 --- a/0000-ledger-plugin-interface-2/0000-ledger-plugin-interface-2.md +++ b/0024-ledger-plugin-interface-2/0024-ledger-plugin-interface-2.md @@ -18,7 +18,7 @@ To send ILP payments through a new ledger, one must implement a ledger plugin th ###### Methods | | Name | |:--|:--| -| `new` | [**LedgerPlugin**](#new-ledgerplugin) ( opts ) | +| `new` | [**LedgerPlugin**](#new-ledgerplugin) ( opts, api ) | | | [**connect**](#ledgerpluginconnect) ( options ) `⇒ Promise.` | | | [**disconnect**](#ledgerplugindisconnect) ( ) `⇒ Promise.` | | | [**isConnected**](#ledgerpluginisconnected) ( ) `⇒ Boolean` | @@ -45,11 +45,15 @@ To send ILP payments through a new ledger, one must implement a ledger plugin th ### Instance Management #### new LedgerPlugin -new LedgerPlugin( **opts** : [PluginOptions](#class-pluginoptions) ) +new LedgerPlugin( **opts** : object, **api**? : [PluginServices](#class-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. -Throws `InvalidFieldsError` if the constructor is given incorrect arguments. +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`. ###### Parameters | Name | Type | Description | @@ -188,27 +192,27 @@ Removes the currently used money handler. This has the same effect as if [`regis If no money handler is currently set, this method does nothing. -## Class: PluginOptions -class PluginOptions +## Class: PluginServices +class PluginServices -Plugin options are passed in to the [`LedgerPlugin`](#class-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. +Plugin services are optionally passed in to the [`LedgerPlugin`](#class-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. ###### Special Fields | Type | Name | Description | |:--|:--|:--| -| `Object` | [_store](#pluginoptions-_store) | Persistence layer callbacks | +| `Object` | [store](#pluginservices-store) | Simple key-value store object | +| `Object` | [log](#pluginservices-log) | Simple logger object | ### Fields -#### PluginOptions#_store -**_store**:Object +#### PluginServices#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. +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. @@ -230,13 +234,47 @@ Method names are based on the popular LevelUP/LevelDOWN packages. } ``` +#### PluginServices#log +**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](https://wikipedia.org/wiki/Printf_format_string) 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. + +###### Example +```js +{ + // 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 class ConnectOptions ###### Fields | Type | Name | Description | |:--|:--|:--| -| `Number` | [timeout](#connectoptions-timeout) | milliseconds | +| `Number` | [timeout](#connectoptions-timeout) | Amount of time before the client SHOULD give up trying to connect (in milliseconds) | ### Fields