diff --git a/services/gunDB/contact-api/actions.js b/services/gunDB/contact-api/actions.js index 94e405ca..7f758808 100644 --- a/services/gunDB/contact-api/actions.js +++ b/services/gunDB/contact-api/actions.js @@ -3,9 +3,6 @@ */ const uuidv1 = require('uuid/v1') const logger = require('winston') -const isFinite = require('lodash/isFinite') -const isNumber = require('lodash/isNumber') -const isNaN = require('lodash/isNaN') const LightningServices = require('../../../utils/lightningServices') @@ -14,7 +11,11 @@ const Getters = require('./getters') const Key = require('./key') const Utils = require('./utils') // const { promisifyGunNode: p } = Utils -const { isHandshakeRequest, isOrderResponse } = require('./schema') +const { + isHandshakeRequest, + isOrderResponse, + encodeSpontaneousPayment +} = require('./schema') /** * @typedef {import('./SimpleGUN').GUNNode} GUNNode * @typedef {import('./SimpleGUN').ISEA} ISEA @@ -1027,6 +1028,7 @@ const sendPayment = async (to, amount, memo) => { * @typedef {object} SendResponse * @prop {string|null} payment_error * @prop {any[]|null} payment_route + * @prop {string} payment_preimage */ logger.info('Will now send payment through lightning') @@ -1036,7 +1038,8 @@ const sendPayment = async (to, amount, memo) => { payment_request: orderResponse.response } - await new Promise((resolve, rej) => { + /** @type {string} */ + const preimage = await new Promise((resolve, rej) => { lightning.sendPaymentSync(sendPaymentSyncArgs, ( /** @type {SendErr=} */ err, /** @type {SendResponse} */ res @@ -1050,22 +1053,31 @@ const sendPayment = async (to, amount, memo) => { `sendPaymentSync error response: ${JSON.stringify(res)}` ) ) - } else if (!res.payment_route) { + } else if (!res.payment_route || !res.payment_preimage) { rej( new Error( - `sendPaymentSync no payment route response: ${JSON.stringify( + `sendPaymentSync no payment route response or preimage: ${JSON.stringify( res )}` ) ) } else { - resolve() + resolve(res.payment_preimage) } } else { rej(new Error('no error or response received from sendPaymentSync')) } }) }) + + if (Utils.successfulHandshakeAlreadyExists(to)) { + await sendMessage( + to, + encodeSpontaneousPayment(to, memo || 'no memo', preimage), + require('../Mediator').getUser(), + require('../Mediator').mySEA + ) + } } catch (e) { logger.error('Error inside sendPayment()') logger.error(e) diff --git a/services/gunDB/contact-api/schema-types.ts b/services/gunDB/contact-api/schema-types.ts new file mode 100644 index 00000000..2d96ab21 --- /dev/null +++ b/services/gunDB/contact-api/schema-types.ts @@ -0,0 +1,15 @@ +/** + * @format + * Contains types that are used throughout the application. Written in + * typescript for easier implementation. + * + * Nominal types are archieved through the enum method as outlined here: + * https://basarat.gitbook.io/typescript/main-1/nominaltyping + */ +enum _EncSpontPayment { + _ = '' +} +/** + * Spontaneous payment as found inside a chat message body. + */ +export type EncSpontPayment = _EncSpontPayment & string diff --git a/services/gunDB/contact-api/schema.js b/services/gunDB/contact-api/schema.js index 435c0ec4..9f2035ef 100644 --- a/services/gunDB/contact-api/schema.js +++ b/services/gunDB/contact-api/schema.js @@ -1,6 +1,11 @@ /** * @format */ +const logger = require('winston') +const isFinite = require('lodash/isFinite') +const isNumber = require('lodash/isNumber') +const isNaN = require('lodash/isNaN') + /** * @typedef {object} HandshakeRequest * @prop {string} from Public key of the requestor. @@ -410,3 +415,105 @@ exports.isOrderResponse = o => { return obj.type === 'err' || obj.type === 'invoice' } + +/** + * @typedef {import('./schema-types').EncSpontPayment} EncSpontPayment + */ + +const ENC_SPONT_PAYMENT_PREFIX = '$$__SHOCKWALLET__SPONT__PAYMENT__' + +/** + * @param {string} s + * @returns {s is EncSpontPayment} + */ +const isEncodedSpontPayment = s => s.indexOf(ENC_SPONT_PAYMENT_PREFIX) === 0 + +exports.isEncodedSpontPayment = isEncodedSpontPayment + +/** + * @typedef {object} SpontaneousPayment + * @prop {number} amt + * @prop {string} memo + * @prop {string} preimage + */ + +/** + * + * @param {EncSpontPayment} sp + * @throws {Error} If decoding fails. + * @returns {SpontaneousPayment} + */ +exports.decodeSpontPayment = sp => { + try { + const [preimage, amtStr, memo] = sp + .slice(ENC_SPONT_PAYMENT_PREFIX.length) + .split('__') + + if (typeof preimage !== 'string') { + throw new TypeError('Could not parse preimage') + } + + if (typeof amtStr !== 'string') { + throw new TypeError('Could not parse amt') + } + + if (typeof memo !== 'string') { + throw new TypeError('Could not parse memo') + } + + const amt = Number(amtStr) + + if (!isNumber(amt)) { + throw new TypeError(`Could parse amount as a number, not a number?`) + } + + if (isNaN(amt)) { + throw new TypeError(`Could not amount as a number, got NaN.`) + } + + if (!isFinite(amt)) { + throw new TypeError( + `Amount was correctly parsed, but got a non finite number.` + ) + } + + return { + amt, + memo, + preimage + } + } catch (err) { + logger.debug(`Encoded spontaneous payment: ${sp}`) + logger.error(err) + throw err + } +} + +/** + * + * @param {number} amt + * @param {string} memo + * @param {string} preimage + * @returns {EncSpontPayment} + */ +exports.encodeSpontaneousPayment = (amt, memo, preimage) => { + if (amt <= 0) { + throw new RangeError('Amt must be greater than zero') + } + + if (memo.length < 1) { + throw new TypeError('Memo must be populated') + } + + if (preimage.length < 1) { + throw new TypeError('preimage must be populated') + } + + const enc = `${ENC_SPONT_PAYMENT_PREFIX}__${amt}__${memo}__${preimage}` + + if (isEncodedSpontPayment(enc)) { + return enc + } + + throw new Error('isEncodedSpontPayment(enc) false') +}