better sendPayment()

This commit is contained in:
Daniel Lugo 2020-02-28 16:29:47 -04:00
parent 832957171c
commit da3c4cba24

View file

@ -3,6 +3,9 @@
*/ */
const uuidv1 = require('uuid/v1') const uuidv1 = require('uuid/v1')
const logger = require('winston') const logger = require('winston')
const isFinite = require('lodash/isFinite')
const isNumber = require('lodash/isNumber')
const isNaN = require('lodash/isNaN')
const LightningServices = require('../../../utils/lightningServices') const LightningServices = require('../../../utils/lightningServices')
@ -882,124 +885,160 @@ const sendHRWithInitialMsg = async (
* lightning cannot find a route for the payment. * lightning cannot find a route for the payment.
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
const sendPayment = async (to, amount, memo, gun, user, SEA) => { const sendPayment = async (to, amount, memo) => {
if (!user.is) { try {
throw new Error(ErrorCode.NOT_AUTH) const SEA = require('../Mediator').mySEA
} const getUser = () => require('../Mediator').getUser()
const recipientEpub = await Utils.pubToEpub(to)
const ourSecret = await SEA.secret(recipientEpub, getUser()._.sea)
const recipientEpub = await Utils.pubToEpub(to) if (amount < 1) {
const ourSecret = await SEA.secret(recipientEpub, user._.sea) throw new RangeError('Amount must be at least 1 sat.')
}
if (amount < 1) { const currOrderAddress = await Getters.currentOrderAddress(to)
throw new RangeError('Amount must be at least 1 sat.')
}
/** @type {Order} */ logger.info('sendPayment() -> will now create order:')
const order = {
amount: await SEA.encrypt(amount.toString(), ourSecret),
from: user._.sea.pub,
memo: await SEA.encrypt(memo || 'no memo', ourSecret),
timestamp: Date.now()
}
const currOrderAddress = await Getters.currentOrderAddress(to) /** @type {Order} */
const order = {
amount: amount.toString(),
from: getUser()._.sea.pub,
memo: memo || 'no memo',
timestamp: Date.now()
}
order.timestamp = Date.now() logger.info(JSON.stringify(order))
/** @type {string} */ /* eslint-disable require-atomic-updates */
const orderID = await new Promise((res, rej) => { const [encMemo, encAmount] = await Promise.all([
const ord = gun SEA.encrypt(order.memo, ourSecret),
.get(Key.ORDER_NODES) SEA.encrypt(order.amount, ourSecret)
.get(currOrderAddress) ])
.set(order, ack => {
if (ack.err) {
rej(
new Error(
`Error writing order to order node: ${currOrderAddress} for pub: ${to}: ${ack.err}`
)
)
} else {
res(ord._.get)
}
})
})
const bob = gun.user(to) order.memo = encMemo
order.amount = encAmount
order.timestamp = Date.now() // most up to date timestamp
logger.info(`sendPayment() -> encrypted order: ${JSON.stringify(order)}`)
/** @type {string} */ /* eslint-enable require-atomic-updates */
const invoice = await Promise.race([
new Promise((res, rej) => { logger.info(
bob `sendPayment() -> will now place order into address: ${currOrderAddress} for PK: ${to}`
.get(Key.ORDER_TO_RESPONSE) )
.get(orderID)
.on(data => { /** @type {string} */
if (typeof data !== 'string') { const orderID = await new Promise((res, rej) => {
const ord = require('../Mediator')
.getGun()
.get(Key.ORDER_NODES)
.get(currOrderAddress)
.set(order, ack => {
if (ack.err) {
rej( rej(
`Expected order response from pub ${to} to be an string, instead got: ${typeof data}` new Error(
`Error writing order to order node: ${currOrderAddress} for pub: ${to}: ${ack.err}`
)
) )
} else { } else {
res(data) res(ord._.get)
} }
}) })
}),
new Promise((_, rej) => {
setTimeout(() => {
rej(new Error(ErrorCode.ORDER_NOT_ANSWERED_IN_TIME))
}, 20000)
}) })
])
const decInvoice = await SEA.decrypt(invoice, ourSecret) if (typeof orderID !== 'string') {
const msg = `orderID returned by gun not an string, got: ${JSON.stringify(
orderID
)}`
throw new Error(msg)
}
const { /** @type {ReturnType<typeof setTimeout>} */
services: { lightning } // eslint-disable-next-line init-declarations
} = LightningServices let timeoutID
/** /** @type {string} */
* @typedef {object} SendErr const invoice = await Promise.race([
* @prop {string} details Utils.tryAndWait(
*/ gun =>
gun
.user(to)
.get(Key.ORDER_TO_RESPONSE)
.get(orderID)
.then()
.then(v => {
clearTimeout(timeoutID)
return v
}),
v => typeof v !== 'string'
),
new Promise((_, rej) => {
setTimeout(() => {
rej(new Error(ErrorCode.ORDER_NOT_ANSWERED_IN_TIME))
}, 20000)
})
])
/** const decInvoice = await SEA.decrypt(invoice, ourSecret)
* Partial
* https://api.lightning.community/#grpc-response-sendresponse-2
* @typedef {object} SendResponse
* @prop {string|null} payment_error
* @prop {any[]|null} payment_route
*/
await new Promise((resolve, rej) => { logger.info('decoded invoice: ' + decInvoice)
lightning.sendPaymentSync(
{ const {
payment_request: decInvoice services: { lightning }
}, } = LightningServices
(/** @type {SendErr=} */ err, /** @type {SendResponse} */ res) => {
if (err) { /**
rej(new Error(err.details)) * @typedef {object} SendErr
} else if (res) { * @prop {string} details
if (res.payment_error) { */
rej(
new Error( /**
`sendPaymentSync error response: ${JSON.stringify(res)}` * Partial
* https://api.lightning.community/#grpc-response-sendresponse-2
* @typedef {object} SendResponse
* @prop {string|null} payment_error
* @prop {any[]|null} payment_route
*/
logger.info('Will now send payment through lightning')
await new Promise((resolve, rej) => {
lightning.sendPaymentSync(
{
payment_request: decInvoice
},
(/** @type {SendErr=} */ err, /** @type {SendResponse} */ res) => {
if (err) {
rej(new Error(err.details))
} else if (res) {
if (res.payment_error) {
rej(
new Error(
`sendPaymentSync error response: ${JSON.stringify(res)}`
)
) )
) } else if (!res.payment_route) {
} else if (!res.payment_route) { rej(
rej( new Error(
new Error( `sendPaymentSync no payment route response: ${JSON.stringify(
`sendPaymentSync no payment route response: ${JSON.stringify( res
res )}`
)}` )
) )
) } else {
resolve()
}
} else { } else {
resolve() rej(new Error('no error or response received from sendPaymentSync'))
} }
} else {
rej(new Error('no error or response received from sendPaymentSync'))
} }
} )
) })
}) } catch (e) {
logger.error('Error inside sendPayment()')
logger.error(e)
throw e
}
} }
/** /**