commit
de45807e59
12 changed files with 1001 additions and 341 deletions
|
|
@ -7,4 +7,4 @@ SHOCK_CACHE=true
|
||||||
TRUSTED_KEYS=true
|
TRUSTED_KEYS=true
|
||||||
LOCAL_TUNNEL_SERVER=https://tunnel.rip
|
LOCAL_TUNNEL_SERVER=https://tunnel.rip
|
||||||
TORRENT_SEED_URL=https://webtorrent.shock.network
|
TORRENT_SEED_URL=https://webtorrent.shock.network
|
||||||
TORRENT_SEED_TOKEN=jibberish
|
TORRENT_SEED_TOKEN=jibberish
|
||||||
|
|
@ -105,4 +105,4 @@
|
||||||
"pre-commit": "yarn lint && yarn typecheck && yarn lint-staged"
|
"pre-commit": "yarn lint && yarn typecheck && yarn lint-staged"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
/**
|
|
||||||
* @format
|
|
||||||
*/
|
|
||||||
|
|
||||||
const Common = require('shock-common')
|
|
||||||
const mapValues = require('lodash/mapValues')
|
|
||||||
const pickBy = require('lodash/pickBy')
|
|
||||||
const Bluebird = require('bluebird')
|
|
||||||
const Logger = require('winston')
|
|
||||||
const Key = require('../services/gunDB/contact-api/key')
|
|
||||||
|
|
||||||
const { getUser, getMySecret, mySEA } = require('./gunDB/Mediator')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} coordID
|
|
||||||
* @param {Common.Coordinate} data
|
|
||||||
* @returns {Promise<void>}
|
|
||||||
*/
|
|
||||||
module.exports.writeCoordinate = async (coordID, data) => {
|
|
||||||
if (coordID !== data.id) {
|
|
||||||
throw new Error('CoordID must be equal to data.id')
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
/**
|
|
||||||
* Because there are optional properties, typescript can also allow them
|
|
||||||
* to be specified but with a value of `undefined`. Filter out these.
|
|
||||||
* @type {Record<string, number|boolean|string>}
|
|
||||||
*/
|
|
||||||
const sanitizedData = pickBy(data, v => typeof v !== 'undefined')
|
|
||||||
|
|
||||||
const encData = await Bluebird.props(
|
|
||||||
mapValues(sanitizedData, v => {
|
|
||||||
return mySEA.encrypt(v, getMySecret())
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
getUser()
|
|
||||||
.get(Key.COORDINATES)
|
|
||||||
.get(coordID)
|
|
||||||
.put(encData, ack => {
|
|
||||||
if (ack.err && typeof ack.err !== 'number') {
|
|
||||||
Logger.info(
|
|
||||||
`Error writting corrdinate, coordinate id: ${coordID}, data: ${JSON.stringify(
|
|
||||||
data,
|
|
||||||
null,
|
|
||||||
2
|
|
||||||
)}`
|
|
||||||
)
|
|
||||||
Logger.error(ack.err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} catch (e) {
|
|
||||||
Logger.info(
|
|
||||||
`Error writing coordinate, coordinate id: ${coordID}, data: ${JSON.stringify(
|
|
||||||
data,
|
|
||||||
null,
|
|
||||||
2
|
|
||||||
)}`
|
|
||||||
)
|
|
||||||
Logger.error(e.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -11,8 +11,7 @@ const { ErrorCode } = Constants
|
||||||
|
|
||||||
const {
|
const {
|
||||||
sendPaymentV2Invoice,
|
sendPaymentV2Invoice,
|
||||||
decodePayReq,
|
decodePayReq
|
||||||
myLNDPub
|
|
||||||
} = require('../../../utils/lightningServices/v2')
|
} = require('../../../utils/lightningServices/v2')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -22,7 +21,8 @@ const {
|
||||||
const Getters = require('./getters')
|
const Getters = require('./getters')
|
||||||
const Key = require('./key')
|
const Key = require('./key')
|
||||||
const Utils = require('./utils')
|
const Utils = require('./utils')
|
||||||
const { writeCoordinate } = require('../../coordinates')
|
const SchemaManager = require('../../schema')
|
||||||
|
const LNDHealthMananger = require('../../../utils/lightningServices/errors')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('./SimpleGUN').GUNNode} GUNNode
|
* @typedef {import('./SimpleGUN').GUNNode} GUNNode
|
||||||
|
|
@ -260,7 +260,7 @@ const acceptRequest = async (
|
||||||
newlyCreatedOutgoingFeedID,
|
newlyCreatedOutgoingFeedID,
|
||||||
ourSecret
|
ourSecret
|
||||||
)
|
)
|
||||||
|
//why await if you dont need the response?
|
||||||
await /** @type {Promise<void>} */ (new Promise((res, rej) => {
|
await /** @type {Promise<void>} */ (new Promise((res, rej) => {
|
||||||
gun
|
gun
|
||||||
.get(Key.HANDSHAKE_NODES)
|
.get(Key.HANDSHAKE_NODES)
|
||||||
|
|
@ -358,7 +358,7 @@ const generateHandshakeAddress = async () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
|
//why await if you dont need the response?
|
||||||
await /** @type {Promise<void>} */ (new Promise((res, rej) => {
|
await /** @type {Promise<void>} */ (new Promise((res, rej) => {
|
||||||
gun
|
gun
|
||||||
.get(Key.HANDSHAKE_NODES)
|
.get(Key.HANDSHAKE_NODES)
|
||||||
|
|
@ -643,7 +643,7 @@ const sendHandshakeRequest = async (recipientPublicKey, gun, user, SEA) => {
|
||||||
handshakeAddress: await SEA.encrypt(currentHandshakeAddress, mySecret),
|
handshakeAddress: await SEA.encrypt(currentHandshakeAddress, mySecret),
|
||||||
timestamp
|
timestamp
|
||||||
}
|
}
|
||||||
|
//why await if you dont need the response?
|
||||||
await /** @type {Promise<void>} */ (new Promise((res, rej) => {
|
await /** @type {Promise<void>} */ (new Promise((res, rej) => {
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
user.get(Key.STORED_REQS).set(storedReq, ack => {
|
user.get(Key.STORED_REQS).set(storedReq, ack => {
|
||||||
|
|
@ -923,10 +923,13 @@ const sendHRWithInitialMsg = async (
|
||||||
/**
|
/**
|
||||||
* @typedef {object} SpontPaymentOptions
|
* @typedef {object} SpontPaymentOptions
|
||||||
* @prop {Common.Schema.OrderTargetType} type
|
* @prop {Common.Schema.OrderTargetType} type
|
||||||
* @prop {string=} postID
|
|
||||||
* @prop {string=} ackInfo
|
* @prop {string=} ackInfo
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* @typedef {object} OrderRes
|
||||||
|
* @prop {PaymentV2} payment
|
||||||
|
* @prop {object=} orderAck
|
||||||
|
*/
|
||||||
/**
|
/**
|
||||||
* Returns the preimage corresponding to the payment.
|
* Returns the preimage corresponding to the payment.
|
||||||
* @param {string} to
|
* @param {string} to
|
||||||
|
|
@ -936,7 +939,7 @@ const sendHRWithInitialMsg = async (
|
||||||
* @param {SpontPaymentOptions} opts
|
* @param {SpontPaymentOptions} opts
|
||||||
* @throws {Error} If no response in less than 20 seconds from the recipient, or
|
* @throws {Error} If no response in less than 20 seconds from the recipient, or
|
||||||
* lightning cannot find a route for the payment.
|
* lightning cannot find a route for the payment.
|
||||||
* @returns {Promise<PaymentV2>} The payment's preimage.
|
* @returns {Promise<OrderRes>} The payment's preimage.
|
||||||
*/
|
*/
|
||||||
const sendSpontaneousPayment = async (
|
const sendSpontaneousPayment = async (
|
||||||
to,
|
to,
|
||||||
|
|
@ -965,11 +968,8 @@ const sendSpontaneousPayment = async (
|
||||||
from: getUser()._.sea.pub,
|
from: getUser()._.sea.pub,
|
||||||
memo: memo || 'no memo',
|
memo: memo || 'no memo',
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
targetType: opts.type
|
targetType: opts.type,
|
||||||
}
|
ackInfo: opts.ackInfo
|
||||||
|
|
||||||
if (opts.type === 'tip') {
|
|
||||||
order.ackInfo = opts.postID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(JSON.stringify(order))
|
logger.info(JSON.stringify(order))
|
||||||
|
|
@ -1006,7 +1006,7 @@ const sendSpontaneousPayment = async (
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
res(ord._.get)
|
setTimeout(() => res(ord._.get), 0)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
@ -1017,7 +1017,8 @@ const sendSpontaneousPayment = async (
|
||||||
)}`
|
)}`
|
||||||
throw new Error(msg)
|
throw new Error(msg)
|
||||||
}
|
}
|
||||||
|
console.log('ORDER ID')
|
||||||
|
console.log(orderID)
|
||||||
/** @type {import('shock-common').Schema.OrderResponse} */
|
/** @type {import('shock-common').Schema.OrderResponse} */
|
||||||
const encryptedOrderRes = await Utils.tryAndWait(
|
const encryptedOrderRes = await Utils.tryAndWait(
|
||||||
gun =>
|
gun =>
|
||||||
|
|
@ -1027,12 +1028,13 @@ const sendSpontaneousPayment = async (
|
||||||
.get(Key.ORDER_TO_RESPONSE)
|
.get(Key.ORDER_TO_RESPONSE)
|
||||||
.get(orderID)
|
.get(orderID)
|
||||||
.on(orderResponse => {
|
.on(orderResponse => {
|
||||||
|
console.log(orderResponse)
|
||||||
if (Schema.isOrderResponse(orderResponse)) {
|
if (Schema.isOrderResponse(orderResponse)) {
|
||||||
res(orderResponse)
|
res(orderResponse)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
v => !Schema.isOrderResponse(v)
|
v => Schema.isOrderResponse(v)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!Schema.isOrderResponse(encryptedOrderRes)) {
|
if (!Schema.isOrderResponse(encryptedOrderRes)) {
|
||||||
|
|
@ -1043,10 +1045,12 @@ const sendSpontaneousPayment = async (
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @type {import('shock-common').Schema.OrderResponse} */
|
/** @type {import('shock-common').Schema.OrderResponse &{ackNode:string}} */
|
||||||
const orderResponse = {
|
const orderResponse = {
|
||||||
response: await SEA.decrypt(encryptedOrderRes.response, ourSecret),
|
response: await SEA.decrypt(encryptedOrderRes.response, ourSecret),
|
||||||
type: encryptedOrderRes.type
|
type: encryptedOrderRes.type,
|
||||||
|
//@ts-expect-error
|
||||||
|
ackNode: encryptedOrderRes.ackNode
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info('decoded orderResponse: ' + JSON.stringify(orderResponse))
|
logger.info('decoded orderResponse: ' + JSON.stringify(orderResponse))
|
||||||
|
|
@ -1076,35 +1080,87 @@ const sendSpontaneousPayment = async (
|
||||||
feeLimit,
|
feeLimit,
|
||||||
payment_request: orderResponse.response
|
payment_request: orderResponse.response
|
||||||
})
|
})
|
||||||
|
const myLndPub = LNDHealthMananger.lndPub
|
||||||
|
if (
|
||||||
|
(opts.type !== 'contentReveal' && opts.type !== 'torrentSeed') ||
|
||||||
|
!orderResponse.ackNode
|
||||||
|
) {
|
||||||
|
SchemaManager.AddOrder({
|
||||||
|
type: opts.type,
|
||||||
|
amount: parseInt(payment.value_sat, 10),
|
||||||
|
coordinateHash: payment.payment_hash,
|
||||||
|
coordinateIndex: parseInt(payment.payment_index, 10),
|
||||||
|
fromLndPub: myLndPub || undefined,
|
||||||
|
inbound: false,
|
||||||
|
fromGunPub: getUser()._.sea.pub,
|
||||||
|
toGunPub: to,
|
||||||
|
invoiceMemo: memo
|
||||||
|
})
|
||||||
|
return { payment }
|
||||||
|
}
|
||||||
|
console.log('ACK NODE')
|
||||||
|
console.log(orderResponse.ackNode)
|
||||||
|
/** @type {import('shock-common').Schema.OrderResponse} */
|
||||||
|
const encryptedOrderAckRes = await Utils.tryAndWait(
|
||||||
|
gun =>
|
||||||
|
new Promise(res => {
|
||||||
|
gun
|
||||||
|
.user(to)
|
||||||
|
.get(Key.ORDER_TO_RESPONSE)
|
||||||
|
.get(orderResponse.ackNode)
|
||||||
|
.on(orderResponse => {
|
||||||
|
console.log(orderResponse)
|
||||||
|
console.log(Schema.isOrderResponse(orderResponse))
|
||||||
|
|
||||||
await writeCoordinate(payment.payment_hash, {
|
//@ts-expect-error
|
||||||
id: payment.payment_hash,
|
if (orderResponse && orderResponse.type === 'orderAck') {
|
||||||
type: (() => {
|
//@ts-expect-error
|
||||||
if (opts.type === 'tip') {
|
res(orderResponse)
|
||||||
return 'tip'
|
}
|
||||||
} else if (opts.type === 'spontaneousPayment') {
|
})
|
||||||
return 'spontaneousPayment'
|
}),
|
||||||
} else if (opts.type === 'contentReveal') {
|
//@ts-expect-error
|
||||||
return 'other' // TODO
|
v => !v || !v.type
|
||||||
} else if (opts.type === 'other') {
|
)
|
||||||
return 'other' // TODO
|
|
||||||
} else if (opts.type === 'torrentSeed') {
|
|
||||||
return 'other' // TODO
|
|
||||||
}
|
|
||||||
// ensures we handle all possible types
|
|
||||||
/** @type {never} */
|
|
||||||
const assertNever = opts.type
|
|
||||||
|
|
||||||
return assertNever && opts.type // please TS
|
if (!encryptedOrderAckRes || !encryptedOrderAckRes.type) {
|
||||||
})(),
|
const e = TypeError(
|
||||||
amount: Number(payment.value_sat),
|
`Expected encryptedOrderAckRes got: ${typeof encryptedOrderAckRes}`
|
||||||
|
)
|
||||||
|
logger.error(e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {import('shock-common').Schema.OrderResponse} */
|
||||||
|
const orderAck = {
|
||||||
|
response: await SEA.decrypt(encryptedOrderAckRes.response, ourSecret),
|
||||||
|
type: encryptedOrderAckRes.type
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info('decoded encryptedOrderAck: ' + JSON.stringify(orderAck))
|
||||||
|
|
||||||
|
if (orderAck.type === 'err') {
|
||||||
|
throw new Error(orderAck.response)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (orderAck.type !== 'orderAck') {
|
||||||
|
throw new Error(`expected orderAck response, got: ${orderAck.type}`)
|
||||||
|
}
|
||||||
|
SchemaManager.AddOrder({
|
||||||
|
type: opts.type,
|
||||||
|
amount: parseInt(payment.value_sat, 10),
|
||||||
|
coordinateHash: payment.payment_hash,
|
||||||
|
coordinateIndex: parseInt(payment.payment_index, 10),
|
||||||
|
fromLndPub: myLndPub || undefined,
|
||||||
inbound: false,
|
inbound: false,
|
||||||
timestamp: Date.now(),
|
fromGunPub: getUser()._.sea.pub,
|
||||||
toLndPub: await myLNDPub()
|
toGunPub: to,
|
||||||
|
invoiceMemo: memo,
|
||||||
|
metadata: JSON.stringify(orderAck)
|
||||||
})
|
})
|
||||||
|
return { payment, orderAck }
|
||||||
return payment
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
logger.error('Error inside sendPayment()')
|
logger.error('Error inside sendPayment()')
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
throw e
|
throw e
|
||||||
|
|
@ -1122,8 +1178,8 @@ const sendSpontaneousPayment = async (
|
||||||
* @returns {Promise<string>} The payment's preimage.
|
* @returns {Promise<string>} The payment's preimage.
|
||||||
*/
|
*/
|
||||||
const sendPayment = async (to, amount, memo, feeLimit) => {
|
const sendPayment = async (to, amount, memo, feeLimit) => {
|
||||||
const payment = await sendSpontaneousPayment(to, amount, memo, feeLimit)
|
const res = await sendSpontaneousPayment(to, amount, memo, feeLimit)
|
||||||
return payment.payment_preimage
|
return res.payment.payment_preimage
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1307,12 +1363,6 @@ const createPostNew = async (tags, title, content) => {
|
||||||
contentItems: {}
|
contentItems: {}
|
||||||
}
|
}
|
||||||
|
|
||||||
content.forEach(c => {
|
|
||||||
// @ts-expect-error
|
|
||||||
const uuid = Gun.text.random()
|
|
||||||
newPost.contentItems[uuid] = c
|
|
||||||
})
|
|
||||||
|
|
||||||
const mySecret = require('../Mediator').getMySecret()
|
const mySecret = require('../Mediator').getMySecret()
|
||||||
|
|
||||||
await Common.Utils.asyncForEach(content, async c => {
|
await Common.Utils.asyncForEach(content, async c => {
|
||||||
|
|
@ -1543,7 +1593,7 @@ const follow = async (publicKey, isPrivate) => {
|
||||||
status: 'ok',
|
status: 'ok',
|
||||||
user: publicKey
|
user: publicKey
|
||||||
}
|
}
|
||||||
|
//why await if you dont need the response?
|
||||||
await /** @type {Promise<void>} */ (new Promise((res, rej) => {
|
await /** @type {Promise<void>} */ (new Promise((res, rej) => {
|
||||||
require('../Mediator')
|
require('../Mediator')
|
||||||
.getUser()
|
.getUser()
|
||||||
|
|
|
||||||
|
|
@ -7,20 +7,14 @@ const isFinite = require('lodash/isFinite')
|
||||||
const isNumber = require('lodash/isNumber')
|
const isNumber = require('lodash/isNumber')
|
||||||
const isNaN = require('lodash/isNaN')
|
const isNaN = require('lodash/isNaN')
|
||||||
const Common = require('shock-common')
|
const Common = require('shock-common')
|
||||||
|
const crypto = require('crypto')
|
||||||
|
const fetch = require('node-fetch')
|
||||||
const {
|
const {
|
||||||
Constants: { ErrorCode },
|
Constants: { ErrorCode },
|
||||||
Schema
|
Schema
|
||||||
} = Common
|
} = Common
|
||||||
const { assertNever } = require('assert-never')
|
const SchemaManager = require('../../../schema')
|
||||||
const crypto = require('crypto')
|
|
||||||
const fetch = require('node-fetch')
|
|
||||||
|
|
||||||
const LightningServices = require('../../../../utils/lightningServices')
|
const LightningServices = require('../../../../utils/lightningServices')
|
||||||
const {
|
|
||||||
addInvoice,
|
|
||||||
myLNDPub
|
|
||||||
} = require('../../../../utils/lightningServices/v2')
|
|
||||||
const { writeCoordinate } = require('../../../coordinates')
|
|
||||||
const Key = require('../key')
|
const Key = require('../key')
|
||||||
const Utils = require('../utils')
|
const Utils = require('../utils')
|
||||||
const { gunUUID } = require('../../../../utils')
|
const { gunUUID } = require('../../../../utils')
|
||||||
|
|
@ -63,6 +57,28 @@ const ordersProcessed = new Set()
|
||||||
|
|
||||||
let currentOrderAddr = ''
|
let currentOrderAddr = ''
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {InvoiceRequest} invoiceReq
|
||||||
|
* @returns {Promise<InvoiceResponse>}
|
||||||
|
*/
|
||||||
|
const _addInvoice = invoiceReq =>
|
||||||
|
new Promise((resolve, rej) => {
|
||||||
|
const {
|
||||||
|
services: { lightning }
|
||||||
|
} = LightningServices
|
||||||
|
|
||||||
|
lightning.addInvoice(invoiceReq, (
|
||||||
|
/** @type {any} */ error,
|
||||||
|
/** @type {InvoiceResponse} */ response
|
||||||
|
) => {
|
||||||
|
if (error) {
|
||||||
|
rej(error)
|
||||||
|
} else {
|
||||||
|
resolve(response)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} addr
|
* @param {string} addr
|
||||||
* @param {ISEA} SEA
|
* @param {ISEA} SEA
|
||||||
|
|
@ -89,6 +105,8 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//const listenerStartTime = performance.now()
|
||||||
|
|
||||||
ordersProcessed.add(orderID)
|
ordersProcessed.add(orderID)
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
|
|
@ -146,12 +164,7 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => {
|
||||||
`onOrders() -> Will now create an invoice : ${JSON.stringify(invoiceReq)}`
|
`onOrders() -> Will now create an invoice : ${JSON.stringify(invoiceReq)}`
|
||||||
)
|
)
|
||||||
|
|
||||||
const invoice = await addInvoice(
|
const invoice = await _addInvoice(invoiceReq)
|
||||||
invoiceReq.value,
|
|
||||||
invoiceReq.memo,
|
|
||||||
true,
|
|
||||||
invoiceReq.expiry
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
'onOrders() -> Successfully created the invoice, will now encrypt it'
|
'onOrders() -> Successfully created the invoice, will now encrypt it'
|
||||||
|
|
@ -162,11 +175,13 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => {
|
||||||
logger.info(
|
logger.info(
|
||||||
`onOrders() -> Will now place the encrypted invoice in order to response usergraph: ${addr}`
|
`onOrders() -> Will now place the encrypted invoice in order to response usergraph: ${addr}`
|
||||||
)
|
)
|
||||||
|
const ackNode = gunUUID()
|
||||||
|
|
||||||
/** @type {import('shock-common').Schema.OrderResponse} */
|
/** @type {import('shock-common').Schema.OrderResponse} */
|
||||||
const orderResponse = {
|
const orderResponse = {
|
||||||
response: encInvoice,
|
response: encInvoice,
|
||||||
type: 'invoice'
|
type: 'invoice',
|
||||||
|
ackNode
|
||||||
}
|
}
|
||||||
|
|
||||||
await /** @type {Promise<void>} */ (new Promise((res, rej) => {
|
await /** @type {Promise<void>} */ (new Promise((res, rej) => {
|
||||||
|
|
@ -187,86 +202,74 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => {
|
||||||
})
|
})
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// invoices should be settled right away so we can rely on this single
|
//logger.info(`[PERF] Added invoice to GunDB in ${invoicePutEndTime}ms`)
|
||||||
// subscription instead of life-long all invoices subscription
|
|
||||||
if (order.targetType === 'tip') {
|
|
||||||
const { ackInfo } = order
|
|
||||||
if (!Common.isPopulatedString(ackInfo)) {
|
|
||||||
throw new TypeError(`ackInfo(postID) not a a populated string`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// A post tip order lifecycle is short enough that we can do it like this.
|
|
||||||
const stream = LightningServices.invoices.subscribeSingleInvoice({
|
|
||||||
r_hash: invoice.r_hash
|
|
||||||
})
|
|
||||||
|
|
||||||
/** @type {Common.Coordinate} */
|
|
||||||
const coord = {
|
|
||||||
amount,
|
|
||||||
id: invoice.r_hash.toString(),
|
|
||||||
inbound: true,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
type: 'invoice',
|
|
||||||
invoiceMemo: memo,
|
|
||||||
fromGunPub: order.from,
|
|
||||||
toGunPub: getUser()._.sea.pub,
|
|
||||||
toLndPub: await myLNDPub()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (order.targetType === 'tip') {
|
|
||||||
coord.type = 'tip'
|
|
||||||
} else {
|
|
||||||
coord.type = 'spontaneousPayment'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Common.InvoiceWhenListed} invoice
|
*
|
||||||
|
* @param {Common.Schema.InvoiceWhenListed & {r_hash:Buffer,payment_addr:string}} paidInvoice
|
||||||
*/
|
*/
|
||||||
const onData = async invoice => {
|
const invoicePaidCb = async paidInvoice => {
|
||||||
if (invoice.settled) {
|
console.log('INVOICE PAID')
|
||||||
writeCoordinate(invoice.r_hash.toString(), coord)
|
let breakError = null
|
||||||
|
let orderMetadata //eslint-disable-line init-declarations
|
||||||
if (order.targetType === 'tip') {
|
const hashString = paidInvoice.r_hash.toString('hex')
|
||||||
|
const {
|
||||||
|
amt_paid_sat: amt,
|
||||||
|
add_index: addIndex,
|
||||||
|
payment_addr: paymentAddr
|
||||||
|
} = paidInvoice
|
||||||
|
const orderType = order.targetType
|
||||||
|
const { ackInfo } = order //a string representing what has been requested
|
||||||
|
switch (orderType) {
|
||||||
|
case 'tip': {
|
||||||
|
const postID = ackInfo
|
||||||
|
if (!Common.isPopulatedString(postID)) {
|
||||||
|
breakError = 'invalid ackInfo provided for postID'
|
||||||
|
break //create the coordinate, but stop because of the invalid id
|
||||||
|
}
|
||||||
getUser()
|
getUser()
|
||||||
.get('postToTipCount')
|
.get('postToTipCount')
|
||||||
// CAST: Checked above.
|
.get(postID)
|
||||||
.get(/** @type {string} */ (order.ackInfo))
|
|
||||||
.set(null) // each item in the set is a tip
|
.set(null) // each item in the set is a tip
|
||||||
} else if (order.targetType === 'contentReveal') {
|
break
|
||||||
// -----------------------------------------
|
}
|
||||||
logger.debug('Content Reveal')
|
case 'spontaneousPayment': {
|
||||||
|
//no action required
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'contentReveal': {
|
||||||
|
console.log('cONTENT REVEAL')
|
||||||
//assuming digital product that only requires to be unlocked
|
//assuming digital product that only requires to be unlocked
|
||||||
const postID = order.ackInfo
|
const postID = ackInfo
|
||||||
|
console.log('ACK INFO')
|
||||||
|
console.log(ackInfo)
|
||||||
if (!Common.isPopulatedString(postID)) {
|
if (!Common.isPopulatedString(postID)) {
|
||||||
logger.error(`Invalid post ID`)
|
breakError = 'invalid ackInfo provided for postID'
|
||||||
logger.error(postID)
|
break //create the coordinate, but stop because of the invalid id
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
console.log('IS STRING')
|
||||||
// TODO: do this reactively
|
|
||||||
const selectedPost = await new Promise(res => {
|
const selectedPost = await new Promise(res => {
|
||||||
getUser()
|
getUser()
|
||||||
.get(Key.POSTS_NEW)
|
.get(Key.POSTS_NEW)
|
||||||
.get(postID)
|
.get(postID)
|
||||||
.load(res)
|
.load(res)
|
||||||
})
|
})
|
||||||
|
console.log('LOAD ok')
|
||||||
logger.debug(selectedPost)
|
console.log(selectedPost)
|
||||||
|
if (
|
||||||
if (Common.isPost(selectedPost)) {
|
!selectedPost ||
|
||||||
logger.error('Post id provided does not correspond to a valid post')
|
!selectedPost.status ||
|
||||||
return
|
selectedPost.status !== 'publish'
|
||||||
|
) {
|
||||||
|
breakError = 'ackInfo provided does not correspond to a valid post'
|
||||||
|
break //create the coordinate, but stop because of the invalid post
|
||||||
}
|
}
|
||||||
|
console.log('IS POST')
|
||||||
/**
|
/**
|
||||||
* @type {Record<string,string>} <contentID,decryptedRef>
|
* @type {Record<string,string>} <contentID,decryptedRef>
|
||||||
*/
|
*/
|
||||||
const contentsToSend = {}
|
const contentsToSend = {}
|
||||||
const mySecret = require('../../Mediator').getMySecret()
|
const mySecret = require('../../Mediator').getMySecret()
|
||||||
logger.debug('SECRET OK')
|
console.log('SECRET OK')
|
||||||
let privateFound = false
|
let privateFound = false
|
||||||
await Common.Utils.asyncForEach(
|
await Common.Utils.asyncForEach(
|
||||||
Object.entries(selectedPost.contentItems),
|
Object.entries(selectedPost.contentItems),
|
||||||
|
|
@ -286,8 +289,9 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if (!privateFound) {
|
if (!privateFound) {
|
||||||
logger.error(`Post provided does not contain private content`)
|
breakError =
|
||||||
return
|
'post provided from ackInfo does not contain private content'
|
||||||
|
break //no private content in this post
|
||||||
}
|
}
|
||||||
const ackData = { unlockedContents: contentsToSend }
|
const ackData = { unlockedContents: contentsToSend }
|
||||||
const toSend = JSON.stringify(ackData)
|
const toSend = JSON.stringify(ackData)
|
||||||
|
|
@ -296,15 +300,12 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => {
|
||||||
type: 'orderAck',
|
type: 'orderAck',
|
||||||
response: encrypted
|
response: encrypted
|
||||||
}
|
}
|
||||||
logger.debug('RES READY')
|
console.log('RES READY')
|
||||||
|
|
||||||
const uuid = gunUUID()
|
await new Promise((res, rej) => {
|
||||||
orderResponse.ackNode = uuid
|
|
||||||
|
|
||||||
await /** @type {Promise<void>} */ (new Promise((res, rej) => {
|
|
||||||
getUser()
|
getUser()
|
||||||
.get(Key.ORDER_TO_RESPONSE)
|
.get(Key.ORDER_TO_RESPONSE)
|
||||||
.get(uuid)
|
.get(ackNode)
|
||||||
.put(ordResponse, ack => {
|
.put(ordResponse, ack => {
|
||||||
if (ack.err && typeof ack.err !== 'number') {
|
if (ack.err && typeof ack.err !== 'number') {
|
||||||
rej(
|
rej(
|
||||||
|
|
@ -313,29 +314,28 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
res()
|
res(null)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}))
|
})
|
||||||
logger.debug('RES SENT CONTENT')
|
console.log('RES SENT CONTENT')
|
||||||
|
orderMetadata = JSON.stringify(ordResponse)
|
||||||
// ----------------------------------------------------------------------------------
|
break
|
||||||
} else if (order.targetType === 'spontaneousPayment') {
|
}
|
||||||
// no action required
|
case 'torrentSeed': {
|
||||||
} else if (order.targetType === 'torrentSeed') {
|
console.log('TORRENT')
|
||||||
logger.debug('TORRENT')
|
const numberOfTokens = Number(ackInfo)
|
||||||
const numberOfTokens = Number(order.ackInfo)
|
|
||||||
if (isNaN(numberOfTokens)) {
|
if (isNaN(numberOfTokens)) {
|
||||||
logger.error('ackInfo provided is not a valid number')
|
breakError = 'ackInfo provided is not a valid number'
|
||||||
return
|
break
|
||||||
}
|
}
|
||||||
const seedUrl = process.env.TORRENT_SEED_URL
|
const seedUrl = process.env.TORRENT_SEED_URL
|
||||||
const seedToken = process.env.TORRENT_SEED_TOKEN
|
const seedToken = process.env.TORRENT_SEED_TOKEN
|
||||||
if (!seedUrl || !seedToken) {
|
if (!seedUrl || !seedToken) {
|
||||||
logger.error('torrentSeed service not available')
|
breakError = 'torrentSeed service not available'
|
||||||
return
|
break //service not available
|
||||||
}
|
}
|
||||||
logger.debug('SEED URL OK')
|
console.log('SEED URL OK')
|
||||||
const tokens = Array(numberOfTokens)
|
const tokens = Array(numberOfTokens)
|
||||||
for (let i = 0; i < numberOfTokens; i++) {
|
for (let i = 0; i < numberOfTokens; i++) {
|
||||||
tokens[i] = crypto.randomBytes(32).toString('hex')
|
tokens[i] = crypto.randomBytes(32).toString('hex')
|
||||||
|
|
@ -346,7 +346,7 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => {
|
||||||
seed_token: seedToken,
|
seed_token: seedToken,
|
||||||
wallet_token: token
|
wallet_token: token
|
||||||
}
|
}
|
||||||
// @ts-expect-error TODO
|
//@ts-expect-error
|
||||||
const res = await fetch(`${seedUrl}/api/enroll_token`, {
|
const res = await fetch(`${seedUrl}/api/enroll_token`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
|
|
@ -359,7 +359,7 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await Promise.all(tokens.map(enrollToken))
|
await Promise.all(tokens.map(enrollToken))
|
||||||
logger.debug('RES SEED OK')
|
console.log('RES SEED OK')
|
||||||
const ackData = { seedUrl, tokens }
|
const ackData = { seedUrl, tokens }
|
||||||
const toSend = JSON.stringify(ackData)
|
const toSend = JSON.stringify(ackData)
|
||||||
const encrypted = await SEA.encrypt(toSend, secret)
|
const encrypted = await SEA.encrypt(toSend, secret)
|
||||||
|
|
@ -368,14 +368,10 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => {
|
||||||
response: encrypted
|
response: encrypted
|
||||||
}
|
}
|
||||||
console.log('RES SEED SENT')
|
console.log('RES SEED SENT')
|
||||||
|
await new Promise((res, rej) => {
|
||||||
const uuid = gunUUID()
|
|
||||||
orderResponse.ackNode = uuid
|
|
||||||
|
|
||||||
await /** @type {Promise<void>} */ (new Promise((res, rej) => {
|
|
||||||
getUser()
|
getUser()
|
||||||
.get(Key.ORDER_TO_RESPONSE)
|
.get(Key.ORDER_TO_RESPONSE)
|
||||||
.get(uuid)
|
.get(ackNode)
|
||||||
.put(serviceResponse, ack => {
|
.put(serviceResponse, ack => {
|
||||||
if (ack.err && typeof ack.err !== 'number') {
|
if (ack.err && typeof ack.err !== 'number') {
|
||||||
rej(
|
rej(
|
||||||
|
|
@ -384,32 +380,68 @@ const listenerForAddr = (addr, SEA) => async (order, orderID) => {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
res()
|
res(null)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}))
|
})
|
||||||
logger.debug('RES SENT SEED')
|
console.log('RES SENT SEED')
|
||||||
} else if (order.targetType === 'other') {
|
orderMetadata = JSON.stringify(serviceResponse)
|
||||||
// TODO
|
break
|
||||||
} else {
|
|
||||||
assertNever(order.targetType)
|
|
||||||
}
|
}
|
||||||
|
case 'other': //not implemented yet but save them as a coordinate anyways
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
return //exit because not implemented
|
||||||
|
}
|
||||||
|
const metadata = breakError ? JSON.stringify(breakError) : orderMetadata
|
||||||
|
const myGunPub = getUser()._.sea.pub
|
||||||
|
SchemaManager.AddOrder({
|
||||||
|
type: orderType,
|
||||||
|
coordinateHash: hashString,
|
||||||
|
coordinateIndex: parseInt(addIndex, 10),
|
||||||
|
inbound: true,
|
||||||
|
amount: parseInt(amt, 10),
|
||||||
|
|
||||||
stream.off()
|
toLndPub: paymentAddr,
|
||||||
|
fromGunPub: order.from,
|
||||||
|
toGunPub: myGunPub,
|
||||||
|
invoiceMemo: memo,
|
||||||
|
|
||||||
|
metadata
|
||||||
|
})
|
||||||
|
if (breakError) {
|
||||||
|
throw new Error(breakError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
console.log('WAITING INVOICE TO BE PAID')
|
||||||
|
new Promise(res => SchemaManager.addListenInvoice(invoice.r_hash, res))
|
||||||
|
.then(invoicePaidCb)
|
||||||
|
.catch(err => {
|
||||||
|
logger.error(
|
||||||
|
`error inside onOrders, orderAddr: ${addr}, orderID: ${orderID}, order: ${JSON.stringify(
|
||||||
|
order
|
||||||
|
)}`
|
||||||
|
)
|
||||||
|
logger.error(err)
|
||||||
|
|
||||||
stream.on('data', onData)
|
/** @type {import('shock-common').Schema.OrderResponse} */
|
||||||
|
const orderResponse = {
|
||||||
|
response: err.message,
|
||||||
|
type: 'err'
|
||||||
|
}
|
||||||
|
|
||||||
stream.on('status', (/** @type {any} */ status) => {
|
getUser()
|
||||||
logger.info(`Post tip, post: ${order.ackInfo}, invoice status:`, status)
|
.get(Key.ORDER_TO_RESPONSE)
|
||||||
})
|
.get(orderID)
|
||||||
stream.on('end', () => {
|
// @ts-expect-error
|
||||||
logger.warn(`Post tip, post: ${order.ackInfo}, invoice stream ended`)
|
.put(orderResponse, ack => {
|
||||||
})
|
if (ack.err && typeof ack.err !== 'number') {
|
||||||
stream.on('error', (/** @type {any} */ e) => {
|
logger.error(
|
||||||
logger.warn(`Post tip, post: ${order.ackInfo}, error:`, e)
|
`Error saving encrypted invoice to order to response usergraph: ${ack}`
|
||||||
})
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(
|
logger.error(
|
||||||
`error inside onOrders, orderAddr: ${addr}, orderID: ${orderID}, order: ${JSON.stringify(
|
`error inside onOrders, orderAddr: ${addr}, orderID: ${orderID}, order: ${JSON.stringify(
|
||||||
|
|
|
||||||
|
|
@ -63,4 +63,11 @@ exports.PROFILE_BINARY = 'profileBinary'
|
||||||
|
|
||||||
exports.POSTS_NEW = 'posts'
|
exports.POSTS_NEW = 'posts'
|
||||||
|
|
||||||
|
// For Coordinates
|
||||||
exports.COORDINATES = 'coordinates'
|
exports.COORDINATES = 'coordinates'
|
||||||
|
|
||||||
|
exports.COORDINATE_INDEX = 'coordinateIndex'
|
||||||
|
|
||||||
|
exports.TMP_CHAIN_COORDINATE = 'tmpChainCoordinate'
|
||||||
|
|
||||||
|
exports.DATE_COORDINATE_INDEX = 'dateCoordinateIndex'
|
||||||
|
|
|
||||||
601
services/schema/index.js
Normal file
601
services/schema/index.js
Normal file
|
|
@ -0,0 +1,601 @@
|
||||||
|
const Crypto = require('crypto')
|
||||||
|
const logger = require('winston')
|
||||||
|
const Common = require('shock-common')
|
||||||
|
const getGunUser = () => require('../gunDB/Mediator').getUser()
|
||||||
|
const isAuthenticated = () => require('../gunDB/Mediator').isAuthenticated()
|
||||||
|
const Key = require('../gunDB/contact-api/key')
|
||||||
|
const lndV2 = require('../../utils/lightningServices/v2')
|
||||||
|
/**
|
||||||
|
* @typedef {import('../gunDB/contact-api/SimpleGUN').ISEA} ISEA
|
||||||
|
* @typedef { 'spontaneousPayment' | 'tip' | 'torrentSeed' | 'contentReveal' | 'other'|'invoice'|'payment'|'chainTx' } OrderType
|
||||||
|
*
|
||||||
|
* This represents a settled order only, unsettled orders have no coordinate
|
||||||
|
* @typedef {object} CoordinateOrder //everything is optional for different types
|
||||||
|
* @prop {string=} fromLndPub can be unknown when inbound
|
||||||
|
* @prop {string=} toLndPub always known
|
||||||
|
* @prop {string=} fromGunPub can be optional, if the payment/invoice is not related to an order
|
||||||
|
* @prop {string=} toGunPub can be optional, if the payment/invoice is not related to an order
|
||||||
|
* @prop {string=} fromBtcPub
|
||||||
|
* @prop {string=} toBtcPub
|
||||||
|
* @prop {boolean} inbound
|
||||||
|
* NOTE: type specific checks are not made before creating the order node, filters must be done before rendering or processing
|
||||||
|
*
|
||||||
|
* @prop {string=} ownerGunPub Reserved for buddy system:
|
||||||
|
* can be undefined, '', 'me', or node owner pub key to represent node owner,
|
||||||
|
* otherwise it represents a buddy
|
||||||
|
*
|
||||||
|
* @prop {number} coordinateIndex can be payment_index, or add_index depending on if it's a payment or an invoice
|
||||||
|
* @prop {string} coordinateHash can be payment_hash, or r_hash depending on if it's a payment or an invoice,
|
||||||
|
* if it's a r_hash, must be hex encoded
|
||||||
|
*
|
||||||
|
* @prop {OrderType} type
|
||||||
|
* @prop {number} amount
|
||||||
|
* @prop {string=} description
|
||||||
|
* @prop {string=} invoiceMemo
|
||||||
|
* @prop {string=} metadata JSON encoded string to store extra data for special use cases
|
||||||
|
* @prop {number=} timestamp timestamp will be added at processing time if empty
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {CoordinateOrder} order
|
||||||
|
*/
|
||||||
|
const checkOrderInfo = order => {
|
||||||
|
const {
|
||||||
|
fromLndPub,
|
||||||
|
toLndPub,
|
||||||
|
fromGunPub,
|
||||||
|
toGunPub,
|
||||||
|
fromBtcPub,
|
||||||
|
toBtcPub,
|
||||||
|
inbound,
|
||||||
|
type,
|
||||||
|
amount,
|
||||||
|
description,
|
||||||
|
coordinateIndex,
|
||||||
|
coordinateHash,
|
||||||
|
metadata,
|
||||||
|
invoiceMemo
|
||||||
|
} = order
|
||||||
|
|
||||||
|
if (fromLndPub && (typeof fromLndPub !== 'string' || fromLndPub === '')) {
|
||||||
|
return 'invalid "fromLndPub" field provided to order coordinate'
|
||||||
|
}
|
||||||
|
if (toLndPub && (typeof toLndPub !== 'string' || toLndPub === '')) {
|
||||||
|
return 'invalid or no "toLndPub" field provided to order coordinate'
|
||||||
|
}
|
||||||
|
if (fromGunPub && (typeof fromGunPub !== 'string' || fromGunPub === '')) {
|
||||||
|
return 'invalid "fromGunPub" field provided to order coordinate'
|
||||||
|
}
|
||||||
|
if (toGunPub && (typeof toGunPub !== 'string' || toGunPub === '')) {
|
||||||
|
return 'invalid "toGunPub" field provided to order coordinate'
|
||||||
|
}
|
||||||
|
if (fromBtcPub && (typeof fromBtcPub !== 'string' || fromBtcPub === '')) {
|
||||||
|
return 'invalid "fromBtcPub" field provided to order coordinate'
|
||||||
|
}
|
||||||
|
if (toBtcPub && (typeof toBtcPub !== 'string' || toBtcPub === '')) {
|
||||||
|
return 'invalid "toBtcPub" field provided to order coordinate'
|
||||||
|
}
|
||||||
|
if (typeof inbound !== 'boolean') {
|
||||||
|
return 'invalid or no "inbound" field provided to order coordinate'
|
||||||
|
}
|
||||||
|
//@ts-expect-error
|
||||||
|
if (typeof type !== 'string' || type === '') {
|
||||||
|
return 'invalid or no "type" field provided to order coordinate'
|
||||||
|
}
|
||||||
|
if (typeof amount !== 'number') {
|
||||||
|
return 'invalid or no "amount" field provided to order coordinate'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof coordinateIndex !== 'number') {
|
||||||
|
return 'invalid or no "coordinateIndex" field provided to order coordinate'
|
||||||
|
}
|
||||||
|
if (typeof coordinateHash !== 'string' || coordinateHash === '') {
|
||||||
|
return 'invalid or no "coordinateHash" field provided to order coordinate'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (description && (typeof description !== 'string' || description === '')) {
|
||||||
|
return 'invalid "description" field provided to order coordinate'
|
||||||
|
}
|
||||||
|
if (invoiceMemo && (typeof invoiceMemo !== 'string' || invoiceMemo === '')) {
|
||||||
|
return 'invalid "invoiceMemo" field provided to order coordinate'
|
||||||
|
}
|
||||||
|
if (metadata && (typeof metadata !== 'string' || metadata === '')) {
|
||||||
|
return 'invalid "metadata" field provided to order coordinate'
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* @param {CoordinateOrder} orderInfo
|
||||||
|
* @param {string} coordinateSHA256
|
||||||
|
*//*
|
||||||
|
const dateIndexCreateCb = (orderInfo, coordinateSHA256) => {
|
||||||
|
//if (this.memIndex) { need bind to use this here
|
||||||
|
//update date memIndex
|
||||||
|
//}
|
||||||
|
const date = new Date(orderInfo.timestamp || 0)
|
||||||
|
//use UTC for consistency?
|
||||||
|
const year = date.getUTCFullYear().toString()
|
||||||
|
const month = date.getUTCMonth().toString()
|
||||||
|
|
||||||
|
getGunUser()
|
||||||
|
.get(Key.DATE_COORDINATE_INDEX)
|
||||||
|
.get(year)
|
||||||
|
.get(month)
|
||||||
|
.set(coordinateSHA256)
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* if not provided, assume current month and year
|
||||||
|
* @param {number|null} year
|
||||||
|
* @param {number|null} month
|
||||||
|
*//*
|
||||||
|
const getMonthCoordinates = async (year = null, month = null) => {
|
||||||
|
const now = Date.now()
|
||||||
|
const stringYear = year !== null ? year.toString() : now.getUTCFullYear().toString()
|
||||||
|
const stringMonth = month !== null ? month.toString() : now.getUTCMonth().toString()
|
||||||
|
|
||||||
|
const data = await new Promise(res => {
|
||||||
|
getGunUser()
|
||||||
|
.get(Key.DATE_COORDINATE_INDEX)
|
||||||
|
.get(stringYear)
|
||||||
|
.get(stringMonth)
|
||||||
|
.load(res)
|
||||||
|
})
|
||||||
|
const coordinatesArray = Object
|
||||||
|
.values(data)
|
||||||
|
.filter(coordinateSHA256 => typeof coordinateSHA256 === 'string')
|
||||||
|
|
||||||
|
return coordinatesArray
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string|undefined} address
|
||||||
|
* @param {CoordinateOrder} orderInfo
|
||||||
|
*/
|
||||||
|
const AddTmpChainOrder = async (address, orderInfo) => {
|
||||||
|
if (!address) {
|
||||||
|
throw new Error("invalid address passed to AddTmpChainOrder")
|
||||||
|
}
|
||||||
|
if (!orderInfo.toBtcPub) {
|
||||||
|
throw new Error("invalid toBtcPub passed to AddTmpChainOrder")
|
||||||
|
}
|
||||||
|
const checkErr = checkOrderInfo(orderInfo)
|
||||||
|
if (checkErr) {
|
||||||
|
throw new Error(checkErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {CoordinateOrder}
|
||||||
|
*/
|
||||||
|
const filteredOrder = {
|
||||||
|
fromLndPub: orderInfo.fromLndPub,
|
||||||
|
toLndPub: orderInfo.toLndPub,
|
||||||
|
fromGunPub: orderInfo.fromGunPub,
|
||||||
|
toGunPub: orderInfo.toGunPub,
|
||||||
|
inbound: orderInfo.inbound,
|
||||||
|
ownerGunPub: orderInfo.ownerGunPub,
|
||||||
|
coordinateIndex: orderInfo.coordinateIndex,
|
||||||
|
coordinateHash: orderInfo.coordinateHash,
|
||||||
|
type: orderInfo.type,
|
||||||
|
amount: orderInfo.amount,
|
||||||
|
description: orderInfo.description,
|
||||||
|
metadata: orderInfo.metadata,
|
||||||
|
|
||||||
|
timestamp: orderInfo.timestamp || Date.now(),
|
||||||
|
}
|
||||||
|
const orderString = JSON.stringify(filteredOrder)
|
||||||
|
const mySecret = require('../gunDB/Mediator').getMySecret()
|
||||||
|
const SEA = require('../gunDB/Mediator').mySEA
|
||||||
|
const encryptedOrderString = await SEA.encrypt(orderString, mySecret)
|
||||||
|
|
||||||
|
const addressSHA256 = Crypto.createHash('SHA256')
|
||||||
|
.update(address)
|
||||||
|
.digest('hex')
|
||||||
|
|
||||||
|
await new Promise((res, rej) => {
|
||||||
|
getGunUser()
|
||||||
|
.get(Key.TMP_CHAIN_COORDINATE)
|
||||||
|
.get(addressSHA256)
|
||||||
|
.put(encryptedOrderString, ack => {
|
||||||
|
if (ack.err && typeof ack.err !== 'number') {
|
||||||
|
rej(
|
||||||
|
new Error(
|
||||||
|
`Error saving tmp chain coordinate order to user-graph: ${ack}`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
res(null)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} address
|
||||||
|
* @returns {Promise<false|CoordinateOrder>}
|
||||||
|
*/
|
||||||
|
const isTmpChainOrder = async (address) => {
|
||||||
|
if (typeof address !== 'string' || address === '') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const addressSHA256 = Crypto.createHash('SHA256')
|
||||||
|
.update(address)
|
||||||
|
.digest('hex')
|
||||||
|
|
||||||
|
const maybeData = await getGunUser()
|
||||||
|
.get(Key.TMP_CHAIN_COORDINATE)
|
||||||
|
.get(addressSHA256)
|
||||||
|
.then()
|
||||||
|
|
||||||
|
if (typeof maybeData !== 'string' || maybeData === '') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const mySecret = require('../gunDB/Mediator').getMySecret()
|
||||||
|
const SEA = require('../gunDB/Mediator').mySEA
|
||||||
|
const decryptedString = await SEA.decrypt(maybeData, mySecret)
|
||||||
|
if (typeof decryptedString !== 'string' || decryptedString === '') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const tmpOrder = JSON.parse(decryptedString)
|
||||||
|
const checkErr = checkOrderInfo(tmpOrder)
|
||||||
|
if (checkErr) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return tmpOrder
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} address
|
||||||
|
*/
|
||||||
|
const clearTmpChainOrder = async (address) => {
|
||||||
|
if (typeof address !== 'string' || address === '') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const addressSHA256 = Crypto.createHash('SHA256')
|
||||||
|
.update(address)
|
||||||
|
.digest('hex')
|
||||||
|
|
||||||
|
await new Promise((res, rej) => {
|
||||||
|
getGunUser()
|
||||||
|
.get(Key.TMP_CHAIN_COORDINATE)
|
||||||
|
.get(addressSHA256)
|
||||||
|
.put(null, ack => {
|
||||||
|
if (ack.err && typeof ack.err !== 'number') {
|
||||||
|
rej(
|
||||||
|
new Error(
|
||||||
|
`Error nulling tmp chain coordinate order to user-graph: ${ack}`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
res(null)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Common.Schema.ChainTransaction} tx
|
||||||
|
* @param {CoordinateOrder|false| undefined} order
|
||||||
|
*/
|
||||||
|
const handleUnconfirmedTx = (tx, order) => {
|
||||||
|
const { tx_hash } = tx
|
||||||
|
const amountInt = parseInt(tx.amount, 10)
|
||||||
|
if (order) {
|
||||||
|
/*if an order already exists, update the order data
|
||||||
|
if an unconfirmed transaction has a tmp order already
|
||||||
|
it means the address was generated by shockAPI, or the tx was sent by shockAPI*/
|
||||||
|
const orderUpdate = order
|
||||||
|
orderUpdate.amount = Math.abs(amountInt)
|
||||||
|
orderUpdate.inbound = amountInt > 0
|
||||||
|
/*tmp coordinate does not have a coordinate hash until the transaction is created,
|
||||||
|
before it will contain 'unknown' */
|
||||||
|
orderUpdate.coordinateHash = tx_hash
|
||||||
|
/*update the order data,
|
||||||
|
provides a notification when the TX enters the mempool */
|
||||||
|
AddTmpChainOrder(orderUpdate.toBtcPub, orderUpdate)
|
||||||
|
} else {
|
||||||
|
/*if an order does not exist, create the tmp order,
|
||||||
|
and use tx_hash as key.
|
||||||
|
this means the address was NOT generated by shockAPI, or the tx was NOT sent by shockAPI */
|
||||||
|
AddTmpChainOrder(tx_hash, {
|
||||||
|
type: 'chainTx',
|
||||||
|
amount: Math.abs(amountInt),
|
||||||
|
coordinateHash: tx_hash,
|
||||||
|
coordinateIndex: 0, //coordinate index is 0 until the tx is confirmed and the block is known
|
||||||
|
inbound: amountInt > 0,
|
||||||
|
toBtcPub: 'unknown'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class SchemaManager {
|
||||||
|
//constructor() {
|
||||||
|
// this.orderCreateIndexCallbacks.push(dateIndexCreateCb) //create more Cbs and put them here for more indexes callbacks
|
||||||
|
//}
|
||||||
|
|
||||||
|
//dateIndexName = 'dateIndex'
|
||||||
|
/**
|
||||||
|
* @type {((order : CoordinateOrder,coordinateSHA256 : string)=>void)[]}
|
||||||
|
*/
|
||||||
|
orderCreateIndexCallbacks = []
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {CoordinateOrder} orderInfo
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line class-methods-use-this
|
||||||
|
async AddOrder(orderInfo) {
|
||||||
|
const checkErr = checkOrderInfo(orderInfo)
|
||||||
|
if (checkErr) {
|
||||||
|
throw new Error(checkErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {CoordinateOrder}
|
||||||
|
*/
|
||||||
|
const filteredOrder = {
|
||||||
|
fromLndPub: orderInfo.fromLndPub,
|
||||||
|
toLndPub: orderInfo.toLndPub,
|
||||||
|
fromGunPub: orderInfo.fromGunPub,
|
||||||
|
toGunPub: orderInfo.toGunPub,
|
||||||
|
inbound: orderInfo.inbound,
|
||||||
|
ownerGunPub: orderInfo.ownerGunPub,
|
||||||
|
coordinateIndex: orderInfo.coordinateIndex,
|
||||||
|
coordinateHash: orderInfo.coordinateHash,
|
||||||
|
type: orderInfo.type,
|
||||||
|
amount: orderInfo.amount,
|
||||||
|
description: orderInfo.description,
|
||||||
|
metadata: orderInfo.metadata,
|
||||||
|
timestamp: orderInfo.timestamp || Date.now(),
|
||||||
|
}
|
||||||
|
const orderString = JSON.stringify(filteredOrder)
|
||||||
|
const mySecret = require('../gunDB/Mediator').getMySecret()
|
||||||
|
const SEA = require('../gunDB/Mediator').mySEA
|
||||||
|
const encryptedOrderString = await SEA.encrypt(orderString, mySecret)
|
||||||
|
const coordinatePub = filteredOrder.inbound ? filteredOrder.toLndPub : filteredOrder.fromLndPub
|
||||||
|
const coordinate = `${coordinatePub}__${filteredOrder.coordinateIndex}__${filteredOrder.coordinateHash}`
|
||||||
|
const coordinateSHA256 = Crypto.createHash('SHA256')
|
||||||
|
.update(coordinate)
|
||||||
|
.digest('hex')
|
||||||
|
await new Promise((res, rej) => {
|
||||||
|
getGunUser()
|
||||||
|
.get(Key.COORDINATES)
|
||||||
|
.get(coordinateSHA256)
|
||||||
|
.put(encryptedOrderString, ack => {
|
||||||
|
if (ack.err && typeof ack.err !== 'number') {
|
||||||
|
console.log(ack)
|
||||||
|
rej(
|
||||||
|
new Error(
|
||||||
|
`Error saving coordinate order to user-graph: ${ack}`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
res(null)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
//update all indexes with
|
||||||
|
//this.orderCreateIndexCallbacks.forEach(cb => cb(filteredOrder, coordinateSHA256))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* if not provided, assume current month and year
|
||||||
|
* @param {number|null} year
|
||||||
|
* @param {number|null} month
|
||||||
|
* @returns {Promise<CoordinateOrder[]>} from newer to older
|
||||||
|
*//*
|
||||||
|
async getMonthOrders(year = null, month = null) {
|
||||||
|
const now = new Date()
|
||||||
|
const intYear = year !== null ? year : now.getUTCFullYear()
|
||||||
|
const intMonth = month !== null ? month : now.getUTCMonth()
|
||||||
|
|
||||||
|
let coordinates = null
|
||||||
|
if (this.memIndex) {
|
||||||
|
//get coordinates from this.memDateIndex
|
||||||
|
} else {
|
||||||
|
coordinates = await getMonthCoordinates(intYear, intMonth)
|
||||||
|
}
|
||||||
|
const orders = []
|
||||||
|
if (!coordinates) {
|
||||||
|
return orders
|
||||||
|
}
|
||||||
|
await Common.Utils.asyncForEach(coordinates, async coordinateSHA256 => {
|
||||||
|
const encryptedOrderString = await getGunUser()
|
||||||
|
.get(Key.COORDINATES)
|
||||||
|
.get(coordinateSHA256)
|
||||||
|
.then()
|
||||||
|
if (typeof encryptedOrderString !== 'string') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const mySecret = require('../gunDB/Mediator').getMySecret()
|
||||||
|
const SEA = require('../gunDB/Mediator').mySEA
|
||||||
|
const decryptedString = await SEA.decrypt(encryptedOrderString, mySecret)
|
||||||
|
const orderJSON = JSON.parse(decryptedString)
|
||||||
|
orders.push(orderJSON)
|
||||||
|
})
|
||||||
|
const orderedOrders = orders.sort((a, b) => b.timestamp - a.timestamp)
|
||||||
|
return orderedOrders
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Common.Schema.InvoiceWhenListed & {r_hash:Buffer,payment_addr:string}} Invoice
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @type {Record<string,(invoice:Invoice) =>void>}
|
||||||
|
*/
|
||||||
|
_listeningInvoices = {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Buffer} r_hash
|
||||||
|
* @param {(invoice:Invoice) =>void} done
|
||||||
|
*/
|
||||||
|
addListenInvoice(r_hash, done) {
|
||||||
|
const hashString = r_hash.toString("hex")
|
||||||
|
this._listeningInvoices[hashString] = done
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Common.Schema.InvoiceWhenListed & {r_hash:Buffer,payment_addr:string}} data
|
||||||
|
*/
|
||||||
|
invoiceStreamDataCb(data) {
|
||||||
|
if (!data.settled) {
|
||||||
|
//invoice not paid yet
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const hashString = data.r_hash.toString('hex')
|
||||||
|
const amt = parseInt(data.amt_paid_sat, 10)
|
||||||
|
if (this._listeningInvoices[hashString]) {
|
||||||
|
const done = this._listeningInvoices[hashString]
|
||||||
|
delete this._listeningInvoices[hashString]
|
||||||
|
done(data)
|
||||||
|
} else {
|
||||||
|
this.AddOrder({
|
||||||
|
type: 'invoice',
|
||||||
|
coordinateHash: hashString,
|
||||||
|
coordinateIndex: parseInt(data.add_index, 10),
|
||||||
|
inbound: true,
|
||||||
|
amount: amt,
|
||||||
|
toLndPub: data.payment_addr,
|
||||||
|
invoiceMemo: data.memo
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Record<string,boolean>}
|
||||||
|
* lnd fires a confirmed transaction event TWICE, let's make sure it is only managed ONCE
|
||||||
|
*/
|
||||||
|
_confirmedTransactions = {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Common.Schema.ChainTransaction} data
|
||||||
|
*/
|
||||||
|
async transactionStreamDataCb(data) {
|
||||||
|
const { num_confirmations } = data
|
||||||
|
const responses = await Promise.all(data.dest_addresses.map(isTmpChainOrder))
|
||||||
|
const hasOrder = responses.find(res => res !== false)
|
||||||
|
|
||||||
|
if (num_confirmations === 0) {
|
||||||
|
handleUnconfirmedTx(data, hasOrder)
|
||||||
|
} else {
|
||||||
|
this.handleConfirmedTx(data, hasOrder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Common.Schema.ChainTransaction} tx
|
||||||
|
* @param {CoordinateOrder|false| undefined} order
|
||||||
|
*/
|
||||||
|
handleConfirmedTx(tx, order) {
|
||||||
|
const { tx_hash } = tx
|
||||||
|
if (this._confirmedTransactions[tx_hash]) {
|
||||||
|
//this tx confirmation was already handled
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!order) {
|
||||||
|
/*confirmed transaction MUST have a tmp order,
|
||||||
|
if not, means something gone wrong */
|
||||||
|
logger.error('found a confirmed transaction that does not have a tmp order!!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!order.toBtcPub) {
|
||||||
|
/*confirmed transaction tmp order MUST have a non null toBtcPub */
|
||||||
|
logger.error('found a confirmed transaction that does not have toBtcPub in the order!!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const finalOrder = order
|
||||||
|
finalOrder.coordinateIndex = tx.block_height
|
||||||
|
this.AddOrder(finalOrder)
|
||||||
|
if (order.toBtcPub === 'unknown') {
|
||||||
|
clearTmpChainOrder(tx_hash)
|
||||||
|
} else {
|
||||||
|
clearTmpChainOrder(order.toBtcPub)
|
||||||
|
}
|
||||||
|
this._confirmedTransactions[tx_hash] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Manager = new SchemaManager()
|
||||||
|
/*invoice stream,
|
||||||
|
this is the only place where it's needed,
|
||||||
|
everything is a coordinate now*/
|
||||||
|
let InvoiceShouldRetry = true
|
||||||
|
setInterval(() => {
|
||||||
|
if (!InvoiceShouldRetry) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!isAuthenticated()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
InvoiceShouldRetry = false
|
||||||
|
|
||||||
|
lndV2.subscribeInvoices(
|
||||||
|
invoice => {
|
||||||
|
if (!isAuthenticated) {
|
||||||
|
logger.error("got an invoice while not authenticated, will ignore it and cancel the stream")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
Manager.invoiceStreamDataCb(invoice)
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
logger.error(`Error in invoices sub, will retry in two second, reason: ${error.reason}`)
|
||||||
|
InvoiceShouldRetry = true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
}, 2000)
|
||||||
|
|
||||||
|
/*transactions stream,
|
||||||
|
this is the only place where it's needed,
|
||||||
|
everything is a coordinate now*/
|
||||||
|
let TransactionShouldRetry = true
|
||||||
|
setInterval(() => {
|
||||||
|
if (!TransactionShouldRetry) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!isAuthenticated()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
TransactionShouldRetry = false
|
||||||
|
|
||||||
|
lndV2.subscribeTransactions(
|
||||||
|
transaction => {
|
||||||
|
if (!isAuthenticated) {
|
||||||
|
logger.error("got a transaction while not authenticated, will ignore it and cancel the stream")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
Manager.transactionStreamDataCb(transaction)
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
logger.error(`Error in transaction sub, will retry in two second, reason: ${error.reason}`)
|
||||||
|
TransactionShouldRetry = true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
}, 2000)
|
||||||
|
|
||||||
|
module.exports = Manager
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
const setAccessControlHeaders = (req, res) => {
|
const setAccessControlHeaders = (req, res) => {
|
||||||
res.header("Access-Control-Allow-Origin", "*");
|
res.header("Access-Control-Allow-Origin", "*");
|
||||||
|
res.header("Access-Control-Allow-Methods", "OPTIONS,POST,GET,PUT,DELETE")
|
||||||
res.header(
|
res.header(
|
||||||
"Access-Control-Allow-Headers",
|
"Access-Control-Allow-Headers",
|
||||||
"Origin, X-Requested-With, Content-Type, Accept, Authorization"
|
"Origin, X-Requested-With, Content-Type, Accept, Authorization, public-key-for-decryption"
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1124,7 +1124,7 @@ module.exports = async (
|
||||||
app.get('/api/lnd/listchannels', async (_, res) => {
|
app.get('/api/lnd/listchannels', async (_, res) => {
|
||||||
try {
|
try {
|
||||||
return res.json({
|
return res.json({
|
||||||
channels: await LV2.listChannels()
|
channels: await LV2.listChannels({ active_only: false })
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
|
|
@ -1192,11 +1192,8 @@ module.exports = async (
|
||||||
|
|
||||||
app.post('/api/lnd/unifiedTrx', async (req, res) => {
|
app.post('/api/lnd/unifiedTrx', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { type, amt, to, memo, feeLimit, postID, ackInfo } = req.body
|
const { type, amt, to, memo, feeLimit, ackInfo } = req.body
|
||||||
|
|
||||||
if (
|
if (
|
||||||
type !== 'spont' &&
|
|
||||||
type !== 'post' &&
|
|
||||||
type !== 'spontaneousPayment' &&
|
type !== 'spontaneousPayment' &&
|
||||||
type !== 'tip' &&
|
type !== 'tip' &&
|
||||||
type !== 'torrentSeed' &&
|
type !== 'torrentSeed' &&
|
||||||
|
|
@ -1209,21 +1206,6 @@ module.exports = async (
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const typesThatShouldContainAckInfo = [
|
|
||||||
'tip',
|
|
||||||
'torrentSeed',
|
|
||||||
'contentReveal'
|
|
||||||
]
|
|
||||||
|
|
||||||
const shouldContainAckInfo = typesThatShouldContainAckInfo.includes(type)
|
|
||||||
|
|
||||||
if (shouldContainAckInfo && !Common.isPopulatedString(ackInfo)) {
|
|
||||||
return res.status(400).json({
|
|
||||||
field: 'ackInfo',
|
|
||||||
errorMessage: `Transactions of type ${typesThatShouldContainAckInfo} should contain an ackInfo field.`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const amount = Number(amt)
|
const amount = Number(amt)
|
||||||
|
|
||||||
if (!isARealUsableNumber(amount)) {
|
if (!isARealUsableNumber(amount)) {
|
||||||
|
|
@ -1254,17 +1236,16 @@ module.exports = async (
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'post' && typeof postID !== 'string') {
|
if (type === 'tip' && typeof ackInfo !== 'string') {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
field: 'postID',
|
field: 'ackInfo',
|
||||||
errorMessage: `Send postID`
|
errorMessage: `Send ackInfo`
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.status(200).json(
|
return res.status(200).json(
|
||||||
await GunActions.sendSpontaneousPayment(to, amt, memo, feeLimit, {
|
await GunActions.sendSpontaneousPayment(to, amt, memo, feeLimit, {
|
||||||
type,
|
type,
|
||||||
postID,
|
|
||||||
ackInfo
|
ackInfo
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
@ -2188,10 +2169,12 @@ module.exports = async (
|
||||||
app.post(`/api/gun/wall/`, async (req, res) => {
|
app.post(`/api/gun/wall/`, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { tags, title, contentItems } = req.body
|
const { tags, title, contentItems } = req.body
|
||||||
|
const SEA = require('../services/gunDB/Mediator').mySEA
|
||||||
return res
|
return res
|
||||||
.status(200)
|
.status(200)
|
||||||
.json(await GunActions.createPostNew(tags, title, contentItems))
|
.json(await GunActions.createPostNew(tags, title, contentItems, SEA))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
return res.status(500).json({
|
return res.status(500).json({
|
||||||
errorMessage:
|
errorMessage:
|
||||||
(typeof e === 'string' ? e : e.message) || 'Unknown error.'
|
(typeof e === 'string' ? e : e.message) || 'Unknown error.'
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ const {
|
||||||
} = require('../services/gunDB/Mediator')
|
} = require('../services/gunDB/Mediator')
|
||||||
const { deepDecryptIfNeeded } = require('../services/gunDB/rpc')
|
const { deepDecryptIfNeeded } = require('../services/gunDB/rpc')
|
||||||
const GunEvents = require('../services/gunDB/contact-api/events')
|
const GunEvents = require('../services/gunDB/contact-api/events')
|
||||||
|
const SchemaManager = require('../services/schema')
|
||||||
/**
|
/**
|
||||||
* @typedef {import('../services/gunDB/Mediator').SimpleSocket} SimpleSocket
|
* @typedef {import('../services/gunDB/Mediator').SimpleSocket} SimpleSocket
|
||||||
* @typedef {import('../services/gunDB/contact-api/SimpleGUN').ValidDataValue} ValidDataValue
|
* @typedef {import('../services/gunDB/contact-api/SimpleGUN').ValidDataValue} ValidDataValue
|
||||||
|
|
@ -73,6 +74,17 @@ module.exports = (
|
||||||
stream.on('data', data => {
|
stream.on('data', data => {
|
||||||
logger.info('[SOCKET] New invoice data:', data)
|
logger.info('[SOCKET] New invoice data:', data)
|
||||||
emitEncryptedEvent({ eventName: 'invoice:new', data, socket })
|
emitEncryptedEvent({ eventName: 'invoice:new', data, socket })
|
||||||
|
if (!data.settled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
SchemaManager.AddOrder({
|
||||||
|
type: 'invoice',
|
||||||
|
amount: parseInt(data.amt_paid_sat, 10),
|
||||||
|
coordinateHash: data.r_hash.toString('hex'),
|
||||||
|
coordinateIndex: parseInt(data.add_index, 10),
|
||||||
|
inbound: true,
|
||||||
|
toLndPub: data.payment_addr
|
||||||
|
})
|
||||||
})
|
})
|
||||||
stream.on('end', () => {
|
stream.on('end', () => {
|
||||||
logger.info('New invoice stream ended, starting a new one...')
|
logger.info('New invoice stream ended, starting a new one...')
|
||||||
|
|
@ -138,7 +150,18 @@ module.exports = (
|
||||||
logger.warn('Subscribing to transactions socket...' + subID)
|
logger.warn('Subscribing to transactions socket...' + subID)
|
||||||
stream.on('data', data => {
|
stream.on('data', data => {
|
||||||
logger.info('[SOCKET] New transaction data:', data)
|
logger.info('[SOCKET] New transaction data:', data)
|
||||||
emitEncryptedEvent({ eventName: 'transaction:new', data, socket })
|
|
||||||
|
Promise.all(data.dest_addresses.map(SchemaManager.isTmpChainOrder)).then(
|
||||||
|
responses => {
|
||||||
|
const hasOrder = responses.some(res => res !== false)
|
||||||
|
if (hasOrder && data.num_confirmations > 0) {
|
||||||
|
//buddy needs to manage this
|
||||||
|
} else {
|
||||||
|
//business as usual
|
||||||
|
emitEncryptedEvent({ eventName: 'transaction:new', data, socket })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
stream.on('end', () => {
|
stream.on('end', () => {
|
||||||
logger.info('New transactions stream ended, starting a new one...')
|
logger.info('New transactions stream ended, starting a new one...')
|
||||||
|
|
@ -215,13 +238,14 @@ module.exports = (
|
||||||
const subID = Math.floor(Math.random() * 1000).toString()
|
const subID = Math.floor(Math.random() * 1000).toString()
|
||||||
const isNotifications = isNotificationsSocket ? 'notifications' : ''
|
const isNotifications = isNotificationsSocket ? 'notifications' : ''
|
||||||
logger.info('[LND] New LND Socket created:' + isNotifications + subID)
|
logger.info('[LND] New LND Socket created:' + isNotifications + subID)
|
||||||
|
/* not used by wallet anymore
|
||||||
const cancelInvoiceStream = onNewInvoice(socket, subID)
|
const cancelInvoiceStream = onNewInvoice(socket, subID)
|
||||||
const cancelTransactionStream = onNewTransaction(socket, subID)
|
const cancelTransactionStream = onNewTransaction(socket, subID)
|
||||||
socket.on('disconnect', () => {
|
socket.on('disconnect', () => {
|
||||||
logger.info('LND socket disconnected:' + isNotifications + subID)
|
logger.info('LND socket disconnected:' + isNotifications + subID)
|
||||||
cancelInvoiceStream()
|
cancelInvoiceStream()
|
||||||
cancelTransactionStream()
|
cancelTransactionStream()
|
||||||
})
|
})*/
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,17 +20,26 @@ class LNDErrorManager {
|
||||||
*/
|
*/
|
||||||
_isCheckingHealth = false
|
_isCheckingHealth = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {string|null}
|
||||||
|
*/
|
||||||
|
_lndPub = null
|
||||||
|
|
||||||
|
get lndPub() {
|
||||||
|
return this._lndPub
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {HealthListener[]}
|
* @type {HealthListener[]}
|
||||||
*/
|
*/
|
||||||
_healthListeners = []
|
_healthListeners = []
|
||||||
|
|
||||||
//rejects if(err && err.code !== 12)
|
//rejects if(err && err.code !== 12)
|
||||||
getAvailableService(){
|
getAvailableService() {
|
||||||
|
|
||||||
//require('shock-common').Utils.makePromise((res, rej) => ...)
|
//require('shock-common').Utils.makePromise((res, rej) => ...)
|
||||||
return new Promise((res,rej)=>{
|
return new Promise((res, rej) => {
|
||||||
if(!this._isCheckingHealth){
|
if (!this._isCheckingHealth) {
|
||||||
this._isCheckingHealth = true
|
this._isCheckingHealth = true
|
||||||
this.getInfo()
|
this.getInfo()
|
||||||
}
|
}
|
||||||
|
|
@ -39,7 +48,7 @@ class LNDErrorManager {
|
||||||
* @param {LNDError} err
|
* @param {LNDError} err
|
||||||
* @param {object} response
|
* @param {object} response
|
||||||
*/
|
*/
|
||||||
const listener = (err,response)=>{
|
const listener = (err, response) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
if (err.code === 12) {
|
if (err.code === 12) {
|
||||||
res({
|
res({
|
||||||
|
|
@ -58,7 +67,7 @@ class LNDErrorManager {
|
||||||
walletStatus: 'unknown',
|
walletStatus: 'unknown',
|
||||||
success: false
|
success: false
|
||||||
})
|
})
|
||||||
} else if(err.code === 4){
|
} else if (err.code === 4) {
|
||||||
rej({
|
rej({
|
||||||
service: 'unknown',
|
service: 'unknown',
|
||||||
message:
|
message:
|
||||||
|
|
@ -77,7 +86,7 @@ class LNDErrorManager {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res({
|
res({
|
||||||
service: 'lightning',
|
service: 'lightning',
|
||||||
message: response,
|
message: response,
|
||||||
|
|
@ -85,7 +94,7 @@ class LNDErrorManager {
|
||||||
walletStatus: 'unlocked',
|
walletStatus: 'unlocked',
|
||||||
success: true
|
success: true
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
this._healthListeners.push(listener)
|
this._healthListeners.push(listener)
|
||||||
})
|
})
|
||||||
|
|
@ -93,28 +102,31 @@ class LNDErrorManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
//private
|
//private
|
||||||
getInfo(){
|
getInfo() {
|
||||||
const { lightning } = LightningServices.services
|
const { lightning } = LightningServices.services
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {LNDError} err
|
* @param {LNDError} err
|
||||||
* @param {object} response
|
* @param {{identity_pubkey:string}} response
|
||||||
*/
|
*/
|
||||||
const callback = (err, response) => {
|
const callback = (err, response) => {
|
||||||
this._healthListeners.forEach(l =>{
|
if (response && response.identity_pubkey) {
|
||||||
l(err,response)
|
this._lndPub = response.identity_pubkey
|
||||||
|
}
|
||||||
|
this._healthListeners.forEach(l => {
|
||||||
|
l(err, response)
|
||||||
})
|
})
|
||||||
this._healthListeners.length = 0
|
this._healthListeners.length = 0
|
||||||
this._isCheckingHealth = false
|
this._isCheckingHealth = false
|
||||||
}
|
}
|
||||||
const deadline = Date.now() + 10000
|
const deadline = Date.now() + 10000
|
||||||
lightning.getInfo({},{deadline}, callback)
|
lightning.getInfo({}, { deadline }, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {LNDError} e
|
* @param {LNDError} e
|
||||||
*/
|
*/
|
||||||
handleError(e){
|
handleError(e) {
|
||||||
return this.sanitizeLNDError(e)
|
return this.sanitizeLNDError(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,13 +134,13 @@ class LNDErrorManager {
|
||||||
* @param {LNDError} e
|
* @param {LNDError} e
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
sanitizeLNDError(e){
|
sanitizeLNDError(e) {
|
||||||
let eMessage = ''
|
let eMessage = ''
|
||||||
if(typeof e === 'string'){
|
if (typeof e === 'string') {
|
||||||
eMessage = e
|
eMessage = e
|
||||||
}else if(e.details){
|
} else if (e.details) {
|
||||||
eMessage = e.details
|
eMessage = e.details
|
||||||
} else if(e.message){
|
} else if (e.message) {
|
||||||
eMessage = e.message
|
eMessage = e.message
|
||||||
}
|
}
|
||||||
if (eMessage.toLowerCase().includes('unknown')) {
|
if (eMessage.toLowerCase().includes('unknown')) {
|
||||||
|
|
@ -137,13 +149,13 @@ class LNDErrorManager {
|
||||||
? splittedMessage.slice(1).join('')
|
? splittedMessage.slice(1).join('')
|
||||||
: splittedMessage.join('')
|
: splittedMessage.join('')
|
||||||
}
|
}
|
||||||
if(eMessage === ''){
|
if (eMessage === '') {
|
||||||
return 'unknown LND error'
|
return 'unknown LND error'
|
||||||
}
|
}
|
||||||
return eMessage
|
return eMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,6 @@ const logger = require('winston')
|
||||||
const Common = require('shock-common')
|
const Common = require('shock-common')
|
||||||
const Ramda = require('ramda')
|
const Ramda = require('ramda')
|
||||||
|
|
||||||
const { writeCoordinate } = require('../../services/coordinates')
|
|
||||||
|
|
||||||
const lightningServices = require('./lightning-services')
|
const lightningServices = require('./lightning-services')
|
||||||
/**
|
/**
|
||||||
* @typedef {import('./types').PaymentV2} PaymentV2
|
* @typedef {import('./types').PaymentV2} PaymentV2
|
||||||
|
|
@ -237,28 +235,12 @@ const decodePayReq = payReq =>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {Promise<string>}
|
|
||||||
*/
|
|
||||||
const myLNDPub = () =>
|
|
||||||
Common.makePromise((res, rej) => {
|
|
||||||
const { lightning } = lightningServices.getServices()
|
|
||||||
|
|
||||||
lightning.getInfo({}, (err, data) => {
|
|
||||||
if (err) {
|
|
||||||
rej(new Error(err.message))
|
|
||||||
} else {
|
|
||||||
res(data.identity_pubkey)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* aklssjdklasd
|
* aklssjdklasd
|
||||||
* @param {SendPaymentV2Request} sendPaymentRequest
|
* @param {SendPaymentV2Request} sendPaymentRequest
|
||||||
* @returns {Promise<PaymentV2>}
|
* @returns {Promise<PaymentV2>}
|
||||||
*/
|
*/
|
||||||
const sendPaymentV2 = async sendPaymentRequest => {
|
const sendPaymentV2 = sendPaymentRequest => {
|
||||||
const {
|
const {
|
||||||
services: { router }
|
services: { router }
|
||||||
} = lightningServices
|
} = lightningServices
|
||||||
|
|
@ -269,10 +251,7 @@ const sendPaymentV2 = async sendPaymentRequest => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
return Common.makePromise((res, rej) => {
|
||||||
* @type {import("./types").PaymentV2}
|
|
||||||
*/
|
|
||||||
const paymentV2 = await Common.makePromise((res, rej) => {
|
|
||||||
const stream = router.sendPaymentV2(sendPaymentRequest)
|
const stream = router.sendPaymentV2(sendPaymentRequest)
|
||||||
|
|
||||||
stream.on(
|
stream.on(
|
||||||
|
|
@ -311,33 +290,6 @@ const sendPaymentV2 = async sendPaymentRequest => {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
/** @type {Common.Coordinate} */
|
|
||||||
const coord = {
|
|
||||||
amount: Number(paymentV2.value_sat),
|
|
||||||
id: paymentV2.payment_hash,
|
|
||||||
inbound: false,
|
|
||||||
timestamp: Date.now(),
|
|
||||||
toLndPub: await myLNDPub(),
|
|
||||||
fromLndPub: undefined,
|
|
||||||
invoiceMemo: undefined,
|
|
||||||
type: 'payment'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sendPaymentRequest.payment_request) {
|
|
||||||
const invoice = await decodePayReq(sendPaymentRequest.payment_request)
|
|
||||||
|
|
||||||
coord.invoiceMemo = invoice.description
|
|
||||||
coord.toLndPub = invoice.destination
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sendPaymentRequest.dest) {
|
|
||||||
coord.toLndPub = sendPaymentRequest.dest.toString('base64')
|
|
||||||
}
|
|
||||||
|
|
||||||
await writeCoordinate(paymentV2.payment_hash, coord)
|
|
||||||
|
|
||||||
return paymentV2
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -619,6 +571,66 @@ const addInvoice = (value, memo = '', confidential = true, expiry = 180) =>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} lndErr
|
||||||
|
* @prop {string} reason
|
||||||
|
* @prop {number} code
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @param {(invoice:Common.Schema.InvoiceWhenListed & {r_hash:Buffer,payment_addr:string}) => (boolean | undefined)} dataCb
|
||||||
|
* @param {(error:lndErr) => void} errorCb
|
||||||
|
*/
|
||||||
|
const subscribeInvoices = (dataCb, errorCb) => {
|
||||||
|
const { lightning } = lightningServices.getServices()
|
||||||
|
const stream = lightning.subscribeInvoices({})
|
||||||
|
stream.on('data', invoice => {
|
||||||
|
const cancelStream = dataCb(invoice)
|
||||||
|
if (cancelStream) {
|
||||||
|
//@ts-expect-error
|
||||||
|
stream.cancel()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
stream.on('error', error => {
|
||||||
|
errorCb(error)
|
||||||
|
try {
|
||||||
|
//@ts-expect-error
|
||||||
|
stream.cancel()
|
||||||
|
} catch {
|
||||||
|
logger.info(
|
||||||
|
'[subscribeInvoices] tried to cancel an already canceled stream'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {(tx:Common.Schema.ChainTransaction) => (boolean | undefined)} dataCb
|
||||||
|
* @param {(error:lndErr) => void} errorCb
|
||||||
|
*/
|
||||||
|
const subscribeTransactions = (dataCb, errorCb) => {
|
||||||
|
const { lightning } = lightningServices.getServices()
|
||||||
|
const stream = lightning.subscribeTransactions({})
|
||||||
|
stream.on('data', transaction => {
|
||||||
|
const cancelStream = dataCb(transaction)
|
||||||
|
if (cancelStream) {
|
||||||
|
//@ts-expect-error
|
||||||
|
stream.cancel()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
stream.on('error', error => {
|
||||||
|
errorCb(error)
|
||||||
|
try {
|
||||||
|
//@ts-expect-error
|
||||||
|
stream.cancel()
|
||||||
|
} catch {
|
||||||
|
logger.info(
|
||||||
|
'[subscribeTransactions] tried to cancel an already canceled stream'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
sendPaymentV2Keysend,
|
sendPaymentV2Keysend,
|
||||||
sendPaymentV2Invoice,
|
sendPaymentV2Invoice,
|
||||||
|
|
@ -631,5 +643,6 @@ module.exports = {
|
||||||
listPeers,
|
listPeers,
|
||||||
pendingChannels,
|
pendingChannels,
|
||||||
addInvoice,
|
addInvoice,
|
||||||
myLNDPub
|
subscribeInvoices,
|
||||||
|
subscribeTransactions
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue