mirror of
https://github.com/alexbosworth/balanceofsatoshis.git
synced 2026-08-13 12:33:37 +02:00
add cleartext export for credentials
This commit is contained in:
parent
583f2523cc
commit
a82fa3b3bc
29 changed files with 926 additions and 349 deletions
|
|
@ -1,5 +1,9 @@
|
|||
# Versions
|
||||
|
||||
## Version 5.6.2
|
||||
|
||||
- `credentials`: add `--cleartext` option to output node access credentials
|
||||
|
||||
## Version 5.6.1
|
||||
|
||||
- `increase-outbound-liquidity`: add `fee-rate` option to specify chain fee rate
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
const {floor} = Math;
|
||||
const {isArray} = Array;
|
||||
const {random} = Math;
|
||||
|
||||
/** Shuffle array
|
||||
|
|
@ -13,15 +14,21 @@ const {random} = Math;
|
|||
}
|
||||
*/
|
||||
module.exports = ({array}) => {
|
||||
const shuffle = array.slice();
|
||||
|
||||
if (!!shuffle.length) {
|
||||
for (let i = shuffle.length - 1; !!i; i--) {
|
||||
const j = floor(random() * (i + 1));
|
||||
|
||||
[shuffle[i], shuffle[j]] = [shuffle[j], shuffle[i]];
|
||||
}
|
||||
if (!isArray(array)) {
|
||||
throw new Error('ExpectedArrayToShuffle');
|
||||
}
|
||||
|
||||
return {shuffled: shuffle};
|
||||
if (!array.length) {
|
||||
return {shuffled: []};
|
||||
}
|
||||
|
||||
const shuffled = array.slice();
|
||||
|
||||
for (let i = shuffled.length - 1; !!i; i--) {
|
||||
const j = floor(random() * (i + 1));
|
||||
|
||||
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
||||
}
|
||||
|
||||
return {shuffled};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ const none = 0;
|
|||
[is_offchain_only]: <Get Only Channels Tokens Bool>
|
||||
[is_onchain_only]: <Get Only Chain Tokens Bool>
|
||||
lnd: <Authenticated LND gRPC API Object>
|
||||
[node]: <Node Name String>
|
||||
}
|
||||
|
||||
@returns via cbk
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ const {percentile} = require('stats-lite');
|
|||
const {returnResult} = require('asyncjs-util');
|
||||
|
||||
const balanceFromTokens = require('./balance_from_tokens');
|
||||
const {lndCredentials} = require('./../lnd');
|
||||
|
||||
const {round} = Math;
|
||||
const topPercentile = 0.9;
|
||||
|
|
@ -17,7 +16,7 @@ const topPercentile = 0.9;
|
|||
[below]: <Tokens Below Tokens Number>
|
||||
[is_outbound]: <Return Outbound Liquidity Bool>
|
||||
[is_top]: <Return Top Liquidity Bool>
|
||||
[node]: <Node Name String>
|
||||
lnd: <Authenticated LND gRPC API Object>
|
||||
[with]: <Liquidity With Specific Node Public Key Hex String>
|
||||
}
|
||||
|
||||
|
|
@ -27,57 +26,54 @@ const topPercentile = 0.9;
|
|||
}
|
||||
*/
|
||||
module.exports = (args, cbk) => {
|
||||
return asyncAuto({
|
||||
// Credentials
|
||||
credentials: cbk => lndCredentials({node: args.node}, cbk),
|
||||
return new Promise((resolve, reject) => {
|
||||
return asyncAuto({
|
||||
// Check arguments
|
||||
validate: cbk => {
|
||||
if (!args.lnd) {
|
||||
return cbk([400, 'ExpectedLndToGetLiquidity']);
|
||||
}
|
||||
|
||||
// Lnd
|
||||
lnd: ['credentials', ({credentials}, cbk) => {
|
||||
return cbk(null, authenticatedLndGrpc({
|
||||
cert: credentials.cert,
|
||||
macaroon: credentials.macaroon,
|
||||
socket: credentials.socket,
|
||||
}).lnd);
|
||||
}],
|
||||
return cbk();
|
||||
},
|
||||
|
||||
// Get the channels
|
||||
getChannels: ['lnd', ({lnd}, cbk) => getChannels({lnd}, cbk)],
|
||||
// Get the channels
|
||||
getChannels: ['validate', ({}, cbk) => {
|
||||
return getChannels({lnd: args.lnd}, cbk);
|
||||
}],
|
||||
|
||||
// List of tokens to sum
|
||||
tokens: ['getChannels', ({getChannels}, cbk) => {
|
||||
const activeChannels = getChannels.channels
|
||||
.filter(n => !!n.is_active)
|
||||
.filter(n => !args.with || n.partner_public_key === args.with);
|
||||
// List of tokens to sum
|
||||
tokens: ['getChannels', ({getChannels}, cbk) => {
|
||||
const activeChannels = getChannels.channels
|
||||
.filter(n => !!n.is_active)
|
||||
.filter(n => !args.with || n.partner_public_key === args.with);
|
||||
|
||||
const balanceType = !!args.is_outbound ? 'local' : 'remote';
|
||||
const balanceType = !!args.is_outbound ? 'local' : 'remote';
|
||||
|
||||
const tokens = activeChannels.map(n => n[`${balanceType}_balance`]);
|
||||
const tokens = activeChannels.map(n => n[`${balanceType}_balance`]);
|
||||
|
||||
if (!!args.is_top) {
|
||||
return cbk(null, [round(percentile(tokens, topPercentile))]);
|
||||
}
|
||||
if (!!args.is_top) {
|
||||
return cbk(null, [round(percentile(tokens, topPercentile))]);
|
||||
}
|
||||
|
||||
return cbk(null, tokens);
|
||||
}],
|
||||
return cbk(null, tokens);
|
||||
}],
|
||||
|
||||
// Total balances
|
||||
total: ['tokens', ({tokens}, cbk) => {
|
||||
const {above} = args;
|
||||
const {below} = args;
|
||||
// Total balances
|
||||
total: ['tokens', ({tokens}, cbk) => {
|
||||
const {above} = args;
|
||||
const {below} = args;
|
||||
|
||||
try {
|
||||
const balance = balanceFromTokens({above, below, tokens});
|
||||
|
||||
return cbk(null, balance);
|
||||
} catch (err) {
|
||||
return cbk([500, 'FailedToCalculateLiquidityBalance', err]);
|
||||
}
|
||||
}],
|
||||
}],
|
||||
|
||||
// Liquidity
|
||||
liquidity: ['total', ({total}, cbk) => {
|
||||
return cbk(null, {balance: total});
|
||||
}],
|
||||
},
|
||||
returnResult({of: 'liquidity'}, cbk));
|
||||
// Liquidity
|
||||
liquidity: ['total', ({total}, cbk) => {
|
||||
return cbk(null, {balance: total});
|
||||
}],
|
||||
},
|
||||
returnResult({reject, resolve, of: 'liquidity'}, cbk));
|
||||
});
|
||||
};
|
||||
|
|
|
|||
93
bos
93
bos
|
|
@ -57,6 +57,7 @@ const {version} = require('./package');
|
|||
|
||||
const {exit} = process;
|
||||
const flatten = arr => [].concat(...arr);
|
||||
const {FLOAT} = prog;
|
||||
const {INT} = prog;
|
||||
const {isArray} = Array;
|
||||
const {keys} = Object;
|
||||
|
|
@ -281,12 +282,14 @@ prog
|
|||
// Export LND credentials from the standard
|
||||
.command('credentials', 'Export local credentials')
|
||||
.help('Output encrypted remote access credentials. Use with "nodes --add"')
|
||||
.option('--cleartext', 'Output remote access credentials without encryption')
|
||||
.option('--node <node_name>', 'Get credentials for a saved node')
|
||||
.action((args, options, logger) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return getCredentials({
|
||||
logger,
|
||||
ask: (n, cbk) => inquirer.prompt([n]).then(res => cbk(res)),
|
||||
is_cleartext: options.cleartext,
|
||||
node: options.node,
|
||||
},
|
||||
returnObject({logger, reject, resolve}));
|
||||
|
|
@ -324,13 +327,17 @@ prog
|
|||
.option('--no-color', 'Mute all colors')
|
||||
.option('--node <node_name>', 'Node to find record on')
|
||||
.action((args, options, logger) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return findRecord({
|
||||
node: options.node,
|
||||
query: args.query,
|
||||
},
|
||||
returnObject({logger, reject, resolve}));
|
||||
})
|
||||
try {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
return findRecord({
|
||||
lnd: (await authenticatedLnd({logger, node: options.node})).lnd,
|
||||
query: args.query,
|
||||
},
|
||||
returnObject({logger, reject, resolve}));
|
||||
});
|
||||
} catch (err) {
|
||||
return reject(err);
|
||||
}
|
||||
})
|
||||
|
||||
// Get forwards
|
||||
|
|
@ -387,14 +394,18 @@ prog
|
|||
.argument('<amount>', 'Tokens to give', INT)
|
||||
.option('--node <node_name>', 'Source node to use to pay gift')
|
||||
.action((args, options, logger) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return sendGift({
|
||||
node: options.node,
|
||||
to: args.target,
|
||||
tokens: args.amount,
|
||||
},
|
||||
returnNumber({logger, reject, resolve, number: 'gave_tokens'}));
|
||||
});
|
||||
try {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
return sendGift({
|
||||
lnd: (await authenticatedLnd({logger, node: options.node})).lnd,
|
||||
to: args.target,
|
||||
tokens: args.amount,
|
||||
},
|
||||
returnNumber({logger, reject, resolve, number: 'gave_tokens'}));
|
||||
});
|
||||
} catch (err) {
|
||||
return reject(err);
|
||||
}
|
||||
})
|
||||
|
||||
// Get inbound liquidity information: available inbound off-chain tokens
|
||||
|
|
@ -405,16 +416,20 @@ prog
|
|||
.option('--top', 'Top percentile inbound liquidity in an individual channel')
|
||||
.option('--with', 'Liquidity with a specific node')
|
||||
.action((args, options, logger) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return getLiquidity({
|
||||
above: options.above || undefined,
|
||||
below: options.below || undefined,
|
||||
is_top: options.top || undefined,
|
||||
node: options.node || undefined,
|
||||
with: options.with || undefined,
|
||||
},
|
||||
returnNumber({logger, reject, resolve, number: 'balance'}));
|
||||
});
|
||||
try {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
return getLiquidity({
|
||||
above: options.above || undefined,
|
||||
below: options.below || undefined,
|
||||
is_top: options.top || undefined,
|
||||
lnd: (await authenticatedLnd({logger, node: options.node})).lnd,
|
||||
with: options.with || undefined,
|
||||
},
|
||||
returnNumber({logger, reject, resolve, number: 'balance'}));
|
||||
});
|
||||
} catch (err) {
|
||||
return reject(err);
|
||||
}
|
||||
})
|
||||
|
||||
// Increase inbound liquidity
|
||||
|
|
@ -465,7 +480,7 @@ prog
|
|||
.help('Open a new channel to add more off-chain liquidity')
|
||||
.option('--amount <amount>', 'Amount to assign to new channel capacity', INT)
|
||||
.option('--dryrun', 'Avoid actually opening a channel')
|
||||
.option('--fee-rate <fee_rate>', 'Use specific fee rate (per vbyte)', INT)
|
||||
.option('--fee-rate <fee_rate>', 'Use specific fee rate (per vbyte)', FLOAT)
|
||||
.option('--node <node_name>', 'Increase outbound liquidity on saved node')
|
||||
.option('--with <peer_public_key>', 'Select a specific peer to open with')
|
||||
.action((args, options, logger) => {
|
||||
|
|
@ -567,17 +582,21 @@ prog
|
|||
.option('--top', 'Top percentile inbound liquidity in an individual channel')
|
||||
.option('--with', 'Liquidity with a specific node')
|
||||
.action((args, options, logger) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return getLiquidity({
|
||||
above: options.above || undefined,
|
||||
below: options.below || undefined,
|
||||
is_outbound: true,
|
||||
is_top: options.top || undefined,
|
||||
node: options.node || undefined,
|
||||
with: options.with || undefined,
|
||||
},
|
||||
returnNumber({logger, reject, resolve, number: 'balance'}));
|
||||
});
|
||||
try {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
return getLiquidity({
|
||||
above: options.above || undefined,
|
||||
below: options.below || undefined,
|
||||
is_outbound: true,
|
||||
is_top: options.top || undefined,
|
||||
lnd: (await authenticatedLnd({logger, node: options.node})).lnd,
|
||||
with: options.with || undefined,
|
||||
},
|
||||
returnNumber({logger, reject, resolve, number: 'balance'}));
|
||||
});
|
||||
} catch (err) {
|
||||
return reject(err);
|
||||
}
|
||||
})
|
||||
|
||||
// Pay a payment request, probing first
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ const {take} = require('lodash');
|
|||
|
||||
const {authenticatedLnd} = require('./../lnd');
|
||||
const getChannelResolution = require('./get_channel_resolution');
|
||||
const {getNetwork} = require('./../network');
|
||||
const getNetwork = require('./../network/get_network');
|
||||
|
||||
const defaultLimit = 20;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@ const {returnResult} = require('asyncjs-util');
|
|||
|
||||
const interval = n => 50 * Math.pow(2, n);
|
||||
const isNumber = n => !isNaN(n);
|
||||
const times = 10;
|
||||
|
||||
/** Get mempool size
|
||||
|
||||
{
|
||||
network: <Network Name String>
|
||||
request: <Request Function>
|
||||
[retries]: <Retries Count Number>
|
||||
}
|
||||
|
||||
@returns via cbk or Promise
|
||||
|
|
@ -18,7 +18,7 @@ const times = 10;
|
|||
[vbytes]: <Size of Mempool Virtual Bytes Number>
|
||||
}
|
||||
*/
|
||||
module.exports = ({network, request}, cbk) => {
|
||||
module.exports = ({network, request, retries}, cbk) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return asyncAuto({
|
||||
// Check arguments
|
||||
|
|
@ -54,7 +54,7 @@ module.exports = ({network, request}, cbk) => {
|
|||
return cbk(null, {});
|
||||
}
|
||||
|
||||
return asyncRetry({interval, times}, cbk => {
|
||||
return asyncRetry({interval, times: retries}, cbk => {
|
||||
return request({
|
||||
json: true,
|
||||
url: `${api}/api/mempool`,
|
||||
|
|
|
|||
|
|
@ -6,8 +6,6 @@ const {getPayment} = require('ln-service');
|
|||
const moment = require('moment');
|
||||
const {returnResult} = require('asyncjs-util');
|
||||
|
||||
const authenticatedLnd = require('./authenticated_lnd');
|
||||
|
||||
const asBigUnit = tokens => (tokens / 1e8).toFixed(8);
|
||||
const standardIdHexLength = Buffer.alloc(32).toString('hex').length;
|
||||
|
||||
|
|
@ -16,7 +14,7 @@ const standardIdHexLength = Buffer.alloc(32).toString('hex').length;
|
|||
Try to find a record by id
|
||||
|
||||
{
|
||||
[node]: <Node Name String>
|
||||
lnd: <Authenticated LND gRPC API Object>
|
||||
[query]: <Query String>
|
||||
}
|
||||
|
||||
|
|
@ -27,98 +25,104 @@ const standardIdHexLength = Buffer.alloc(32).toString('hex').length;
|
|||
}
|
||||
}
|
||||
*/
|
||||
module.exports = ({node, query}, cbk) => {
|
||||
return asyncAuto({
|
||||
// Lnd
|
||||
getLnd: cbk => authenticatedLnd({node}, cbk),
|
||||
module.exports = ({lnd, query}, cbk) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return asyncAuto({
|
||||
// Check arguments
|
||||
validate: cbk => {
|
||||
if (!lnd) {
|
||||
return cbk([400, 'ExpectedLndObjectToFindRecord']);
|
||||
}
|
||||
|
||||
// Get graph
|
||||
getGraph: ['getLnd', ({getLnd}, cbk) => {
|
||||
return getNetworkGraph({lnd: getLnd.lnd}, cbk);
|
||||
}],
|
||||
return cbk();
|
||||
},
|
||||
|
||||
// Payment
|
||||
getPayment: ['getLnd', ({getLnd}, cbk) => {
|
||||
if (query.length !== standardIdHexLength) {
|
||||
return cbk(null, {});
|
||||
}
|
||||
// Get graph
|
||||
getGraph: ['validate', ({}, cbk) => getNetworkGraph({lnd}, cbk)],
|
||||
|
||||
return getPayment({id: query, lnd: getLnd.lnd}, cbk);
|
||||
}],
|
||||
// Payment
|
||||
getPayment: ['validate', ({}, cbk) => {
|
||||
if (query.length !== standardIdHexLength) {
|
||||
return cbk(null, {});
|
||||
}
|
||||
|
||||
// Records
|
||||
records: ['getGraph', 'getPayment', ({getGraph, getPayment}, cbk) => {
|
||||
const nodes = getGraph.nodes
|
||||
.filter(node => {
|
||||
if (node.alias.toLowerCase().includes(query.toLowerCase())) {
|
||||
return true;
|
||||
}
|
||||
return getPayment({lnd, id: query}, cbk);
|
||||
}],
|
||||
|
||||
return node.public_key === query;
|
||||
})
|
||||
.map(node => {
|
||||
return {
|
||||
alias: node.alias,
|
||||
capacity: asBigUnit(getGraph.channels.reduce(
|
||||
(sum, {capacity, policies}) => {
|
||||
if (!policies.find(n => n.public_key === node.public_key)) {
|
||||
return sum;
|
||||
}
|
||||
|
||||
return sum + capacity;
|
||||
},
|
||||
0
|
||||
)),
|
||||
public_key: node.public_key,
|
||||
updated: moment(node.updated_at).fromNow(),
|
||||
urls: node.sockets.map(socket => `${node.public_key}@${socket}`),
|
||||
}
|
||||
})
|
||||
.filter(node => node.capacity !== asBigUnit(0));
|
||||
|
||||
const channels = getGraph.channels
|
||||
.filter(channel => {
|
||||
try {
|
||||
if (channel.id === chanFormat({number: query}).channel) {
|
||||
// Records
|
||||
records: ['getGraph', 'getPayment', ({getGraph, getPayment}, cbk) => {
|
||||
const nodes = getGraph.nodes
|
||||
.filter(node => {
|
||||
if (node.alias.toLowerCase().includes(query.toLowerCase())) {
|
||||
return true;
|
||||
}
|
||||
} catch (err) {}
|
||||
|
||||
return channel.id === query;
|
||||
})
|
||||
.map(channel => {
|
||||
return {
|
||||
capacity: channel.capacity,
|
||||
id: channel.id,
|
||||
policies: channel.policies.map(policy => {
|
||||
const node = getGraph.nodes
|
||||
.find(n => n.public_key === policy.public_key);
|
||||
return node.public_key === query;
|
||||
})
|
||||
.map(node => {
|
||||
return {
|
||||
alias: node.alias,
|
||||
capacity: asBigUnit(getGraph.channels.reduce(
|
||||
(sum, {capacity, policies}) => {
|
||||
if (!policies.find(n => n.public_key === node.public_key)) {
|
||||
return sum;
|
||||
}
|
||||
|
||||
return {
|
||||
alias: !node ? undefined : node.alias,
|
||||
base_fee_mtokens: policy.base_fee_mtokens,
|
||||
cltv_delta: policy.cltv_delta,
|
||||
fee_rate: policy.fee_rate,
|
||||
is_disabled: policy.is_disabled,
|
||||
max_htlc_mtokens: policy.max_htlc_mtokens,
|
||||
min_htlc_mtokens: policy.min_htlc_mtokens,
|
||||
public_key: policy.public_key,
|
||||
};
|
||||
}),
|
||||
transaction_id: channel.transaction_id,
|
||||
transaction_vout: channel.transaction_vout,
|
||||
updated_at: channel.updated_at,
|
||||
};
|
||||
return sum + capacity;
|
||||
},
|
||||
0
|
||||
)),
|
||||
public_key: node.public_key,
|
||||
updated: moment(node.updated_at).fromNow(),
|
||||
urls: node.sockets.map(socket => `${node.public_key}@${socket}`),
|
||||
}
|
||||
})
|
||||
.filter(node => node.capacity !== asBigUnit(0));
|
||||
|
||||
const channels = getGraph.channels
|
||||
.filter(channel => {
|
||||
try {
|
||||
if (channel.id === chanFormat({number: query}).channel) {
|
||||
return true;
|
||||
}
|
||||
} catch (err) {}
|
||||
|
||||
return channel.id === query;
|
||||
})
|
||||
.map(channel => {
|
||||
return {
|
||||
capacity: channel.capacity,
|
||||
id: channel.id,
|
||||
policies: channel.policies.map(policy => {
|
||||
const node = getGraph.nodes
|
||||
.find(n => n.public_key === policy.public_key);
|
||||
|
||||
return {
|
||||
alias: !node ? undefined : node.alias,
|
||||
base_fee_mtokens: policy.base_fee_mtokens,
|
||||
cltv_delta: policy.cltv_delta,
|
||||
fee_rate: policy.fee_rate,
|
||||
is_disabled: policy.is_disabled,
|
||||
max_htlc_mtokens: policy.max_htlc_mtokens,
|
||||
min_htlc_mtokens: policy.min_htlc_mtokens,
|
||||
public_key: policy.public_key,
|
||||
};
|
||||
}),
|
||||
transaction_id: channel.transaction_id,
|
||||
transaction_vout: channel.transaction_vout,
|
||||
updated_at: channel.updated_at,
|
||||
};
|
||||
});
|
||||
|
||||
return cbk(null, {
|
||||
channels: !!channels.length ? channels : undefined,
|
||||
nodes: !!nodes.length ? nodes : undefined,
|
||||
payment: getPayment.payment || undefined,
|
||||
payment_failed: getPayment.failed || undefined,
|
||||
payment_pending: getPayment.is_pending || undefined,
|
||||
});
|
||||
|
||||
return cbk(null, {
|
||||
channels: !!channels.length ? channels : undefined,
|
||||
nodes: !!nodes.length ? nodes : undefined,
|
||||
payment: getPayment.payment || undefined,
|
||||
payment_failed: getPayment.failed || undefined,
|
||||
payment_pending: getPayment.is_pending || undefined,
|
||||
});
|
||||
}],
|
||||
},
|
||||
returnResult({of :'records'}, cbk));
|
||||
}],
|
||||
},
|
||||
returnResult({reject, resolve, of :'records'}, cbk));
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,25 +9,31 @@ const {pemAsDer} = require('./../encryption');
|
|||
|
||||
{
|
||||
ask: <Inquirer Function> ({message, name, type}, cbk) => {}
|
||||
is_cleartext: <Export Clear Credential Components Bool>
|
||||
logger: <Winston Logger Object> ({info}) => ()
|
||||
[node]: <Node Name String>
|
||||
}
|
||||
|
||||
@returns via cbk or Promise
|
||||
{
|
||||
credentials: <Encrypted Node Credentials CBOR Hex String>
|
||||
[cleartext]: {
|
||||
cert: <TLS Cert File Base64 Encoded String>
|
||||
macaroon: <Macaroon Authentication File Base64 Encoded String>
|
||||
socket: <External Host and Port String>
|
||||
}
|
||||
[credentials]: <Encrypted Node Credentials CBOR Hex String>
|
||||
}
|
||||
*/
|
||||
module.exports = ({ask, logger, node}, cbk) => {
|
||||
module.exports = (args, cbk) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return asyncAuto({
|
||||
// Check arguments
|
||||
validate: cbk => {
|
||||
if (!ask) {
|
||||
if (!args.ask) {
|
||||
return cbk([400, 'ExpectedPromptFunctionToGetCredentials']);
|
||||
}
|
||||
|
||||
if (!logger) {
|
||||
if (!args.logger) {
|
||||
return cbk([400, 'ExpectedLoggerToGetCredentials']);
|
||||
}
|
||||
|
||||
|
|
@ -36,22 +42,43 @@ module.exports = ({ask, logger, node}, cbk) => {
|
|||
|
||||
// Ask for the transfer key
|
||||
key: ['validate', ({}, cbk) => {
|
||||
if (!!args.is_cleartext) {
|
||||
return cbk();
|
||||
}
|
||||
|
||||
const enterTransferKey = {
|
||||
message: 'Enter a transfer public key:',
|
||||
name: 'key',
|
||||
type: 'input',
|
||||
};
|
||||
|
||||
return ask(enterTransferKey, ({key}) => cbk(null, key));
|
||||
return args.ask(enterTransferKey, ({key}) => cbk(null, key));
|
||||
}],
|
||||
|
||||
// Get credentials encrypted to transfer key
|
||||
getCredentials: ['key', ({key}, cbk) => {
|
||||
return lndCredentials({key, logger, node}, cbk);
|
||||
if (!args.is_cleartext && !key) {
|
||||
return cbk([400, 'ExpectedCredentialsTransferKeyFromNodesAdd']);
|
||||
}
|
||||
|
||||
return lndCredentials({
|
||||
key,
|
||||
logger: args.logger,
|
||||
node: args.node,
|
||||
},
|
||||
cbk);
|
||||
}],
|
||||
|
||||
// Packaged credentials
|
||||
credentials: ['getCredentials', ({getCredentials}, cbk) => {
|
||||
if (!!args.is_cleartext) {
|
||||
return cbk(null, {
|
||||
cert: getCredentials.cert,
|
||||
macaroon: getCredentials.macaroon,
|
||||
socket: getCredentials.external_socket || getCredentials.socket,
|
||||
});
|
||||
}
|
||||
|
||||
const encryptedMacaroon = getCredentials.encrypted_macaroon;
|
||||
const externalSocket = getCredentials.external_socket;
|
||||
|
||||
|
|
|
|||
35
network/gift_callback_error.js
Normal file
35
network/gift_callback_error.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
/** Map a gift route error to a callback error
|
||||
|
||||
{
|
||||
err: {
|
||||
message: <Error Message String>
|
||||
}
|
||||
}
|
||||
|
||||
@returns
|
||||
[
|
||||
<Callback Error Code Number>
|
||||
<Callback Error Message String>
|
||||
[Callback Error Context Object>]
|
||||
]
|
||||
*/
|
||||
module.exports = ({err}) => {
|
||||
const {message} = err;
|
||||
|
||||
switch (message) {
|
||||
case 'NoActiveChannelWithSpecifiedPeer':
|
||||
return [400, 'SendingGiftRequiresActiveChannelWithPeer'];
|
||||
|
||||
case 'NoActiveChannelWithSufficientLocalBalance':
|
||||
return [400, 'SendingGiftRequiresChanWithSufficientBalance'];
|
||||
|
||||
case 'NoActiveChannelWithSufficientRemoteBalance':
|
||||
return [400, 'SendingGiftRequiresChanWithSomeRemoteBalance'];
|
||||
|
||||
case 'NoDirectChannelWithSpecifiedPeer':
|
||||
return [400, 'SendingGiftRequiresDirectChannelWithPeer'];
|
||||
|
||||
default:
|
||||
return [500, 'UnexpectedErrorDeterminingChanForGift', {err}];
|
||||
}
|
||||
};
|
||||
|
|
@ -17,10 +17,12 @@ const getScoredNodes = require('./get_scored_nodes');
|
|||
const peersWithActivity = require('./peers_with_activity');
|
||||
const {shuffle} = require('./../arrays');
|
||||
|
||||
const asBigTok = tokens => (tokens / 1e8).toFixed(8);
|
||||
const channelTokens = 5e6;
|
||||
const days = 90;
|
||||
const fastConf = 6;
|
||||
const {floor} = Math;
|
||||
const getMempoolRetries = 10;
|
||||
const maxMempoolSize = 2e6;
|
||||
const minOutbound = 4294967;
|
||||
const minForwarded = 1e5;
|
||||
|
|
@ -121,6 +123,7 @@ module.exports = (args, cbk) => {
|
|||
return getMempoolSize({
|
||||
network: getNetwork.network,
|
||||
request: args.request,
|
||||
retries: getMempoolRetries,
|
||||
},
|
||||
cbk);
|
||||
}],
|
||||
|
|
@ -247,9 +250,9 @@ module.exports = (args, cbk) => {
|
|||
|
||||
const node = {
|
||||
alias: !!res && !!res.alias ? res.alias : undefined,
|
||||
forwarded: candidate.forwarded,
|
||||
inbound: candidate.inbound,
|
||||
outbound: candidate.outbound,
|
||||
past_forwarded: asBigTok(candidate.forwarded),
|
||||
current_inbound: asBigTok(candidate.inbound),
|
||||
current_outbound: asBigTok(candidate.outbound),
|
||||
public_key: candidate.public_key,
|
||||
socket: !!socket ? socket.socket : undefined,
|
||||
};
|
||||
|
|
@ -272,6 +275,7 @@ module.exports = (args, cbk) => {
|
|||
opening_with: node,
|
||||
chain_fee_tokens_per_vbyte: getNormalFee.tokens_per_vbyte,
|
||||
is_dry_run: true,
|
||||
new_channel_size: asBigTok(args.tokens || channelTokens),
|
||||
});
|
||||
|
||||
return cbk(null, true);
|
||||
|
|
@ -305,6 +309,7 @@ module.exports = (args, cbk) => {
|
|||
opening_with: node,
|
||||
chain_fee_tokens_per_vbyte: getNormalFee.tokens_per_vbyte,
|
||||
transaction_id: res.transaction_id,
|
||||
new_channel_size: asBigTok(args.tokens || channelTokens),
|
||||
});
|
||||
|
||||
return cbk(null, true);
|
||||
|
|
|
|||
|
|
@ -4,159 +4,127 @@ const {getChannel} = require('ln-service');
|
|||
const {getChannels} = require('ln-service');
|
||||
const {getWalletInfo} = require('ln-service');
|
||||
const {payViaRoutes} = require('ln-service');
|
||||
const {routeFromChannels} = require('ln-service');
|
||||
const {returnResult} = require('asyncjs-util');
|
||||
|
||||
const {authenticatedLnd} = require('./../lnd');
|
||||
const {channelForGift} = require('./../routing');
|
||||
const giftCallbackError = require('./gift_callback_error');
|
||||
const {giftRoute} = require('./../routing');
|
||||
const {sortBy} = require('./../arrays');
|
||||
|
||||
const {floor} = Math;
|
||||
const minFeeRate = 0;
|
||||
const minReceivableMtokens = BigInt(1000);
|
||||
const mtokPerTok = BigInt(1000);
|
||||
const reserveRatio = 0.01;
|
||||
|
||||
/** Send a gift of some tokens to a peer.
|
||||
|
||||
{
|
||||
[node]: <From Node Name String>
|
||||
to: <To Node Public Key hex string>
|
||||
lnd: <Authenticated LND gRPC API Object>
|
||||
to: <To Node Public Key Hex string>
|
||||
tokens: <Tokens to Gift Number>
|
||||
}
|
||||
|
||||
@returns via cbk
|
||||
@returns via cbk or Promise
|
||||
{
|
||||
gave_tokens: <Gave Tokens Number>
|
||||
}
|
||||
*/
|
||||
module.exports = ({node, to, tokens}, cbk) => {
|
||||
return asyncAuto({
|
||||
// Credentials
|
||||
getLnd: cbk => authenticatedLnd({node}, cbk),
|
||||
|
||||
// Check arguments
|
||||
validate: cbk => {
|
||||
if (!to) {
|
||||
return cbk([400, 'ExpectedPeerToSendGiftTo']);
|
||||
}
|
||||
|
||||
if (!tokens) {
|
||||
return cbk([400, 'ExpectedTokensToGiftToPeer']);
|
||||
}
|
||||
|
||||
return cbk();
|
||||
},
|
||||
|
||||
// Lnd
|
||||
lnd: ['getLnd', ({getLnd}, cbk) => cbk(null, getLnd.lnd)],
|
||||
|
||||
// Get channels
|
||||
getChannels: ['lnd', ({lnd}, cbk) => getChannels({lnd}, cbk)],
|
||||
|
||||
// Peer channel
|
||||
peerChannel: ['getChannels', ({getChannels}, cbk) => {
|
||||
try {
|
||||
const {channels} = getChannels;
|
||||
|
||||
return cbk(null, channelForGift({channels, to, tokens}).id);
|
||||
} catch (err) {
|
||||
const {message} = err;
|
||||
|
||||
switch (message) {
|
||||
case 'NoActiveChannelWithSpecifiedPeer':
|
||||
return cbk([400, 'SendingGiftRequiresActiveChannelWithPeer']);
|
||||
|
||||
case 'NoActiveChannelWithSufficientLocalBalance':
|
||||
return cbk([400, 'SendingGiftRequiresChannelWithSufficientBalance']);
|
||||
|
||||
case 'NoActiveChannelWithSufficientRemoteBalance':
|
||||
return cbk([400, 'SendingGiftRequiresChannelWithSomeRemoteBalance']);
|
||||
|
||||
case 'NoDirectChannelWithSpecifiedPeer':
|
||||
return cbk([400, 'SendingGiftRequiresDirectChannelWithPeer']);
|
||||
|
||||
default:
|
||||
return cbk([500, 'UnexpectedErrorDeterminingChannelForGift', {err}]);
|
||||
module.exports = ({lnd, to, tokens}, cbk) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return asyncAuto({
|
||||
// Check arguments
|
||||
validate: cbk => {
|
||||
if (!lnd) {
|
||||
return cbk([400, 'ExpectedLndToSendGiftWith']);
|
||||
}
|
||||
}
|
||||
}],
|
||||
|
||||
// Get channel policy info
|
||||
getChannel: ['lnd', 'peerChannel', ({lnd, peerChannel}, cbk) => {
|
||||
return getChannel({lnd, id: peerChannel}, cbk);
|
||||
}],
|
||||
|
||||
// Create invoice
|
||||
createInvoice: ['getChannel', 'lnd', ({lnd}, cbk) => {
|
||||
return createInvoice({lnd}, cbk);
|
||||
}],
|
||||
|
||||
// Get wallet
|
||||
getWallet: ['getChannel', 'lnd', ({lnd}, cbk) => {
|
||||
return getWalletInfo({lnd}, cbk);
|
||||
}],
|
||||
|
||||
// Route
|
||||
route: [
|
||||
'createInvoice',
|
||||
'getChannel',
|
||||
'getWallet',
|
||||
({createInvoice, getChannel, getWallet}, cbk) =>
|
||||
{
|
||||
try {
|
||||
const {route} = giftRoute({
|
||||
tokens,
|
||||
channel: getChannel,
|
||||
destination: getWallet.public_key,
|
||||
height: getWallet.current_block_height,
|
||||
});
|
||||
|
||||
return cbk(null, route);
|
||||
} catch (err) {
|
||||
const {message} = err;
|
||||
|
||||
switch (message) {
|
||||
case 'GiftAmountTooLowToSend':
|
||||
case 'OwnPolicyTooLowToCompleteForward':
|
||||
case 'PeerPolicyTooLowToCompleteForward':
|
||||
return cbk([400, 'AmountTooLowToCompleteGiftSend']);
|
||||
|
||||
default:
|
||||
return cbk([500, 'FailedToConstructGiftRoute', {err}]);
|
||||
if (!to) {
|
||||
return cbk([400, 'ExpectedPeerToSendGiftTo']);
|
||||
}
|
||||
}
|
||||
}],
|
||||
|
||||
// Send the gift
|
||||
pay: [
|
||||
'createInvoice',
|
||||
'lnd',
|
||||
'route',
|
||||
({createInvoice, lnd, route}, cbk) =>
|
||||
{
|
||||
const {id} = createInvoice;
|
||||
if (!tokens) {
|
||||
return cbk([400, 'ExpectedTokensToGiftToPeer']);
|
||||
}
|
||||
|
||||
return payViaRoutes({id, lnd, routes: [route]}, (err, res) => {
|
||||
if (!!err) {
|
||||
const [errCode, errMessage] = err;
|
||||
return cbk();
|
||||
},
|
||||
|
||||
switch (errMessage) {
|
||||
case 'RejectedUnacceptableFee':
|
||||
return cbk([400, 'GiftTokensAmountTooLowToSend']);
|
||||
// Get channels
|
||||
getChannels: ['validate', ({}, cbk) => getChannels({lnd}, cbk)],
|
||||
|
||||
// Peer channel
|
||||
peerChannel: ['getChannels', ({getChannels}, cbk) => {
|
||||
try {
|
||||
const {channels} = getChannels;
|
||||
|
||||
const {id} = channelForGift({channels, to, tokens});
|
||||
|
||||
return cbk(null, id);
|
||||
} catch (err) {
|
||||
return cbk(giftCallbackError({err}));
|
||||
}
|
||||
}],
|
||||
|
||||
// Get channel policy info
|
||||
getChannel: ['peerChannel', ({peerChannel}, cbk) => {
|
||||
return getChannel({lnd, id: peerChannel}, cbk);
|
||||
}],
|
||||
|
||||
// Create invoice
|
||||
createInvoice: ['getChannel', ({l}, cbk) => createInvoice({lnd}, cbk)],
|
||||
|
||||
// Get wallet
|
||||
getWallet: ['getChannel', ({}, cbk) => getWalletInfo({lnd}, cbk)],
|
||||
|
||||
// Route
|
||||
route: [
|
||||
'createInvoice',
|
||||
'getChannel',
|
||||
'getWallet',
|
||||
({createInvoice, getChannel, getWallet}, cbk) =>
|
||||
{
|
||||
try {
|
||||
const {route} = giftRoute({
|
||||
tokens,
|
||||
channel: getChannel,
|
||||
destination: getWallet.public_key,
|
||||
height: getWallet.current_block_height,
|
||||
});
|
||||
|
||||
return cbk(null, route);
|
||||
} catch (err) {
|
||||
const {message} = err;
|
||||
|
||||
switch (message) {
|
||||
case 'GiftAmountTooLowToSend':
|
||||
case 'OwnPolicyTooLowToCompleteForward':
|
||||
case 'PeerPolicyTooLowToCompleteForward':
|
||||
return cbk([400, 'AmountTooLowToCompleteGiftSend']);
|
||||
|
||||
default:
|
||||
return cbk([503, 'UnexpectedErrorSendingGiftTokens', {err}]);
|
||||
return cbk([500, 'FailedToConstructGiftRoute', {err}]);
|
||||
}
|
||||
}
|
||||
}],
|
||||
|
||||
return cbk(null, {gave_tokens: res.fee});
|
||||
});
|
||||
}],
|
||||
// Send the gift
|
||||
pay: ['createInvoice', 'route', ({createInvoice, route}, cbk) => {
|
||||
const {id} = createInvoice;
|
||||
|
||||
// Done paying
|
||||
paid: ['pay', ({pay}, cbk) => cbk(null, {gave_tokens: pay.gave_tokens})],
|
||||
},
|
||||
returnResult({of: 'paid'}, cbk));
|
||||
return payViaRoutes({id, lnd, routes: [route]}, (err, res) => {
|
||||
if (!!err) {
|
||||
const [errCode, errMessage] = err;
|
||||
|
||||
switch (errMessage) {
|
||||
case 'RejectedUnacceptableFee':
|
||||
return cbk([400, 'GiftTokensAmountTooLowToSend']);
|
||||
|
||||
default:
|
||||
return cbk([503, 'UnexpectedErrorSendingGiftTokens', {err}]);
|
||||
}
|
||||
}
|
||||
|
||||
return cbk(null, {gave_tokens: res.fee});
|
||||
});
|
||||
}],
|
||||
|
||||
// Done paying
|
||||
paid: ['pay', ({pay}, cbk) => cbk(null, {gave_tokens: pay.gave_tokens})],
|
||||
},
|
||||
returnResult({reject, resolve, of: 'paid'}, cbk));
|
||||
});
|
||||
};
|
||||
|
|
|
|||
8
package-lock.json
generated
8
package-lock.json
generated
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "balanceofsatoshis",
|
||||
"version": "5.6.1",
|
||||
"version": "5.6.2",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
|
@ -3333,9 +3333,9 @@
|
|||
}
|
||||
},
|
||||
"ln-service": {
|
||||
"version": "47.5.4",
|
||||
"resolved": "https://registry.npmjs.org/ln-service/-/ln-service-47.5.4.tgz",
|
||||
"integrity": "sha512-y8R+3gkEVG+RUXbPuWSU6C8+QZgT2Pvcs/xUAqIOc/bzTrlPz8S2kIy7K9JAXGRMBMqg7AmRC/InWTF/aOqAOg==",
|
||||
"version": "47.5.5",
|
||||
"resolved": "https://registry.npmjs.org/ln-service/-/ln-service-47.5.5.tgz",
|
||||
"integrity": "sha512-/x3nogCbOL5i0FZQc03BOs0xT/X8dJ+EDmBtWfI9Qt1yoZ1+174dASoZuaUSwiTIv6wnWKNyTDa05mMu6SlW/w==",
|
||||
"requires": {
|
||||
"@datastructures-js/priority-queue": "1.0.2",
|
||||
"@grpc/proto-loader": "0.5.3",
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
"ini": "1.3.5",
|
||||
"inquirer": "7.0.0",
|
||||
"ln-accounting": "3.1.6",
|
||||
"ln-service": "47.5.4",
|
||||
"ln-service": "47.5.5",
|
||||
"moment": "2.24.0",
|
||||
"qrcode-terminal": "0.12.0",
|
||||
"request": "2.88.0",
|
||||
|
|
@ -61,5 +61,5 @@
|
|||
"scripts": {
|
||||
"test": "tap test/arrays/*.js test/balances/*.js test/chain/*.js test/encryption/*.js test/fiat/*.js test/lnd/*.js test/network/*.js test/nodes/*.js test/responses/*.js test/routing/*.js test/swaps/*.js test/telegram/*.js"
|
||||
},
|
||||
"version": "5.6.1"
|
||||
"version": "5.6.2"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ const isRoutePayable = require('./is_route_payable');
|
|||
const accuracy = 1000;
|
||||
const {isArray} = Array;
|
||||
const from = 0;
|
||||
const slowPaymentMs = 1000 * 30;
|
||||
const to = tokens => tokens + Math.round(Math.random() * 1000);
|
||||
|
||||
/** Find max routable
|
||||
|
|
@ -91,7 +92,14 @@ module.exports = ({cltv, hops, lnd, logger, max}, cbk) => {
|
|||
|
||||
logger.info({evaluating_amount: cursor});
|
||||
|
||||
const slowWarning = setTimeout(() => {
|
||||
logger.info({slow_path_timeout_in: '30 seconds'});
|
||||
},
|
||||
slowPaymentMs);
|
||||
|
||||
return isRoutePayable({channels, cltv, lnd, tokens}, (err, res) => {
|
||||
clearTimeout(slowWarning);
|
||||
|
||||
if (!!err) {
|
||||
return cbk(err);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
const asyncAuto = require('async/auto');
|
||||
const asyncMap = require('async/map');
|
||||
const {getChannel} = require('ln-service');
|
||||
const {getChannels} = require('ln-service');
|
||||
const {getNode} = require('ln-service');
|
||||
const {getWalletInfo} = require('ln-service');
|
||||
|
|
@ -62,15 +64,32 @@ module.exports = ({destination, lnd, through, tokens}, cbk) => {
|
|||
return getNode({lnd, public_key: destination}, cbk);
|
||||
}],
|
||||
|
||||
// Local channels
|
||||
localChannels: ['getChannels', ({getChannels}, cbk) => {
|
||||
const localChannels = getChannels.channels.filter(n => {
|
||||
return n.partner_public_key === through && !!n.is_private;
|
||||
});
|
||||
|
||||
return asyncMap(localChannels, (channel, cbk) => {
|
||||
return getChannel({lnd, id: channel.id}, cbk);
|
||||
},
|
||||
cbk);
|
||||
}],
|
||||
|
||||
// Connecting path
|
||||
path: [
|
||||
'getChannels',
|
||||
'getInfo',
|
||||
'getNode',
|
||||
({getChannels, getInfo, getNode}, cbk) =>
|
||||
'localChannels',
|
||||
({getChannels, getInfo, getNode, localChannels}, cbk) =>
|
||||
{
|
||||
const connectingChannels = getNode.channels
|
||||
.filter(chan => !!chan.policies.find(n => n.public_key === through));
|
||||
const channels = [].concat(getNode.channels).concat(localChannels);
|
||||
|
||||
const connectingChannels = channels.filter(chan => {
|
||||
// Channel has a policy that matches the key of the through key
|
||||
return !!chan.policies.find(n => n.public_key === through);
|
||||
});
|
||||
|
||||
if (!connectingChannels.length) {
|
||||
return cbk([400, 'NoConnectingChannelToPayIn']);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
const asyncAuto = require('async/auto');
|
||||
const asyncTimeout = require('async/timeout');
|
||||
const {getWalletInfo} = require('ln-service');
|
||||
const {payViaRoutes} = require('ln-service');
|
||||
const {returnResult} = require('asyncjs-util');
|
||||
|
|
@ -8,6 +9,7 @@ const invalidPaymentMessage = 'UnknownPaymentHash';
|
|||
const {isArray} = Array;
|
||||
const mtokensFromTokens = tokens => (BigInt(tokens) * BigInt(1e3)).toString();
|
||||
const pathfindingTimeoutMs = 1000 * 60;
|
||||
const payWithTimeout = asyncTimeout(payViaRoutes, 1000 * 60);
|
||||
|
||||
/** Find out if route is payable
|
||||
|
||||
|
|
@ -76,12 +78,16 @@ module.exports = ({channels, cltv, lnd, tokens}, cbk) => {
|
|||
|
||||
// Attempt the route
|
||||
attempt: ['route', ({route}, cbk) => {
|
||||
return payViaRoutes({
|
||||
return payWithTimeout({
|
||||
lnd,
|
||||
pathfinding_timeout: pathfindingTimeoutMs,
|
||||
routes: [route],
|
||||
},
|
||||
err => {
|
||||
if (!!err && !isArray(err)) {
|
||||
return cbk(null, {is_payable: false});
|
||||
}
|
||||
|
||||
const [, code] = err;
|
||||
|
||||
return cbk(null, {is_payable: code === invalidPaymentMessage});
|
||||
|
|
|
|||
|
|
@ -215,7 +215,7 @@ module.exports = (args, cbk) => {
|
|||
|
||||
return probeDestination({
|
||||
destination: getPublicKey.public_key,
|
||||
find_max: 5e6,
|
||||
find_max: 4294967,
|
||||
ignore: [{from_public_key: getPublicKey.public_key}].concat(avoid),
|
||||
in_through: getInbound.public_key,
|
||||
logger: args.logger,
|
||||
|
|
|
|||
|
|
@ -68,21 +68,6 @@ module.exports = (args, cbk) => {
|
|||
return cbk();
|
||||
},
|
||||
|
||||
// Get channels
|
||||
getLiquidity: ['validate', ({}, cbk) => {
|
||||
// Exit early when recovering from an existing swap
|
||||
if (!!args.recovery) {
|
||||
return cbk();
|
||||
}
|
||||
|
||||
return getLiquidity({
|
||||
above: args.tokens,
|
||||
is_top: true,
|
||||
node: args.node,
|
||||
},
|
||||
cbk);
|
||||
}],
|
||||
|
||||
// Get authenticated lnd connection
|
||||
getLnd: ['validate', ({}, cbk) => {
|
||||
return authenticatedLnd({logger: args.logger, node: args.node}, cbk);
|
||||
|
|
@ -93,6 +78,21 @@ module.exports = (args, cbk) => {
|
|||
return getWalletInfo({lnd: getLnd.lnd}, cbk);
|
||||
}],
|
||||
|
||||
// Get channels
|
||||
getLiquidity: ['getLnd', ({getLnd}, cbk) => {
|
||||
// Exit early when recovering from an existing swap
|
||||
if (!!args.recovery) {
|
||||
return cbk();
|
||||
}
|
||||
|
||||
return getLiquidity({
|
||||
above: args.tokens,
|
||||
is_top: true,
|
||||
lnd: getLnd.lnd,
|
||||
},
|
||||
cbk);
|
||||
}],
|
||||
|
||||
// Get network
|
||||
getNetwork: ['getLnd', ({getLnd}, cbk) => {
|
||||
return getNetwork({lnd: getLnd.lnd}, cbk)
|
||||
|
|
|
|||
|
|
@ -830,6 +830,21 @@ module.exports = (args, cbk) => {
|
|||
return cbk();
|
||||
}],
|
||||
|
||||
// Register deposit height
|
||||
depositHeight: ['findDeposit', ({findDeposit}, cbk) => {
|
||||
if (!!args.recovery) {
|
||||
return cbk();
|
||||
}
|
||||
|
||||
return getWalletInfo({lnd: args.lnd}, (err, res) => {
|
||||
if (!!err) {
|
||||
return cbk(err);
|
||||
}
|
||||
|
||||
return cbk(null, res.current_block_height);
|
||||
});
|
||||
}],
|
||||
|
||||
// Claim details
|
||||
claim: [
|
||||
'findDeposit',
|
||||
|
|
@ -850,6 +865,7 @@ module.exports = (args, cbk) => {
|
|||
rawRecovery: [
|
||||
'claim',
|
||||
'createAddress',
|
||||
'depositHeight',
|
||||
'initiateSwap',
|
||||
'network',
|
||||
'recover',
|
||||
|
|
@ -857,6 +873,7 @@ module.exports = (args, cbk) => {
|
|||
({
|
||||
claim,
|
||||
createAddress,
|
||||
depositHeight,
|
||||
initiateSwap,
|
||||
network,
|
||||
recover,
|
||||
|
|
@ -888,7 +905,7 @@ module.exports = (args, cbk) => {
|
|||
min_fee_rate: minFeeRate,
|
||||
private_key: claim.private_key,
|
||||
secret: claim.secret,
|
||||
start_height: initiateSwap.start_height || startHeight,
|
||||
start_height: initiateSwap.start_height || depositHeight,
|
||||
sweep_address: createAddress.address,
|
||||
transaction_id: claim.transaction_id,
|
||||
transaction_vout: claim.transaction_vout,
|
||||
|
|
@ -918,6 +935,7 @@ module.exports = (args, cbk) => {
|
|||
sweep: [
|
||||
'claim',
|
||||
'createAddress',
|
||||
'depositHeight',
|
||||
'initiateSwap',
|
||||
'network',
|
||||
'rawRecovery',
|
||||
|
|
@ -926,6 +944,7 @@ module.exports = (args, cbk) => {
|
|||
({
|
||||
claim,
|
||||
createAddress,
|
||||
depositHeight,
|
||||
initiateSwap,
|
||||
network,
|
||||
recover,
|
||||
|
|
@ -959,7 +978,7 @@ module.exports = (args, cbk) => {
|
|||
max_fee_multiplier: maxFeeMultiplier,
|
||||
private_key: claim.private_key,
|
||||
secret: claim.secret,
|
||||
start_height: startHeight,
|
||||
start_height: depositHeight,
|
||||
sweep_address: createAddress.address,
|
||||
transaction_id: claim.transaction_id,
|
||||
transaction_vout: claim.transaction_vout,
|
||||
|
|
|
|||
41
test/arrays/test_shuffle.js
Normal file
41
test/arrays/test_shuffle.js
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
const {test} = require('tap');
|
||||
|
||||
const {shuffle} = require('./../../arrays');
|
||||
|
||||
const tests = [
|
||||
{
|
||||
args: {},
|
||||
description: 'An array is required',
|
||||
error: 'ExpectedArrayToShuffle',
|
||||
},
|
||||
{
|
||||
args: {array: []},
|
||||
description: 'An empty array returns an empty array',
|
||||
expected: {shuffled: ''},
|
||||
},
|
||||
{
|
||||
args: {array: [1, 2, 3]},
|
||||
description: 'An array is shuffled as expected',
|
||||
expected: {shuffled: '3,1,2'},
|
||||
},
|
||||
];
|
||||
|
||||
tests.forEach(({args, description, error, expected}) => {
|
||||
return test(description, ({deepIs, end, equal, throws}) => {
|
||||
if (!!error) {
|
||||
throws(() => shuffle(args), new Error(error), 'Got expected error');
|
||||
} else if (!expected.shuffled) {
|
||||
equal(shuffle(args).shuffled.join(''), '', 'Empty array is returned');
|
||||
} else {
|
||||
let shuffled = [];
|
||||
|
||||
while (shuffled.join(',') !== expected.shuffled) {
|
||||
shuffled = shuffle(args).shuffled;
|
||||
}
|
||||
|
||||
equal(shuffled.join(','), expected.shuffled, 'Array is shuffled');
|
||||
}
|
||||
|
||||
return end();
|
||||
});
|
||||
});
|
||||
115
test/balances/test_get_liquidity.js
Normal file
115
test/balances/test_get_liquidity.js
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
const {test} = require('tap');
|
||||
|
||||
const {getLiquidity} = require('./../../balances');
|
||||
|
||||
const makeChannels = () => {
|
||||
return [
|
||||
{
|
||||
active: true,
|
||||
capacity: '1',
|
||||
chan_id: '1',
|
||||
channel_point: '00:1',
|
||||
commit_fee: 1,
|
||||
commit_weight: 1,
|
||||
fee_per_kw: 1,
|
||||
local_balance: 1,
|
||||
local_chan_reserve_sat: 1,
|
||||
num_updates: 1,
|
||||
pending_htlcs: [],
|
||||
private: false,
|
||||
remote_balance: 1,
|
||||
remote_chan_reserve_sat: 1,
|
||||
remote_pubkey: 'b',
|
||||
total_satoshis_received: 1,
|
||||
total_satoshis_sent: 1,
|
||||
unsettled_balance: 1,
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
capacity: '1',
|
||||
chan_id: '1',
|
||||
channel_point: '00:1',
|
||||
commit_fee: 1,
|
||||
commit_weight: 1,
|
||||
fee_per_kw: 1,
|
||||
local_balance: 1,
|
||||
local_chan_reserve_sat: 1,
|
||||
num_updates: 1,
|
||||
pending_htlcs: [],
|
||||
private: false,
|
||||
remote_balance: 1,
|
||||
remote_chan_reserve_sat: 1,
|
||||
remote_pubkey: 'b',
|
||||
total_satoshis_received: 1,
|
||||
total_satoshis_sent: 1,
|
||||
unsettled_balance: 1,
|
||||
},
|
||||
{
|
||||
active: true,
|
||||
capacity: '1',
|
||||
chan_id: '1',
|
||||
channel_point: '00:1',
|
||||
commit_fee: 1,
|
||||
commit_weight: 1,
|
||||
fee_per_kw: 1,
|
||||
local_balance: 1,
|
||||
local_chan_reserve_sat: 1,
|
||||
num_updates: 1,
|
||||
pending_htlcs: [],
|
||||
private: false,
|
||||
remote_balance: 1,
|
||||
remote_chan_reserve_sat: 1,
|
||||
remote_pubkey: 'b',
|
||||
total_satoshis_received: 1,
|
||||
total_satoshis_sent: 1,
|
||||
unsettled_balance: 1,
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
const tests = [
|
||||
{
|
||||
args: {},
|
||||
description: 'LND is required',
|
||||
error: [400, 'ExpectedLndToGetLiquidity'],
|
||||
},
|
||||
{
|
||||
args: {
|
||||
is_top: true,
|
||||
lnd: {
|
||||
default: {
|
||||
listChannels: ({}, cbk) => cbk(null, {channels: makeChannels()}),
|
||||
},
|
||||
},
|
||||
},
|
||||
description: 'Liquidity is returned',
|
||||
expected: {balance: 1},
|
||||
},
|
||||
{
|
||||
args: {
|
||||
is_outbound: true,
|
||||
lnd: {
|
||||
default: {
|
||||
listChannels: ({}, cbk) => cbk(null, {channels: makeChannels()}),
|
||||
},
|
||||
},
|
||||
with: 'b',
|
||||
},
|
||||
description: 'Liquidity is returned',
|
||||
expected: {balance: 3},
|
||||
},
|
||||
];
|
||||
|
||||
tests.forEach(({args, description, error, expected}) => {
|
||||
return test(description, async ({end, equal, rejects}) => {
|
||||
if (!!error) {
|
||||
rejects(getLiquidity(args), error, 'Got expected error');
|
||||
} else {
|
||||
const {balance} = await getLiquidity(args);
|
||||
|
||||
equal(balance, expected.balance, 'Balance is calculated');
|
||||
}
|
||||
|
||||
return end();
|
||||
});
|
||||
});
|
||||
67
test/chain/test_get_mempool_size.js
Normal file
67
test/chain/test_get_mempool_size.js
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
const {test} = require('tap');
|
||||
|
||||
const {getMempoolSize} = require('./../../chain');
|
||||
|
||||
const tests = [
|
||||
{
|
||||
args: {},
|
||||
description: 'Network name is required',
|
||||
error: [400, 'ExpectedNetworkNameToGetMempoolSize'],
|
||||
},
|
||||
{
|
||||
args: {network: 'btc'},
|
||||
description: 'Request function is required',
|
||||
error: [400, 'ExpectedRequestMethodToGetMempoolSize'],
|
||||
},
|
||||
{
|
||||
args: {network: 'btc', request: ({}, cbk) => cbk('err'), retries: 1},
|
||||
description: 'Request errors are passed back',
|
||||
error: [503, 'FailedToGetMempoolSizeInfo', {err: 'err'}],
|
||||
},
|
||||
{
|
||||
args: {network: 'btc', request: ({}, cbk) => cbk(), retries: 1},
|
||||
description: 'Mempool information is expected',
|
||||
error: [503, 'ExpectedMempoolInfoInResponse'],
|
||||
},
|
||||
{
|
||||
args: {
|
||||
network: 'btc',
|
||||
request: ({}, cbk) => cbk(null, null, {}),
|
||||
retries: 1,
|
||||
},
|
||||
description: 'Mempool response vbytes are expected',
|
||||
error: [503, 'ExpectedMempoolVirtualByteSize'],
|
||||
},
|
||||
{
|
||||
args: {
|
||||
network: 'btctestnet',
|
||||
request: ({}, cbk) => cbk(null, null, {vsize: 1}),
|
||||
retries: 1,
|
||||
},
|
||||
description: 'VBytes are returned',
|
||||
expected: {vbytes: 1},
|
||||
},
|
||||
{
|
||||
args: {
|
||||
network: 'network',
|
||||
request: ({}, cbk) => cbk(),
|
||||
retries: 1,
|
||||
},
|
||||
description: 'No vbytes are returned',
|
||||
expected: {},
|
||||
},
|
||||
];
|
||||
|
||||
tests.forEach(({args, description, error, expected}) => {
|
||||
return test(description, async ({end, equal, rejects}) => {
|
||||
if (!!error) {
|
||||
rejects(getMempoolSize(args), error, 'Got expected error');
|
||||
} else {
|
||||
const {vbytes} = await getMempoolSize(args);
|
||||
|
||||
equal(vbytes, expected.vbytes, 'Got expected vbytes');
|
||||
}
|
||||
|
||||
return end();
|
||||
});
|
||||
});
|
||||
25
test/fixtures/chan_info_response.json
vendored
Normal file
25
test/fixtures/chan_info_response.json
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"capacity": "1",
|
||||
"chan_point": "1:1",
|
||||
"channel_id": "1",
|
||||
"node1_policy": {
|
||||
"disabled": false,
|
||||
"fee_base_msat": "1",
|
||||
"fee_rate_milli_msat": "1",
|
||||
"last_update": 1,
|
||||
"max_htlc_msat": "1",
|
||||
"min_htlc": "1",
|
||||
"time_lock_delta": 1
|
||||
},
|
||||
"node1_pub": "a",
|
||||
"node2_policy": {
|
||||
"disabled": false,
|
||||
"fee_base_msat": "1",
|
||||
"fee_rate_milli_msat": "1",
|
||||
"last_update": 1,
|
||||
"max_htlc_msat": "1",
|
||||
"min_htlc": "1",
|
||||
"time_lock_delta": 1
|
||||
},
|
||||
"node2_pub": "b"
|
||||
}
|
||||
15
test/fixtures/get_info_response.json
vendored
Normal file
15
test/fixtures/get_info_response.json
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"alias": "",
|
||||
"best_header_timestamp": 1,
|
||||
"block_hash": "00",
|
||||
"block_height": 1,
|
||||
"chains": [{"chain": "bitcoin", "network": "mainnet"}],
|
||||
"color": "#000000",
|
||||
"identity_pubkey": "00",
|
||||
"num_active_channels": 0,
|
||||
"num_peers": 0,
|
||||
"num_pending_channels": 0,
|
||||
"synced_to_chain": false,
|
||||
"uris": [],
|
||||
"version": ""
|
||||
}
|
||||
4
test/fixtures/index.js
vendored
Normal file
4
test/fixtures/index.js
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
const chanInfoResponse = require('./chan_info_response');
|
||||
const getInfoResponse = require('./get_info_response');
|
||||
|
||||
module.exports = {chanInfoResponse, getInfoResponse};
|
||||
47
test/network/test_gift_callback_error.js
Normal file
47
test/network/test_gift_callback_error.js
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
const {test} = require('tap');
|
||||
|
||||
const giftCallbackError = require('./../../network/gift_callback_error');
|
||||
|
||||
const tests = [
|
||||
{
|
||||
args: {err: {message: 'NoActiveChannelWithSpecifiedPeer'}},
|
||||
description: 'No active channel returns a user error',
|
||||
expected: {err: [400, 'SendingGiftRequiresActiveChannelWithPeer']},
|
||||
},
|
||||
{
|
||||
args: {err: {message: 'NoActiveChannelWithSufficientLocalBalance'}},
|
||||
description: 'No active channel with balance returns a user error',
|
||||
expected: {err: [400, 'SendingGiftRequiresChanWithSufficientBalance']},
|
||||
},
|
||||
{
|
||||
args: {err: {message: 'NoActiveChannelWithSufficientRemoteBalance'}},
|
||||
description: 'No channel with remote balance returns a user error',
|
||||
expected: {err: [400, 'SendingGiftRequiresChanWithSomeRemoteBalance']},
|
||||
},
|
||||
{
|
||||
args: {err: {message: 'NoDirectChannelWithSpecifiedPeer'}},
|
||||
description: 'No direct channel with peer returns a user error',
|
||||
expected: {err: [400, 'SendingGiftRequiresDirectChannelWithPeer']},
|
||||
},
|
||||
{
|
||||
args: {err: {message: 'message'}},
|
||||
description: 'An unanticipated error returns a non-user error',
|
||||
expected: {
|
||||
err: [
|
||||
500,
|
||||
'UnexpectedErrorDeterminingChanForGift',
|
||||
{err: {message: 'message'}},
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
tests.forEach(({args, description, expected}) => {
|
||||
return test(description, ({deepIs, end}) => {
|
||||
const err = giftCallbackError(args);
|
||||
|
||||
deepIs(err, expected.err, 'Got expected error');
|
||||
|
||||
return end();
|
||||
});
|
||||
});
|
||||
145
test/network/test_send_gift.js
Normal file
145
test/network/test_send_gift.js
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
const {test} = require('tap');
|
||||
|
||||
const {chanInfoResponse} = require('./../fixtures');
|
||||
const {getInfoResponse} = require('./../fixtures');
|
||||
const {sendGift} = require('./../../network');
|
||||
|
||||
const tests = [
|
||||
{
|
||||
args: {},
|
||||
description: 'LND is required to send a gift',
|
||||
error: [400, 'ExpectedLndToSendGiftWith'],
|
||||
},
|
||||
{
|
||||
args: {lnd: {}},
|
||||
description: 'Peer public key is required to send a gift',
|
||||
error: [400, 'ExpectedPeerToSendGiftTo'],
|
||||
},
|
||||
{
|
||||
args: {lnd: {}, to: 'b'},
|
||||
description: 'Tokens are required to send a gift',
|
||||
error: [400, 'ExpectedTokensToGiftToPeer'],
|
||||
},
|
||||
{
|
||||
args: {
|
||||
lnd: {
|
||||
default: {
|
||||
listChannels: ({}, cbk) => cbk(null, {channels: []})
|
||||
},
|
||||
},
|
||||
to: 'b',
|
||||
tokens: 1,
|
||||
},
|
||||
description: 'A channel is required to send a gift',
|
||||
error: [400, 'SendingGiftRequiresDirectChannelWithPeer'],
|
||||
},
|
||||
{
|
||||
args: {
|
||||
lnd: {
|
||||
default: {
|
||||
addInvoice: ({}, cbk) => cbk(null, {
|
||||
payment_request: 'payment_request',
|
||||
r_hash: Buffer.alloc(32),
|
||||
}),
|
||||
getChanInfo: (args, cbk) => cbk(null, chanInfoResponse),
|
||||
getInfo: ({}, cbk) => cbk(null, getInfoResponse),
|
||||
listChannels: ({}, cbk) => cbk(null, {
|
||||
channels: [{
|
||||
active: true,
|
||||
capacity: 1,
|
||||
chan_id: 1,
|
||||
channel_point: '1:0',
|
||||
commit_fee: 1,
|
||||
commit_weight: 1,
|
||||
fee_per_kw: 1,
|
||||
local_balance: 1,
|
||||
local_chan_reserve_sat: '1',
|
||||
num_updates: 1,
|
||||
pending_htlcs: [],
|
||||
private: true,
|
||||
remote_balance: 1,
|
||||
remote_chan_reserve_sat: '1',
|
||||
remote_pubkey: 'b',
|
||||
total_satoshis_received: 1,
|
||||
total_satoshis_sent: 1,
|
||||
unsettled_balance: 1,
|
||||
}],
|
||||
}),
|
||||
},
|
||||
},
|
||||
to: 'b',
|
||||
tokens: 1,
|
||||
},
|
||||
description: 'A channel with balance is required to send a gift',
|
||||
error: [400, 'SendingGiftRequiresChanWithSufficientBalance'],
|
||||
},
|
||||
{
|
||||
args: {
|
||||
lnd: {
|
||||
default: {
|
||||
addInvoice: ({}, cbk) => cbk(null, {
|
||||
payment_request: 'payment_request',
|
||||
r_hash: Buffer.alloc(32),
|
||||
}),
|
||||
getChanInfo: (args, cbk) => cbk(null, chanInfoResponse),
|
||||
getInfo: ({}, cbk) => cbk(null, getInfoResponse),
|
||||
listChannels: ({}, cbk) => cbk(null, {
|
||||
channels: [{
|
||||
active: true,
|
||||
capacity: 1,
|
||||
chan_id: 1,
|
||||
channel_point: '1:0',
|
||||
commit_fee: 1,
|
||||
commit_weight: 1,
|
||||
fee_per_kw: 1,
|
||||
local_balance: 100,
|
||||
local_chan_reserve_sat: '1',
|
||||
num_updates: 1,
|
||||
pending_htlcs: [],
|
||||
private: true,
|
||||
remote_balance: 1,
|
||||
remote_chan_reserve_sat: '1',
|
||||
remote_pubkey: 'b',
|
||||
total_satoshis_received: 1,
|
||||
total_satoshis_sent: 1,
|
||||
unsettled_balance: 1,
|
||||
}],
|
||||
}),
|
||||
lookupInvoice: ({}, cbk) => cbk(null, {
|
||||
creation_date: 1,
|
||||
description_hash: '',
|
||||
expiry: 1,
|
||||
htlcs: [],
|
||||
memo: '',
|
||||
payment_request: 'payment_request',
|
||||
r_preimage: Buffer.alloc(32),
|
||||
settled: false,
|
||||
value: '1',
|
||||
}),
|
||||
},
|
||||
},
|
||||
to: 'b',
|
||||
tokens: 1,
|
||||
},
|
||||
description: 'A channel with balance is required to send a gift',
|
||||
error: [
|
||||
500,
|
||||
'FailedToConstructGiftRoute',
|
||||
{err: new Error('ExpectedDestinationPolicyToCalculateGiftRoute')},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
tests.forEach(({args, description, error, expected}) => {
|
||||
return test(description, async ({deepIs, end, equal, rejects}) => {
|
||||
if (!!error) {
|
||||
rejects(sendGift(args), error, 'Got expected error');
|
||||
} else {
|
||||
const sent = await sendGift(args);
|
||||
|
||||
equal(sent.gave_tokens, expected.gave_tokens, 'Sent expected tokens');
|
||||
}
|
||||
|
||||
return end();
|
||||
});
|
||||
});
|
||||
|
|
@ -45,16 +45,18 @@ module.exports = ({node, style}, cbk) => {
|
|||
// Get authenticated lnd connection
|
||||
getLnd: cbk => authenticatedLnd({node}, cbk),
|
||||
|
||||
// Get forwards
|
||||
getForwards: cbk => getForwards({node}, cbk),
|
||||
// Get exchange rate
|
||||
getRate: cbk => getExchangeRates({symbols: ['USD']}, cbk),
|
||||
|
||||
// Get balance
|
||||
getBalance: ['getLnd', ({getLnd}, cbk) => {
|
||||
return getBalance({node, lnd: getLnd.lnd}, cbk);
|
||||
}],
|
||||
|
||||
// Get exchange rate
|
||||
getRate: cbk => getExchangeRates({symbols: ['USD']}, cbk),
|
||||
// Get forwards
|
||||
getForwards: ['getLnd', ({getLnd}, cbk) => {
|
||||
return getForwards({lnd: getLnd.lnd}, cbk);
|
||||
}],
|
||||
|
||||
// Get autopilot status
|
||||
getAutopilot: ['getLnd', ({getLnd}, cbk) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue