sendPaymentV2 wrappers

This commit is contained in:
Daniel Lugo 2020-08-21 13:46:24 -04:00
parent 0a82226e2c
commit 7543a7eca9
4 changed files with 452 additions and 4 deletions

View file

@ -0,0 +1 @@
module.exports = require('./lightning-services')

View file

@ -1,13 +1,13 @@
/** /**
* @format * @format
*/ */
const FS = require('../utils/fs')
const lnrpc = require('../services/lnd/lightning')
/** /**
* @typedef {import('commander').Command} Command * @typedef {import('commander').Command} Command
*/ */
const FS = require('../../utils/fs')
const lnrpc = require('../../services/lnd/lightning')
/** /**
* @typedef {object} Config * @typedef {object} Config
* @prop {boolean} useTLS * @prop {boolean} useTLS
@ -46,7 +46,7 @@ class LightningServices {
/** /**
* @type {Config} * @type {Config}
*/ */
const newDefaults = require('../config/defaults')(program.mainnet) const newDefaults = require('../../config/defaults')(program.mainnet)
this.defaults = newDefaults this.defaults = newDefaults

View file

@ -0,0 +1,108 @@
/**
* @format
*/
export interface PaymentV2 {
payment_hash: string
creation_date: string
payment_preimage: string
value_sat: string
value_msat: string
payment_request: string
status: 'UNKNOWN' | 'IN_FLIGHT' | 'SUCCEEDED' | 'FAILED'
fee_sat: string
fee_msat: string
creation_time_ns: string
payment_index: string
failure_reason:
| 'FAILURE_REASON_NONE'
| 'FAILURE_REASON_TIMEOUT'
| 'FAILURE_REASON_NO_ROUTE'
| 'FAILURE_REASON_ERROR'
| 'FAILURE_REASON_INCORRECT_PAYMENT_DETAILS'
| 'FAILURE_REASON_INSUFFICIENT_BALANCE'
}
enum FeatureBit {
DATALOSS_PROTECT_REQ = 0,
DATALOSS_PROTECT_OPT = 1,
INITIAL_ROUING_SYNC = 3,
UPFRONT_SHUTDOWN_SCRIPT_REQ = 4,
UPFRONT_SHUTDOWN_SCRIPT_OPT = 5,
GOSSIP_QUERIES_REQ = 6,
GOSSIP_QUERIES_OPT = 7,
TLV_ONION_REQ = 8,
TLV_ONION_OPT = 9,
EXT_GOSSIP_QUERIES_REQ = 10,
EXT_GOSSIP_QUERIES_OPT = 11,
STATIC_REMOTE_KEY_REQ = 12,
STATIC_REMOTE_KEY_OPT = 13,
PAYMENT_ADDR_REQ = 14,
PAYMENT_ADDR_OPT = 15,
MPP_REQ = 16,
MPP_OPT = 17
}
interface _SendPaymentV2Request {
dest: Buffer
/**
* Number of satoshis to send. The fields amt and amt_msat are mutually
* exclusive.
*/
amt: string
/**
* The CLTV delta from the current height that should be used to set the
* timelock for the final hop.
*/
final_cltv_delta: number
dest_features: FeatureBit[]
dest_custom_records: Record<number, Buffer>
/**
* The hash to use within the payment's HTLC.
*/
payment_hash: Buffer
max_parts: number
timeout_seconds: number
no_inflight_updates: boolean
payment_request: string
fee_limit_sat: string
}
export type SendPaymentV2Request = Partial<_SendPaymentV2Request>
export interface SendPaymentKeysendParams {
amt: string
dest: string
feeLimit: string
finalCltvDelta?: number
maxParts?: number
timeoutSeconds?: number
}
export interface SendPaymentInvoiceParams {
amt?: string
feeLimit: string
max_parts?: number
payment_request: string
timeoutSeconds?: number
}

View file

@ -0,0 +1,339 @@
/**
* @format
*/
const Crypto = require('crypto')
const logger = require('winston')
const lightningServices = require('./lightning-services')
/**
* @typedef {import('./types').PaymentV2} PaymentV2
* @typedef {import('./types').SendPaymentV2Request} SendPaymentV2Request
* @typedef {import('./types').SendPaymentInvoiceParams} SendPaymentInvoiceParams
* @typedef {import('./types').SendPaymentKeysendParams} SendPaymentKeysendParams
*/
//https://github.com/lightningnetwork/lnd/blob/master/record/experimental.go#L5:2
//might break in future updates
const KeySendType = 5482373484
//https://api.lightning.community/#featurebit
const TLV_ONION_REQ = 8
/**
* @param {SendPaymentV2Request} sendPaymentRequest
* @returns {boolean}
*/
const isValidSendPaymentRequest = sendPaymentRequest => {
const {
amt,
dest,
dest_custom_records,
dest_features,
final_cltv_delta,
max_parts,
no_inflight_updates,
payment_hash,
timeout_seconds,
fee_limit_sat,
payment_request
} = sendPaymentRequest
if (typeof amt !== 'undefined' && typeof amt !== 'string') {
return false
}
if (typeof dest !== 'undefined' && !(dest instanceof Buffer)) {
return false
}
if (
typeof dest_custom_records !== 'undefined' &&
typeof dest_custom_records !== 'object'
) {
return false
}
if (
typeof dest_custom_records !== 'undefined' &&
dest_custom_records === null
) {
return false
}
if (
typeof dest_custom_records !== 'undefined' &&
Object.keys(dest_custom_records).length === 0
) {
return false
}
if (typeof dest_features !== 'undefined' && !Array.isArray(dest_features)) {
return false
}
if (typeof dest_features !== 'undefined' && dest_features.length === 0) {
return false
}
if (
typeof final_cltv_delta !== 'undefined' &&
typeof final_cltv_delta !== 'number'
) {
return false
}
if (
typeof payment_hash !== 'undefined' &&
!(payment_hash instanceof Buffer)
) {
return false
}
if (typeof max_parts !== 'undefined' && typeof max_parts !== 'number') {
return false
}
if (
typeof timeout_seconds !== 'undefined' &&
typeof timeout_seconds !== 'number'
) {
return false
}
if (
typeof no_inflight_updates !== 'undefined' &&
typeof no_inflight_updates !== 'boolean'
) {
return false
}
if (
typeof fee_limit_sat !== 'undefined' &&
typeof fee_limit_sat !== 'number'
) {
return false
}
if (
typeof payment_request !== 'undefined' &&
typeof payment_request !== 'string'
) {
return false
}
return true
}
/**
* @param {SendPaymentKeysendParams} sendPaymentKeysendParams
* @returns {boolean}
*/
const isValidSendPaymentKeysendParams = sendPaymentKeysendParams => {
const {
amt,
dest,
feeLimit,
finalCltvDelta,
maxParts,
timeoutSeconds
} = sendPaymentKeysendParams
if (typeof amt !== 'string') {
return false
}
if (typeof dest !== 'string') {
return false
}
if (typeof feeLimit !== 'string') {
return false
}
if (
typeof finalCltvDelta !== 'undefined' &&
typeof finalCltvDelta !== 'number'
) {
return false
}
if (typeof maxParts !== 'undefined' && typeof maxParts !== 'number') {
return false
}
if (
typeof timeoutSeconds !== 'undefined' &&
typeof timeoutSeconds !== 'number'
) {
return false
}
return true
}
/**
* @param {SendPaymentInvoiceParams} sendPaymentInvoiceParams
*/
const isValidSendPaymentInvoiceParams = sendPaymentInvoiceParams => {
const {
amt,
feeLimit,
max_parts,
payment_request,
timeoutSeconds
} = sendPaymentInvoiceParams
// payment_request: string
// timeoutSeconds?: number
if (typeof amt !== 'undefined' && typeof amt !== 'string') {
return false
}
if (typeof feeLimit !== 'string') {
return false
}
if (typeof max_parts !== 'undefined' && typeof max_parts !== 'number') {
return false
}
if (typeof payment_request !== 'string') {
return false
}
if (
typeof timeoutSeconds !== 'undefined' &&
typeof timeoutSeconds !== 'number'
) {
return false
}
return true
}
/**
* aklssjdklasd
* @param {SendPaymentV2Request} sendPaymentRequest
* @returns {Promise<PaymentV2>}
*/
const sendPaymentV2 = sendPaymentRequest => {
const {
services: { router }
} = lightningServices
if (!isValidSendPaymentRequest(sendPaymentRequest)) {
throw new TypeError(
`Invalid SendPaymentRequest: ${JSON.stringify(sendPaymentRequest)}`
)
}
/**
* @type {SendPaymentV2Request}
*/
const paymentRequest = {
...sendPaymentRequest,
no_inflight_updates: true
}
return new Promise((res, rej) => {
const stream = router.sendPaymentV2(paymentRequest)
stream.on(
'data',
/**
* @param {import("./types").PaymentV2} streamingPaymentV2
*/ streamingPaymentV2 => {
if (streamingPaymentV2.failure_reason !== 'FAILURE_REASON_NONE') {
rej(new Error(streamingPaymentV2.failure_reason))
} else {
res(streamingPaymentV2)
}
}
)
// @ts-expect-error
stream.on('status', status => {
logger.info('SendPaymentV2 Status:', status)
})
stream.on(
'error',
/**
* @param {{ details: any; }} err
*/ err => {
logger.error('SendPaymentV2 Error:', err)
rej(err.details || err)
}
)
})
}
/**
* @param {SendPaymentKeysendParams} params
* @returns {Promise<PaymentV2>}
*/
const sendPaymentV2Keysend = params => {
const {
amt,
dest,
feeLimit,
finalCltvDelta,
maxParts,
timeoutSeconds
} = params
if (!isValidSendPaymentKeysendParams(params)) {
throw new TypeError('Invalid SendPaymentKeysendParams')
}
const preimage = Crypto.randomBytes(32)
const r_hash = Crypto.createHash('sha256')
.update(preimage)
.digest()
return sendPaymentV2({
dest: Buffer.from(dest, 'hex'),
amt,
final_cltv_delta: finalCltvDelta,
dest_features: [TLV_ONION_REQ],
dest_custom_records: {
[KeySendType]: preimage
},
payment_hash: r_hash,
max_parts: maxParts,
timeout_seconds: timeoutSeconds,
fee_limit_sat: feeLimit
})
}
/**
* @param {SendPaymentInvoiceParams} params
* @returns {Promise<PaymentV2>}
*/
const sendPaymentV2Invoice = params => {
const {
feeLimit,
payment_request,
amt,
max_parts = 3,
timeoutSeconds = 5
} = params
if (!isValidSendPaymentInvoiceParams(params)) {
throw new TypeError('Invalid SendPaymentInvoiceParams')
}
return sendPaymentV2({
amt,
payment_request,
fee_limit_sat: feeLimit,
max_parts,
timeout_seconds: timeoutSeconds
})
}
module.exports = {
sendPaymentV2Keysend,
sendPaymentV2Invoice
}