0.4.0-alpha.9 • Published 3 years ago

@interledger/pay v0.4.0-alpha.9

Weekly downloads
2
License
Apache-2.0
Repository
-
Last release
3 years ago

pay :money_with_wings:

Send payments over Interledger using STREAM

NPM Package GitHub Actions Codecov Prettier

Install

npm i @interledger/pay

Or using Yarn:

yarn add @interledger/pay

Guide

Flow

  1. Call setupPayment to resolve the payment details, destination asset, and/or Incoming Payment
  2. Add custom logic before continuing, or catch error
  3. Call startQuote to probe the exchange rate, discover the max packet amount, and compute payment limits
  4. Add custom logic to authorize payment for maximum source amount, or catch error
  5. Call pay to execute the payment
  6. Add custom logic to handle payment outcome or error

Pay an Incoming Payment

Fixed delivery amount payment

import { setupPayment, startQuote, pay, closeConnection } from '@interledger/pay'

async function run() {
  let plugin /* Plugin instance */

  const destination = await setupPayment({
    plugin,
    destinationPayment:
      'https://mywallet.example/accounts/alice/incoming-payments/04ef492f-94af-488e-8808-3ea95685c992',
  })

  const quote = await startQuote({
    plugin,
    destination,
    sourceAsset: {
      assetCode: 'USD',
      assetScale: 9,
    },
  })
  // {
  //   maxSourceAmount: 1_950n,
  //   lowEstimatedExchangeRate: 115,
  //   highEstimatedExchangeRate: 135,
  //   minExchangeRate: 110,
  // }

  // Verify the max source amount is appropriate and perform or cancel the payment
  const receipt = await pay({ plugin, destination, quote })
  console.log(receipt)
  // {
  //    amountSent: 1_910n,
  //    amountDelivered: BigInt(234_000),
  //    ...
  // }

  await closeConnection(plugin, destination)
}

Pay an Incoming Payment via a Connection URL

Fixed delivery amount payment

import { setupPayment, startQuote, pay, closeConnection } from '@interledger/pay'

async function run() {
  let plugin /* Plugin instance */

  const destination = await setupPayment({
    plugin,
    destinationConnection: 'https://mywallet.example/bddcc820-c8a1-4a15-b768-95ea2a4ed37b',
  })

  const quote = await startQuote({
    plugin,
    destination,
    sourceAsset: {
      assetCode: 'USD',
      assetScale: 9,
    },
  })
  // {
  //   maxSourceAmount: 1_950n,
  //   lowEstimatedExchangeRate: 115,
  //   highEstimatedExchangeRate: 135,
  //   minExchangeRate: 110,
  // }

  // Verify the max source amount is appropriate and perform or cancel the payment
  const receipt = await pay({ plugin, destination, quote })

  await closeConnection(plugin, destination)
}

Pay to a Payment Pointer

Fixed source amount payment

import { setupPayment, startQuote, pay, closeConnection } from '@interledger/pay'

async function run() {
  let plugin /* Plugin instance */

  const destination = await setupPayment({
    plugin,
    paymentPointer: '$rafiki.money/p/example',
  })

  const quote = await startQuote(
    plugin,
    amountToSend: '314159',
    sourceAmount: {
      assetCode: 'EUR',
      assetScale: 6,
    },
    destination
  })

  const receipt = await pay({ plugin, destination, quote })

  await closeConnection(plugin, destination)
}

Units

On Interledger assets and denominations:

Asset amounts may be represented using any arbitrary denomination. For example, one U.S. dollar may be represented as \$1 or 100 cents, each of which is equivalent in value. Likewise, one Bitcoin may be represented as 1 BTC or 100,000,000 satoshis.

A standard unit is the typical unit of value for a particular asset, such as \$1 in the case of U.S. dollars, or 1 BTC in the case of Bitcoin.

A fractional unit represents some unit smaller than the standard unit, but with greater precision. Examples of fractional monetary units include one cent (\$0.01 USD), or 1 satoshi (0.00000001 BTC).

An asset scale is the difference in orders of magnitude between the standard unit and a corresponding fractional unit. More formally, the asset scale is a non-negative integer (0, 1, 2, …) such that one standard unit equals the value of 10^(scale) corresponding fractional units. If the fractional unit equals the standard unit, then the asset scale is 0.

For example, one cent represents an asset scale of 2 in the case of USD, whereas one satoshi represents an asset scale of 8 in the case of Bitcoin.

To simplify accounting, all amounts are represented as unsigned integers in a fractional unit of the asset corresponding to the source asset scale provided, or the destination asset scale resolved from the receiver.

Since applications need to debit the source amount in their own system before executing a payment, this assumes they also know their own source asset and denomination. Therefore, it's not useful to resolve this information dynamically, such as using IL-DCP, which also delays connection establishment.

Amounts

Pay leverages JavaScript BigInt for arbitrarily large integers using its own wrapper for strongly-typed arithmetic operations.

Amounts returned by Pay use these exported classes and interfaces:

  • Int Class representing non-negative integers.
  • PositiveInt Interface narrowing Int, representing non-negative, non-zero integers. (In this context, zero is not considered signed).
  • Ratio Class representing a ratio of two integers: a non-negative numerator, and a non-negative, non-zero denominator.
  • PositiveRatio Interface narrowing Ratio, representing a ratio of two non-negative, non-zero integers.

Int and Ratio offer utility methods for integer operations and comparisons. They may also be converted to/from number, string, bigint, and Long.

Int and Ratio prevent divide-by-zero errors and enforce the internal bigint is always non-negative. They also provide type guards for PositiveInt to reduce unnecessary code paths. For example, if one integer is greater than another, that integer must always be non-zero, and can be safely used as a ratio denominator without any divide-by-zero branch.

Exchange Rates

Pay is designed to provide strict guarantees of the amount that will be delivered.

During the quote step, the application provides Pay with prices for the source and destination assets and its own acceptable slippage percentage, which Pay uses to calculate a minimum exchange rate and corresponding minimum destination amount it will enforce for the payment. Exchange rates are represented as the ratio between a destination amount and a source amount, in fractional units.

Then, Pay probes the recipient to determine the real exchange rate over that path. If it sufficiently exceeds the minimum exchange rate, Pay will allow the payment to proceed. Otherwise, it's not possible to complete the payment. For instance, connectors may have applied a poor rate or charged too much in fees, the max packet size might be too small to avoid rounding errors, or incorrect assets/scales were provided.

Since STREAM payments are packetized, Pay may not be able to complete a payment if, for instance, the sender and receiver become disconnected during the payment. However, Pay guarantees payments never exhaust their quoted maximum source amount without satisfying their quoted minimum delivery amount. Every delivered packet meets or exceeds the quoted minimum exchange rate (*with the exception of the final one, as necessary).

Error Handling

If setup or quoting fails, Pay will reject the Promise with a variant of the PaymentError enum. For example:

import { setupPayment, PaymentError } from '@interledger/pay'

try {
  await setupPayment({ ... })
} catch (err) {
  if (err === PaymentError.InvalidPaymentPointer) {
    console.log('Payment pointer is invalid!')
  }

  // ...
}

Similarly, if an error was encountered during the payment itself, it will include an error property on the result which is a PaymentError variant.

A predicate function, isPaymentError, is also exported to check if any value is a variant of the enum.

Payment Pointers

Pay exports the AccountUrl utility to validate payment pointers and SPSP/Open Payments account URLs. Since payment pointers identify unique Interledger accounts, Pay parses them so they can be compared against external references to the same account.

Connection Security

Some applications may find it useful for multiple Pay library instances to send over a single STREAM connection, such as quoting in one process, and sending money in another.

In this case, the client application must track key security parameters, such as the request count, which STREAM relies on for monotonically increasing sequence numbers and secure acknowledgements of each request.

Pay uses a Counter instance, passed in-process via the ResolvedPayment object, to track how many packets have been sent. Applications that resume connections MUST use the counter instance to fetch how many packets have been sent, then create a new counter with the existing request count to pass to new Pay instances that use the same connection.

Other connection invariants applications should enforce:

  1. Only one Pay instance (any actively running call to startQuote, pay, or closeConnection) can send over a single connection at one time.
  2. After a connection is closed via calling closeConnection, those connection details may no longer be used for sending.

API

setupPayment

(options:SetupOptions) => Promise<ResolvedPayment>

Resolve destination details and asset of the payment in order to establish a STREAM connection.

startQuote

(options:QuoteOptions) => Promise<Quote>

Perform a rate probe: discover path max packet amount, probe the real exchange rate, and compute the minimum exchange rate and bounds of the payment.

pay

(options:PayOptions) => Promise<PaymentProgress>

Send the payment: send a series of packets to attempt the payment within the completion criteria and limits of the provided quote.

closeConnection

(plugin:Plugin, destination:ResolvedPayment) => Promise<void>

If the connection was established, notify receiver to close the connection. For stateless receivers, this may have no effect.

SetupOptions

Interface

Parameters to setup and resolve payment details from the recipient.

PropertyTypeDescription
pluginPluginPlugin to send packets over a connected Interledger network (no receive functionality is necessary). Pay does not call connect or disconnect on the plugin, so the application must perform that manually.
destinationAccount (Optional)stringSPSP Payment pointer or SPSP account URL to query STREAM connection credentials and exchange asset details. Example: $rafiki.money/p/alice. Either destinationAccount , destinationPayment, or destinationConnection must be provided.
destinationPayment (Optional)stringOpen Payments Incoming Payment URL to query the details for a fixed-delivery payment. The amount to deliver and destination asset details will automatically be resolved from the Incoming Payment. Either destinationAccount , destinationPayment, or destinationConnection must be provided.
destinationConnection (Optional)stringOpen Payments STREAM Connection URL to query STREAM connection credentials and exchange asset details for a fixed-delivery payment. Either destinationAccount , destinationPayment, or destinationConnection must be provided.
amountToDeliver (Optional)AmountFixed amount of the created Incoming Payment, in base units of the destination asset. Note: this option requires the destination asset to be known in advance. The application must ensure the destination asset resolved via STREAM is the expected asset and denomination.

ResolvedPayment

Interface

Resolved destination details of a proposed payment, such as the destination asset, Incoming Payment, and STREAM credentials, ready to perform a quote.

PropertyTypeDescription
destinationAssetAssetDetailsDestination asset and denomination, resolved using Open Payments or STREAM, or provided directly.
destinationAddressstringILP address of the destination STREAM recipient, uniquely identifying this connection.
sharedSecretUint8Array32-byte seed to derive keys to encrypt STREAM messages and generate ILP packet fulfillments.
destinationPaymentDetails (Optional)IncomingPaymentOpen Payments Incoming Payment metadata, if the payment pays into an Incoming Payment.
accountUrl (Optional)stringURL of the recipient Open Payments/SPSP account (with well-known path, and stripped trailing slash). Each payment pointer and its corresponding account URL identifies a unique payment recipient. Not applicable if Open Payments STREAM Connection URL or STREAM credentials were provided directly.
destinationAccount (Optional)stringPayment pointer, prefixed with "\$", corresponding to the recipient Open Payments/SPSP account. Each payment pointer and its corresponding account URL identifies a unique payment recipient. Not applicable if STREAM credentials were provided directly.
requestCounterCounterStrict counter of how many packets have been sent, to safely resume a connection

QuoteOptions

Interface

Limits and target to quote a payment and probe the rate.

PropertyTypeDescription
pluginPluginPlugin to send packets over a connected Interledger network (no receive functionality is necessary). Pay does not call connect or disconnect on the plugin, so the application must perform that manually.
destinationResolvedPaymentResolved destination details of the payment, including the asset, Incoming Payment, and connection establishment information.
sourceAsset (Optional)AssetDetailsSource asset and denomination for the sender. Required to compute the minimum exchange rate, unless slippage is 100%.
amountToSend (Optional)string, number, bigint or IntFixed amount to send to the recipient, in base units of the sending asset. Either amountToSend, amountToDeliver, or destinationPayment must be provided, in order to determine how much to pay.
amountToDeliver (Optional)string, number, bigint or IntFixed amount to deliver to the recipient, in base units of the destination asset. destinationPayment is recommended method to send fixed delivery payments, but this option enables sending a fixed-delivery payment to an SPSP server that doesn't support Open Payments.Note: this option requires the destination asset to be known in advance. The application must ensure the destination asset resolved via STREAM is the expected asset and denomination.
prices (Optional){ [string]: number }Object of asset codes to prices in a standardized base asset to compute exchange rates. For example, using U.S. dollars as a base asset: { USD: 1, EUR: 1.09, BTC: 8806.94 }.If the source and destination assets are the same, a 1:1 rate will be used as the basis, so prices doesn't need to be provided. It may also be omitted if the slippage is set to 100%, since no minimum exchange rates will be enforced.
slippage (Optional)numberPercentage to subtract from the external exchange rate to determine the minimum acceptable exchange rate and destination amount for each packet, between 0 and 1 (inclusive). Defaults to 0.01, or 1% slippage below the exchange rate computed from the given prices.If 1 is provided for a fixed source amount payment, no minimum exchange rate will be enforced. For fixed delivery payments, slippage cannot be 100%.

Quote

Interface

Parameters of payment execution and the projected outcome of a payment.

PropertyTypeDescription
paymentTypePaymentTypeThe completion criteria of the payment. For fixed source amount payments, "FixedSend"; for Incoming Payments and fixed delivery payments, "FixedDelivery".
maxSourceAmountbigintMaximum amount that will be sent in the base unit and asset of the sending account. This is intended to be presented to the user or agent before authorizing a fixed delivery payment. For fixed source amount payments, this will be the provided amountToSend.
minDeliveryAmountbigintMinimum amount that will be delivered if the payment completes, in the base unit and asset of the receiving account. For fixed delivery payments, this will be the provided amountToDeliver or amount of the Incoming Payment.
maxPacketAmountbigintDiscovered maximum packet amount allowed over this payment path.
minExchangeRateRatioAggregate exchange rate the payment is guaranteed to meet, as a ratio of destination base units to source base units. Corresponds to the minimum exchange rate enforced on each packet (*except for the final packet) to ensure sufficient money gets delivered. For strict bookkeeping, use maxSourceAmount instead.
lowEstimatedExchangeRateRatioLower bound of probed exchange rate over the path (inclusive). Ratio of destination base units to source base units
highEstimatedExchangeRateRatioUpper bound of probed exchange rate over the path (exclusive). Ratio of destination base units to source base units

PayOptions

Interface

Payment execution parameters.

PropertyTypeDescription
pluginPluginPlugin to send packets over a connected Interledger network (no receive functionality is necessary). Pay does not call connect or disconnect on the plugin, so the application must perform that manually.
destinationResolvedPaymentResolved destination details of the payment, including the asset, Incoming Payment, and connection establishment information.
quoteQuoteParameters and rates to enforce during payment execution.
progressHandler (Optional)(progress:PaymentProgress) => voidCallback to process streaming updates as packets are sent and received, such as to perform accounting while the payment is in progress. Handler will be called for all fulfillable packets and replies before the payment resolves.

PaymentProgress

Interface

Intermediate state or outcome of the payment, to account for sent/delivered amounts. If the payment failed, the error property is included.

PropertyTypeDescription
error (Optional)PaymentErrorError state, if the payment failed.
amountSentbigintAmount sent and fulfilled, in base units of the source asset.
amountDeliveredbigintAmount delivered to the recipient, in base units of the destination asset.
sourceAmountInFlightbigintAmount sent that is yet to be fulfilled or rejected, in base units of the source asset.
destinationAmountInFlightbigintEstimate of the amount that may be delivered from in-flight packets, in base units of the destination asset.
streamReceipt (Optional)Uint8ArrayLatest STREAM receipt to provide proof-of-delivery to a 3rd party verifier.

IncomingPayment

Interface

Open Payments Incoming Payment metadata

PropertyTypeDescription
idstringURL used to query and identify the Incoming Payment.
paymentPointerstringURL of the recipient Open Payments account to which incoming payments will be credited (with well-known path, and stripped trailing slash). Each payment pointer and its corresponding paymentPointer identifies a unique payment recipient.
completedbooleanDescribes whether the Incoming Payment has completed receiving funds.
incomingAmountAmount (Optional)Fixed destination amount that must be delivered to complete payment of the Incoming Payment.
receivedAmountAmountAmount that has already been paid toward the Incoming Payment.
expiresAtnumber (Optional)UNIX timestamp in milliseconds after which payments toward the Incoming Payment will no longer be accepted.
descriptionstring (Optional)Human-readable description of what is provided in return for completion of the Incoming Payment.
externalRefstring (Optional)Human-readable external reference that can be used by external systems to reconcile this payment with outside systems.

Amount

Interface

Amount details of an IncomingPayment.

PropertyTypeDescription
valuebigintAmount, in base units.
assetScalenumberPrecision of the asset denomination: number of decimal places of the ordinary unit.
assetCodestringAsset code or symbol identifying the currency of the account.

AssetDetails

Interface

Asset and denomination for an Interledger account (source or destination asset)

PropertyTypeDescription
scalenumberPrecision of the asset denomination: number of decimal places of the ordinary unit.
codestringAsset code or symbol identifying the currency of the account.

PaymentType

String enum

Completion criteria of the payment

VariantDescription
FixedSendSend up to a maximum source amount
FixedDeliverySend to meet a minimum delivery amount, bounding the source amount and rates

PaymentError

String enum

Payment error states

Errors likely caused by the user
VariantDescription
InvalidPaymentPointerPayment pointer or SPSP URL is syntactically invalid
InvalidCredentialsNo valid STREAM credentials or URL to fetch them was provided
InvalidSlippageSlippage percentage is not between 0 and 1 (inclusive)
UnknownSourceAssetSource asset or denomination was not provided
UnknownPaymentTargetNo fixed source amount or fixed destination amount was provided
InvalidSourceAmountFixed source amount is not a positive integer
InvalidDestinationAmountFixed delivery amount is not a positive integer
UnenforceableDeliveryMinimum exchange rate of 0 cannot enforce a fixed-delivery payment
Errors likely caused by the receiver, connectors, or other externalities
VariantDescription
QueryFailedFailed to query the Open Payments or SPSP server, or received an invalid response
IncomingPaymentCompletedIncoming payment was already completed by the Open Payments server, so no payment is necessary
IncomingPaymentExpiredIncoming payment has already expired, so no payment is possible
ConnectorErrorCannot send over this path due to an ILP Reject error
EstablishmentFailedNo authentic reply from receiver: packets may not have been delivered
UnknownDestinationAssetDestination asset details are unknown or the receiver never provided them
DestinationAssetConflictReceiver sent conflicting destination asset details
ExternalRateUnavailableFailed to compute minimum rate: prices for source or destination assets were invalid or not provided
RateProbeFailedRate probe failed to establish the exchange rate or discover path max packet amount
InsufficientExchangeRateReal exchange rate is less than minimum exchange rate with slippage
IdleTimeoutNo packets were fulfilled within timeout
ClosedByReceiverReceiver closed the connection or stream, terminating the payment
IncompatibleReceiveMaxEstimated destination amount exceeds the receiver's limit
ReceiverProtocolViolationReceiver violated the STREAM protocol, misrepresenting delivered amounts
MaxSafeEncryptionLimitEncrypted maximum number of packets using the key for this connection