commit
ac5b5e675d
7 changed files with 600 additions and 163 deletions
|
|
@ -8,7 +8,11 @@ const { Constants, Schema } = Common
|
|||
|
||||
const { ErrorCode } = Constants
|
||||
|
||||
const LightningServices = require('../../../utils/lightningServices')
|
||||
const { sendPaymentV2Invoice } = require('../../../utils/lightningServices/v2')
|
||||
|
||||
/**
|
||||
* @typedef {import('../../../utils/lightningServices/types').PaymentV2} PaymentV2
|
||||
*/
|
||||
|
||||
const Getters = require('./getters')
|
||||
const Key = require('./key')
|
||||
|
|
@ -903,20 +907,11 @@ const sendHRWithInitialMsg = async (
|
|||
* @param {number} amount
|
||||
* @param {string} memo
|
||||
* @param {number} feeLimit
|
||||
* @param {number=} maxParts
|
||||
* @param {number=} timeoutSeconds
|
||||
* @throws {Error} If no response in less than 20 seconds from the recipient, or
|
||||
* lightning cannot find a route for the payment.
|
||||
* @returns {Promise<string>} The payment's preimage.
|
||||
* @returns {Promise<PaymentV2>} The payment's preimage.
|
||||
*/
|
||||
const sendPayment = async (
|
||||
to,
|
||||
amount,
|
||||
memo,
|
||||
feeLimit,
|
||||
maxParts = 3,
|
||||
timeoutSeconds = 5
|
||||
) => {
|
||||
const sendSpontaneousPayment = async (to, amount, memo, feeLimit) => {
|
||||
try {
|
||||
const SEA = require('../Mediator').mySEA
|
||||
const getUser = () => require('../Mediator').getUser()
|
||||
|
|
@ -1046,69 +1041,27 @@ const sendPayment = async (
|
|||
throw new Error(orderResponse.response)
|
||||
}
|
||||
|
||||
const {
|
||||
services: { router }
|
||||
} = LightningServices
|
||||
|
||||
/**
|
||||
* @typedef {object} SendErr
|
||||
* @prop {string} details
|
||||
*/
|
||||
|
||||
/**
|
||||
* Partial
|
||||
* https://api.lightning.community/#sendpaymentv2
|
||||
* @typedef {object} SendResponse
|
||||
* @prop {string} failure_reason
|
||||
* @prop {string} payment_preimage
|
||||
*/
|
||||
|
||||
logger.info('Will now send payment through lightning')
|
||||
|
||||
const sendPaymentV2Args = {
|
||||
/** @type {string} */
|
||||
payment_request: orderResponse.response,
|
||||
max_parts: maxParts,
|
||||
timeout_seconds: timeoutSeconds,
|
||||
no_inflight_updates: true,
|
||||
fee_limit_sat: feeLimit
|
||||
}
|
||||
|
||||
const preimage = await new Promise((res, rej) => {
|
||||
const sentPaymentStream = router.sendPaymentV2(sendPaymentV2Args)
|
||||
/**
|
||||
* @param {SendResponse} response
|
||||
*/
|
||||
const dataCB = response => {
|
||||
logger.info('SendPayment Data:', response)
|
||||
if (response.failure_reason !== 'FAILURE_REASON_NONE') {
|
||||
rej(new Error(response.failure_reason))
|
||||
} else {
|
||||
res(response.payment_preimage)
|
||||
}
|
||||
}
|
||||
sentPaymentStream.on('data', dataCB)
|
||||
/**
|
||||
*
|
||||
* @param {SendErr} err
|
||||
*/
|
||||
const errCB = err => {
|
||||
logger.error('SendPayment Error:', err)
|
||||
rej(err.details)
|
||||
}
|
||||
sentPaymentStream.on('error', errCB)
|
||||
const payment = await sendPaymentV2Invoice({
|
||||
feeLimit,
|
||||
payment_request: orderResponse.response
|
||||
})
|
||||
|
||||
if (Utils.successfulHandshakeAlreadyExists(to)) {
|
||||
await sendMessage(
|
||||
to,
|
||||
Schema.encodeSpontaneousPayment(amount, memo || 'no memo', preimage),
|
||||
Schema.encodeSpontaneousPayment(
|
||||
amount,
|
||||
memo || 'no memo',
|
||||
payment.payment_preimage
|
||||
),
|
||||
require('../Mediator').getUser(),
|
||||
require('../Mediator').mySEA
|
||||
)
|
||||
}
|
||||
|
||||
return preimage
|
||||
return payment
|
||||
} catch (e) {
|
||||
logger.error('Error inside sendPayment()')
|
||||
logger.error(e)
|
||||
|
|
@ -1116,6 +1069,21 @@ const sendPayment = async (
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the preimage corresponding to the payment.
|
||||
* @param {string} to
|
||||
* @param {number} amount
|
||||
* @param {string} memo
|
||||
* @param {number} feeLimit
|
||||
* @throws {Error} If no response in less than 20 seconds from the recipient, or
|
||||
* lightning cannot find a route for the payment.
|
||||
* @returns {Promise<string>} The payment's preimage.
|
||||
*/
|
||||
const sendPayment = async (to, amount, memo, feeLimit) => {
|
||||
const payment = await sendSpontaneousPayment(to, amount, memo, feeLimit)
|
||||
return payment.payment_preimage
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {UserGUNNode} user
|
||||
* @returns {Promise<void>}
|
||||
|
|
@ -1632,5 +1600,6 @@ module.exports = {
|
|||
follow,
|
||||
unfollow,
|
||||
initWall,
|
||||
sendMessageNew
|
||||
sendMessageNew,
|
||||
sendSpontaneousPayment
|
||||
}
|
||||
|
|
|
|||
141
src/routes.js
141
src/routes.js
|
|
@ -29,6 +29,10 @@ const {
|
|||
const GunActions = require('../services/gunDB/contact-api/actions')
|
||||
const GunGetters = require('../services/gunDB/contact-api/getters')
|
||||
const GunKey = require('../services/gunDB/contact-api/key')
|
||||
const {
|
||||
sendPaymentV2Keysend,
|
||||
sendPaymentV2Invoice
|
||||
} = require('../utils/lightningServices/v2')
|
||||
|
||||
const DEFAULT_MAX_NUM_ROUTES_TO_QUERY = 10
|
||||
const SESSION_ID = uuid()
|
||||
|
|
@ -1159,6 +1163,57 @@ module.exports = async (
|
|||
})
|
||||
})
|
||||
|
||||
app.post('/api/lnd/unifiedTrx', async (req, res) => {
|
||||
try {
|
||||
const { type, amt, to, memo, feeLimit } = req.body
|
||||
|
||||
if (type !== 'spont') {
|
||||
return res.status(415).json({
|
||||
field: 'type',
|
||||
errorMessage: `Only 'spont' payments supported via this endpoint for now.`
|
||||
})
|
||||
}
|
||||
|
||||
const amount = Number(amt)
|
||||
|
||||
if (!isARealUsableNumber(amount)) {
|
||||
return res.status(400).json({
|
||||
field: 'amt',
|
||||
errorMessage: 'Not an usable number'
|
||||
})
|
||||
}
|
||||
|
||||
if (amount < 1) {
|
||||
return res.status(400).json({
|
||||
field: 'amt',
|
||||
errorMessage: 'Must be 1 or greater.'
|
||||
})
|
||||
}
|
||||
|
||||
if (!isARealUsableNumber(feeLimit)) {
|
||||
return res.status(400).json({
|
||||
field: 'feeLimit',
|
||||
errorMessage: 'Not an usable number'
|
||||
})
|
||||
}
|
||||
|
||||
if (feeLimit < 1) {
|
||||
return res.status(400).json({
|
||||
field: 'feeLimit',
|
||||
errorMessage: 'Must be 1 or greater.'
|
||||
})
|
||||
}
|
||||
|
||||
return res
|
||||
.status(200)
|
||||
.json(await GunActions.sendSpontaneousPayment(to, amt, memo, feeLimit))
|
||||
} catch (e) {
|
||||
return res.status(500).json({
|
||||
errorMessage: e.message
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// get lnd node payments list
|
||||
app.get('/api/lnd/listpayments', (req, res) => {
|
||||
const { lightning } = LightningServices.services
|
||||
|
|
@ -1442,92 +1497,46 @@ module.exports = async (
|
|||
})
|
||||
|
||||
// sendpayment
|
||||
app.post('/api/lnd/sendpayment', (req, res) => {
|
||||
const { router } = LightningServices.services
|
||||
app.post('/api/lnd/sendpayment', async (req, res) => {
|
||||
// this is the recommended value from lightning labs
|
||||
let paymentRequest = {}
|
||||
const { keysend, maxParts = 3, timeoutSeconds = 5, feeLimit } = req.body
|
||||
|
||||
if (!feeLimit) {
|
||||
return res.status(500).json({
|
||||
return res.status(400).json({
|
||||
errorMessage: 'please provide a "feeLimit" to the send payment request'
|
||||
})
|
||||
}
|
||||
|
||||
if (keysend) {
|
||||
const { dest, amt, finalCltvDelta = 40 } = req.body
|
||||
if (!dest || !amt) {
|
||||
return res.status(500).json({
|
||||
return res.status(400).json({
|
||||
errorMessage: 'please provide "dest" and "amt" for keysend payments'
|
||||
})
|
||||
}
|
||||
const preimage = Crypto.randomBytes(32)
|
||||
const r_hash = Crypto.createHash('sha256')
|
||||
.update(preimage)
|
||||
.digest()
|
||||
//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
|
||||
paymentRequest = {
|
||||
dest: Buffer.from(dest, 'hex'),
|
||||
|
||||
const payment = await sendPaymentV2Keysend({
|
||||
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,
|
||||
no_inflight_updates: true,
|
||||
fee_limit_sat: feeLimit
|
||||
dest,
|
||||
feeLimit,
|
||||
finalCltvDelta,
|
||||
maxParts,
|
||||
timeoutSeconds
|
||||
})
|
||||
|
||||
return res.status(200).json(payment)
|
||||
}
|
||||
} else {
|
||||
const { payreq } = req.body
|
||||
|
||||
paymentRequest = {
|
||||
const payment = await sendPaymentV2Invoice({
|
||||
feeLimit,
|
||||
payment_request: payreq,
|
||||
amt: req.body.amt,
|
||||
max_parts: maxParts,
|
||||
timeout_seconds: timeoutSeconds,
|
||||
no_inflight_updates: true,
|
||||
fee_limit_sat: feeLimit
|
||||
}
|
||||
|
||||
if (req.body.amt) {
|
||||
paymentRequest.amt = req.body.amt
|
||||
}
|
||||
}
|
||||
|
||||
logger.info('Sending payment', paymentRequest)
|
||||
const sentPayment = router.sendPaymentV2(paymentRequest)
|
||||
sentPayment.on('data', response => {
|
||||
logger.info('SendPayment Data:', response)
|
||||
if (response.failure_reason !== 'FAILURE_REASON_NONE') {
|
||||
res.status(500).json({
|
||||
errorMessage: response.failure_reason
|
||||
})
|
||||
} else {
|
||||
res.json(response)
|
||||
}
|
||||
timeoutSeconds
|
||||
})
|
||||
|
||||
sentPayment.on('status', status => {
|
||||
logger.info('SendPayment Status:', status)
|
||||
})
|
||||
|
||||
sentPayment.on('error', async err => {
|
||||
logger.error('SendPayment Error:', err)
|
||||
const health = await checkHealth()
|
||||
if (health.LNDStatus.success) {
|
||||
res.status(500).json({
|
||||
errorMessage: sanitizeLNDError(err.details)
|
||||
})
|
||||
} else {
|
||||
res.status(500)
|
||||
res.json({ errorMessage: 'LND is down' })
|
||||
}
|
||||
})
|
||||
//sentPayment.on('end', () => {})
|
||||
return res.status(200).json(payment)
|
||||
})
|
||||
|
||||
app.post('/api/lnd/trackpayment', (req, res) => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"include": ["./services/gunDB/**/*.*"],
|
||||
"include": ["./services/gunDB/**/*.*", "./utils/lightningServices/**/*.*"],
|
||||
"compilerOptions": {
|
||||
/* Basic Options */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
|
|
|
|||
1
utils/lightningServices/index.js
Normal file
1
utils/lightningServices/index.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
module.exports = require('./lightning-services')
|
||||
|
|
@ -1,10 +1,13 @@
|
|||
const FS = require("../utils/fs");
|
||||
const lnrpc = require("../services/lnd/lightning");
|
||||
|
||||
/**
|
||||
* @format
|
||||
*/
|
||||
/**
|
||||
* @typedef {import('commander').Command} Command
|
||||
*/
|
||||
|
||||
const FS = require('../../utils/fs')
|
||||
const lnrpc = require('../../services/lnd/lightning')
|
||||
|
||||
/**
|
||||
* @typedef {object} Config
|
||||
* @prop {boolean} useTLS
|
||||
|
|
@ -43,9 +46,9 @@ class LightningServices {
|
|||
/**
|
||||
* @type {Config}
|
||||
*/
|
||||
const newDefaults = require("../config/defaults")(program.mainnet)
|
||||
const newDefaults = require('../../config/defaults')(program.mainnet)
|
||||
|
||||
this.defaults = newDefaults;
|
||||
this.defaults = newDefaults
|
||||
|
||||
this._config = {
|
||||
...newDefaults,
|
||||
|
|
@ -55,11 +58,11 @@ class LightningServices {
|
|||
lndHost: program.lndhost || newDefaults.lndHost,
|
||||
lndCertPath: program.lndCertPath || newDefaults.lndCertPath,
|
||||
macaroonPath: program.macaroonPath || newDefaults.macaroonPath
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
isInitialized = () => {
|
||||
return !!(this.lightning && this.walletUnlocker);
|
||||
return !!(this.lightning && this.walletUnlocker)
|
||||
}
|
||||
|
||||
get services() {
|
||||
|
|
@ -67,11 +70,11 @@ class LightningServices {
|
|||
lightning: this.lightning,
|
||||
walletUnlocker: this.walletUnlocker,
|
||||
router: this.router
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
get servicesData() {
|
||||
return this.lnServicesData;
|
||||
return this.lnServicesData
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -82,7 +85,9 @@ class LightningServices {
|
|||
return this._config
|
||||
}
|
||||
|
||||
throw new Error('Tried to access LightningServices.servicesConfig without setting defaults first.')
|
||||
throw new Error(
|
||||
'Tried to access LightningServices.servicesConfig without setting defaults first.'
|
||||
)
|
||||
}
|
||||
|
||||
get config() {
|
||||
|
|
@ -90,7 +95,9 @@ class LightningServices {
|
|||
return this._config
|
||||
}
|
||||
|
||||
throw new Error('Tried to access LightningServices.config without setting defaults first.')
|
||||
throw new Error(
|
||||
'Tried to access LightningServices.config without setting defaults first.'
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -101,38 +108,38 @@ class LightningServices {
|
|||
return this._defaults
|
||||
}
|
||||
|
||||
throw new Error('Tried to access LightningServices.defaults without setting them first.')
|
||||
throw new Error(
|
||||
'Tried to access LightningServices.defaults without setting them first.'
|
||||
)
|
||||
}
|
||||
|
||||
init = async () => {
|
||||
const { macaroonPath, lndHost, lndCertPath } = this.config;
|
||||
const macaroonExists = await FS.access(macaroonPath);
|
||||
const lnServices = await lnrpc(
|
||||
{
|
||||
const { macaroonPath, lndHost, lndCertPath } = this.config
|
||||
const macaroonExists = await FS.access(macaroonPath)
|
||||
const lnServices = await lnrpc({
|
||||
lnrpcProtoPath: this.defaults.lndProto,
|
||||
routerProtoPath: this.defaults.routerProto,
|
||||
walletUnlockerProtoPath: this.defaults.walletUnlockerProto,
|
||||
lndHost,
|
||||
lndCertPath,
|
||||
macaroonPath: macaroonExists ? macaroonPath : null
|
||||
}
|
||||
);
|
||||
})
|
||||
if (!lnServices) {
|
||||
throw new Error(`Could not init lnServices`)
|
||||
}
|
||||
const { lightning, walletUnlocker, router } = lnServices;
|
||||
this.lightning = lightning;
|
||||
const { lightning, walletUnlocker, router } = lnServices
|
||||
this.lightning = lightning
|
||||
this.walletUnlocker = walletUnlocker
|
||||
this.router = router;
|
||||
this.router = router
|
||||
this.lnServicesData = {
|
||||
lndProto: this.defaults.lndProto,
|
||||
lndHost,
|
||||
lndCertPath,
|
||||
macaroonPath: macaroonExists ? macaroonPath : null
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const lightningServices = new LightningServices();
|
||||
const lightningServices = new LightningServices()
|
||||
|
||||
module.exports = lightningServices;
|
||||
module.exports = lightningServices
|
||||
108
utils/lightningServices/types.ts
Normal file
108
utils/lightningServices/types.ts
Normal 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: number
|
||||
|
||||
fee_msat: number
|
||||
|
||||
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: number
|
||||
}
|
||||
|
||||
export type SendPaymentV2Request = Partial<_SendPaymentV2Request>
|
||||
|
||||
export interface SendPaymentKeysendParams {
|
||||
amt: string
|
||||
dest: string
|
||||
feeLimit: number
|
||||
finalCltvDelta?: number
|
||||
maxParts?: number
|
||||
timeoutSeconds?: number
|
||||
}
|
||||
|
||||
export interface SendPaymentInvoiceParams {
|
||||
amt?: string
|
||||
feeLimit: number
|
||||
max_parts?: number
|
||||
payment_request: string
|
||||
timeoutSeconds?: number
|
||||
}
|
||||
343
utils/lightningServices/v2.js
Normal file
343
utils/lightningServices/v2.js
Normal file
|
|
@ -0,0 +1,343 @@
|
|||
/**
|
||||
* @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 !== 'number') {
|
||||
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 !== 'number') {
|
||||
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: ${JSON.stringify(params)}`
|
||||
)
|
||||
}
|
||||
|
||||
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: ${JSON.stringify(params)}`
|
||||
)
|
||||
}
|
||||
|
||||
return sendPaymentV2({
|
||||
amt,
|
||||
payment_request,
|
||||
fee_limit_sat: feeLimit,
|
||||
max_parts,
|
||||
timeout_seconds: timeoutSeconds
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
sendPaymentV2Keysend,
|
||||
sendPaymentV2Invoice
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue