ln-sync
Version:
LN metadata persistence methods
227 lines (201 loc) • 6.71 kB
JavaScript
const {address} = require('bitcoinjs-lib');
const asyncAuto = require('async/auto');
const {createChainAddress} = require('ln-service');
const {getPublicKey} = require('ln-service');
const {networks} = require('bitcoinjs-lib');
const {payments} = require('bitcoinjs-lib');
const {returnResult} = require('asyncjs-util');
const {Transaction} = require('bitcoinjs-lib');
const getFundedTransaction = require('./get_funded_transaction');
const {getNetwork} = require('./../chain');
const getTransitRefund = require('./get_transit_refund');
const bufferAsHex = buffer => buffer.toString('hex');
const familyTemporary = 805;
const {fromBech32} = address;
const {fromHex} = Transaction;
const hexAsBuffer = hex => Buffer.from(hex, 'hex');
const minimum = 294;
const notFoundIndex = -1;
const {p2pkh} = payments;
const {p2wpkh} = payments;
const {toOutputScript} = address;
/** Get on-chain funding and a refund
{
ask: <Ask Function>
lnd: <Authenticated LND API Object>
logger: <Winston Logger Object>
[rate]: <Chain Fee Rate Tokens Per VByte Number>
tokens: <Fund Tokens Number>
}
@returns via cbk or Promise
{
address: <Transit Address String>
id: <Transaction Id Hex String>
index: <Transit Public Key Index Number>
[inputs]: [{
[lock_expires_at]: <UTXO Lock Expires At ISO 8601 Date String>
[lock_id]: <UTXO Lock Id Hex String>
transaction_id: <Transaction Hex Id String>
transaction_vout: <Transaction Output Index Number>
}]
key: <Transit Key Public Key Hex String>
output: <Transit Output Script Hex String>
[psbt]: <Transaction As Finalized PSBT Hex String>
refund: <Refund Transaction Hex String>
script: <Transit Signing Witness Script Hex String>
transaction: <Raw Transaction Hex String>
vout: <Funds Reserved At Output Index Number>
}
*/
module.exports = ({ask, lnd, logger, rate, tokens}, cbk) => {
return new Promise((resolve, reject) => {
return asyncAuto({
// Check arguments
validate: cbk => {
if (!ask) {
return cbk([400, 'ExpectedAskFunctionToReserveTransitFunds']);
}
if (!lnd) {
return cbk([400, 'ExpectedAuthenticatedLndToReserveTransitFunds']);
}
if (!logger) {
return cbk([400, 'ExpectedWinstonLoggerToReserveTransitFunds']);
}
if (!tokens) {
return cbk([400, 'ExpectedTokensToReserveToReserveTransitFunds']);
}
if (tokens < minimum) {
return cbk([400, 'ExpectedLargerAdditionToReserveTransitFunds']);
}
return cbk();
},
// Get the network name
getNetwork: ['validate', ({}, cbk) => getNetwork({lnd}, cbk)],
// Setup a new transit key for capacity increase
getTransitKey: ['getNetwork', ({getNetwork}, cbk) => {
if (!getNetwork.bitcoinjs) {
return cbk([400, 'ExpectedKnownNetworkToReserveTransitFunds']);
}
return getPublicKey({lnd, family: familyTemporary}, cbk);
}],
// Derive a transit address from the transit key
transit: [
'getNetwork',
'getTransitKey',
({getNetwork, getTransitKey}, cbk) =>
{
return cbk(null, p2wpkh({
network: networks[getNetwork.bitcoinjs],
pubkey: hexAsBuffer(getTransitKey.public_key),
}));
}],
// Get funding to the transit key
getFunding: ['transit', ({transit}, cbk) => {
return getFundedTransaction({
ask,
lnd,
logger,
chain_fee_tokens_per_vbyte: rate || undefined,
outputs: [{tokens, address: transit.address}],
},
cbk);
}],
// Create a refund address
createRefund: ['getFunding', ({}, cbk) => {
return createChainAddress({lnd}, cbk);
}],
// Confirm the funding transaction output
transactionVout: [
'getFunding',
'transit',
({getFunding, transit}, cbk) =>
{
const {transaction} = getFunding;
if (!transaction) {
return cbk([400, 'ExpectedFundedTransactionToReserveTransitFunds']);
}
const vout = fromHex(transaction).outs.findIndex(({script}) => {
return script.equals(transit.output);
});
if (vout === notFoundIndex) {
return cbk([400, 'ExpectedTransitTxOutputPayingToTransitAddress']);
}
// The transaction must have the output sending to the address
if (fromHex(transaction).outs[vout].value !== tokens) {
return cbk([
400,
'UnexpectedFundingAmountPayingToTransitAddress',
{expected: tokens},
]);
}
return cbk(null, vout);
}],
// Get a refund transaction for the transit funds
getRefund: [
'createRefund',
'getFunding',
'getNetwork',
'getTransitKey',
'transit',
'transactionVout',
({
createRefund,
getFunding,
getNetwork,
getTransitKey,
transit,
transactionVout,
},
cbk) =>
{
return getTransitRefund({
lnd,
funded_tokens: tokens,
network: getNetwork.network,
refund_address: createRefund.address,
transit_address: transit.address,
transit_key_index: getTransitKey.index,
transit_public_key: getTransitKey.public_key,
transaction_id: getFunding.id,
transaction_vout: transactionVout,
},
cbk);
}],
// Final funding details, including a refund paying out of transit
funding: [
'getFunding',
'getNetwork',
'getRefund',
'getTransitKey',
'transactionVout',
'transit',
({
getFunding,
getNetwork,
getRefund,
getTransitKey,
transactionVout,
transit,
},
cbk) =>
{
const network = networks[getNetwork.bitcoinjs];
const {data} = fromBech32(transit.address, network);
return cbk(null, {
address: transit.address,
id: getFunding.id,
index: getTransitKey.index,
inputs: getFunding.inputs,
key: getTransitKey.public_key,
output: bufferAsHex(toOutputScript(transit.address, network)),
psbt: getFunding.psbt,
refund: getRefund.refund,
script: bufferAsHex(p2pkh({hash: data}).output),
transaction: getFunding.transaction,
vout: transactionVout,
});
}],
},
returnResult({reject, resolve, of: 'funding'}, cbk));
});
};