balanceofsatoshis/bos
2019-12-07 20:13:28 -08:00

834 lines
32 KiB
JavaScript
Executable file

#!/usr/bin/env node
const {lstat} = require('fs');
const {mkdir} = require('fs');
const {readdir} = require('fs');
const {readFile} = require('fs');
const {rmdir} = require('fs');
const {spawn} = require('child_process');
const {unlink} = require('fs');
const {writeFile} = require('fs');
const inquirer = require('inquirer');
const prog = require('caporal');
const {rateProviders} = require('ln-accounting');
const request = require('request');
const {accountingCategories} = require('./balances');
const {adjustSavedNodes} = require('./nodes');
const {authenticatedLnd} = require('./lnd');
const {exchanges} = require('./fiat');
const {findRecord} = require('./lnd');
const {fundDev} = require('./wallets');
const {getAccountingReport} = require('./balances');
const {getBalance} = require('./balances');
const {getCertValidityDays} = require('./lnd');
const {getChainFees} = require('./chain');
const {getChannelCloses} = require('./chain');
const {getCredentials} = require('./lnd');
const {getDepositAddress} = require('./chain');
const {getExchangeRates} = require('./fiat');
const {getFeesChart} = require('./routing');
const {getForwards} = require('./network');
const {getLiquidity} = require('./balances');
const {getPeers} = require('./network');
const {getPriceChart} = require('./fiat');
const {getReport} = require('./wallets');
const {getSwapCost} = require('./swaps');
const {getSwapService} = require('./swaps');
const {getUtxos} = require('./chain');
const {ignoreFromAvoid} = require('./routing');
const marketPairs = require('./fiat').pairs;
const {openChannel} = require('./network');
const {probeDestination} = require('./network');
const {rebalance} = require('./swaps');
const {returnChart} = require('./responses');
const {returnNumber} = require('./responses');
const {returnObject} = require('./responses');
const {returnOutput} = require('./responses');
const {sendGift} = require('./network');
const {setAutopilot} = require('./network');
const {splitUtxos} = require('./chain');
const {startTelegramBot} = require('./telegram');
const {swapIn} = require('./swaps');
const {swapOut} = require('./swaps');
const {swapTypes} = require('./swaps');
const {unlockWallet} = require('./wallets');
const {version} = require('./package');
const {exit} = process;
const flatten = arr => [].concat(...arr);
const {FLOAT} = prog;
const {INT} = prog;
const {isArray} = Array;
const {keys} = Object;
const {REPEATABLE} = prog;
prog
.version(version)
// Get accounting information
.command('accounting', 'Get an accounting rundown')
.argument('<category>', 'Report category', keys(accountingCategories))
.help(`Categories: ${keys(accountingCategories).join(', ')}`)
.help(`Rate providers: ${rateProviders.join(', ')}`)
.option('--csv', 'Output a CSV')
.option('--month <month>', 'Show only records for specific month')
.option('--node <node_name>', 'Get details from named node')
.option('--rate-provider <rate_provider>', 'Rate provider', rateProviders)
.option('--year <year>', 'Show only records for specified year')
.action((args, options, logger) => {
const table = !!options.csv ? null : 'rows';
return new Promise(async (resolve, reject) => {
try {
return getAccountingReport({
category: args.category,
is_csv: !!options.csv,
lnd: (await authenticatedLnd({logger, node: options.node})).lnd,
month: options.month,
node: options.node,
rate_provider: options.rateProvider,
year: options.year,
},
returnObject({logger, reject, resolve, table}));
} catch (err) {
return reject(err);
}
});
})
// Direct autopilot to mirror one or more nodes on the network
.command('autopilot', 'Enable autopilot')
.argument('<status>', 'Status of autopilot', ['off', 'on'])
.help('Autopilot status is either on or off')
.help('Mirroring and urls require lnd --autopilot.heuristic=externalscore:1')
.option('--dryrun', 'Show scoring without changing autopilot settings')
.option('--mirror <pubkey>', 'Mirror channels of node', REPEATABLE)
.option('--no-color', 'Mute all colors')
.option('--node <node_name>', 'Set autopilot on saved node')
.option('--url <url>', 'Follow nodes from a scoring URL', REPEATABLE)
.action((args, options, logger) => {
return new Promise(async (resolve, reject) => {
try {
return setAutopilot({
request,
is_dry_run: !!options.dryrun,
is_enabled: args.status === 'on',
lnd: (await authenticatedLnd({logger, node: options.node})).lnd,
mirrors: flatten([options.mirror].filter(n => !!n)),
node: options.node,
urls: flatten([options.url].filter(n => !!n)),
},
returnObject({logger, reject, resolve}));
} catch (err) {
return reject(err);
}
});
})
// Get local balance information
.command('balance', 'Get total tokens')
.help('Sums balances on-chain, in channels, and pending, minus commit fees')
.option('--above <tokens>', 'Return tokens above watermark', INT)
.option('--below <tokens>', 'Return tokens below watermark', INT)
.option('--confirmed', 'Return confirmed funds only')
.option('--node <node_name>', 'Node to get balance for')
.option('--offchain', 'List only off-chain tokens')
.option('--onchain', 'List only on-chain tokens')
.action((args, options, logger) => {
return new Promise(async (resolve, reject) => {
try {
return getBalance({
above: options.above,
below: options.below,
is_confirmed: !!options.confirmed,
is_offchain_only: !!options.offchain,
is_onchain_only: !!options.onchain,
lnd: (await authenticatedLnd({logger, node: options.node})).lnd,
node: options.node,
},
returnNumber({logger, reject, resolve, number: 'balance'}));
} catch (err) {
return reject(err);
}
});
})
// Get the number of days until the RPC cert expires
.command('cert-validity-days', 'Number of days until the cert is invalid')
.option('--below <number_of_days>', 'Return number of days below mark', INT)
.option('--node <node_name>', 'Node to check cert on')
.action((args, options, logger) => {
return new Promise(async (resolve, reject) => {
return getCertValidityDays({
logger,
below: options.below,
node: options.node,
},
returnNumber({logger, reject, resolve, number: 'days'}));
});
})
// Deposit coins
.command('chain-deposit', 'Deposit coins in the on-chain wallet')
.argument('[amount]', 'Amount to receive', INT)
.option('--node <node_name>', 'Node to deposit coins to')
.action((args, options, logger) => {
return new Promise(async (resolve, reject) => {
try {
return getDepositAddress({
lnd: (await authenticatedLnd({logger, node: options.node})).lnd,
tokens: args.amount,
},
returnObject({logger, reject, resolve}));
} catch (err) {
return reject(err);
}
});
})
// Create on-chain submarine swap
.command('chain-receive', 'Receive funds on-chain via submarine swap')
.argument('[amount]', 'Amount to receive', INT, 1e6)
.option('--max-fee <max_fee>', 'Max fee in tokens to pay', INT, 4000)
.option('--no-color', 'Mute all colors')
.option('--node <node_name>', 'Node to receive funds on')
.option('--recovery <refund_recovery>', 'Attempt refund of swap')
.option('--refund-address <refund_address>', 'Refund address for swap')
.option('--test-refund', 'Reduce refund timeout height to test refund')
.action((args, options, logger) => {
return new Promise((resolve, reject) => {
return swapIn({
logger,
is_refund_test: options.testRefund,
max_fee: options.maxFee,
node: options.node,
recovery: options.recovery,
refund_address: options.refundAddress,
tokens: args.amount,
},
returnObject({exit, logger, reject, resolve}));
});
})
// Get the current chain fee rates
.command('chainfees', 'Get the current chain fee estimates')
.help('Lookup chain fee estimates at various confirm targets')
.option('--blocks <depth>', 'Blocks confirm target depth to estimate to')
.option('--file <path>', 'Write the output to a JSON file at desired path')
.option('--no-color', 'Mute all colors')
.option('--node <node_name>', 'Node to get chain fees view from')
.action((args, options, logger) => {
return new Promise(async (resolve, reject) => {
try {
return getChainFees({
blocks: options.blocks,
lnd: (await authenticatedLnd({logger, node: options.node})).lnd,
},
returnObject({
logger,
reject,
resolve,
file: options.file,
write: writeFile,
}));
} catch (err) {
return reject(err);
}
});
})
// Show a chart of fees earned
.command('chart-fees-earned', 'Get a chart of earned routing fees')
.argument('[via_peer]', 'Routing fees earned via a specified peer')
.help('Show the routing fees earned')
.option('--days <days>', 'Chart fees over the past number of days', INT, 60)
.option('--node <node_name>', 'Get fee earnings chart for saved node')
.action((args, options, logger) => {
return new Promise(async (resolve, reject) => {
try {
return getFeesChart({
days: options.days,
lnd: (await authenticatedLnd({logger, node: options.node})).lnd,
via: args.viaPeer,
},
returnChart({logger, reject, resolve, data: 'fees'}));
} catch (err) {
return reject(err);
}
});
})
// Determine the outcomes of channel closings
.command('closed', 'Get the status of a channel closings')
.help('Channel closes with chain-transaction derived resolution details')
.option('--limit [limit]', 'Limit of closings to get', INT, 20)
.option('--no-color', 'Mute all colors')
.option('--node <node_name>', 'Get channel closes from saved node')
.action((args, options, logger) => {
return new Promise(async (resolve, reject) => {
try {
return getChannelCloses({
limit: options.limit,
lnd: (await authenticatedLnd({logger, node: options.node})).lnd,
},
returnObject({logger, reject, resolve}));
} catch (err) {
return reject(err);
}
});
})
// 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}));
});
})
// Fan out utxos
.command('fanout', 'Fan out utxos')
.argument('<size>', 'UTXO minimum size', INT)
.argument('<count>', 'Desired number of total utxos', INT)
.help('Make a bunch of utxos by making a tx with a bunch of outputs')
.option('--confirmed', 'Only consider confirmed existing utxos')
.option('--dryrun', 'Execute a fan-out dry run')
.option('--feerate <feerate>', 'Feerate in per vbyte rate', INT)
.option('--no-color', 'Mute all colors')
.option('--node <node_name>', 'Node to do fan out for')
.action((args, options, logger) => {
return new Promise((resolve, reject) => {
return splitUtxos({
count: args.count,
is_confirmed: !!options.confirmed,
is_dry_run: !!options.dryrun,
node: options.node,
size: args.size,
tokens_per_vbyte: options.feerate,
},
returnObject({logger, reject, resolve}));
});
})
// Query the node to search for something
.command('find', 'Find a record')
.help('Look for something in the node db that matches a query')
.argument('<query>', 'Query for a record')
.option('--no-color', 'Mute all colors')
.option('--node <node_name>', 'Node to find record on')
.action((args, options, logger) => {
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
.command('forwards', 'Get forwards')
.help('Peers where routing has taken place from inbound and outbound sides')
.option('--days <days>', 'Number of past days to evaluate', INT)
.option('--no-color', 'Mute all colors')
.option('--node <node_name>', 'Node to get forwards for')
.action((args, options, logger) => {
return new Promise(async (resolve, reject) => {
try {
return getForwards({
days: options.days,
lnd: (await authenticatedLnd({logger, node: options.node})).lnd,
},
returnObject({logger, reject, resolve}));
} catch (err) {
return reject(err);
}
});
})
// Help with funding
.command('fund-dev', 'Fund people related to Bitcoin and Lightning efforts')
.help('Send some sats (805 default) to a worthy individual. For the cause!')
.help('Someone will be automatically chosen if no @handle specified')
.help('Nominate worthiness via GitHub issues, include proof of your support')
.option('--amount <amount>', 'Choose amount to send', INT, 805)
.option('--dryrun', 'Show what would happen without actually sending funds')
.option('--node <node_name>', 'Saved node to give funds from')
.option('--twitter-handle <nick>', 'Specifically support someone')
.action(async (args, options, logger) => {
return new Promise(async (resolve, reject) => {
try {
return fundDev({
logger,
request,
is_dry_run: options.dryrun,
lnd: (await authenticatedLnd({logger, node: options.node})).lnd,
to_twitter_account: options.twitterHandle,
tokens: options.amount,
},
returnObject({logger, reject, resolve}));
} catch (err) {
return reject(err);
}
});
})
// Give a peer some tokens
.command('gift', 'Give a direct peer some free funds off-chain')
.help('Send some funds to a connected peer')
.argument('<target>', 'Peer to give funds to')
.argument('<amount>', 'Tokens to give', INT)
.option('--node <node_name>', 'Source node to use to pay gift')
.action((args, options, logger) => {
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
.command('inbound-liquidity', 'Get inbound liquidity size')
.option('--above <tokens>', 'Return amount above watermark', INT)
.option('--below <tokens>', 'Return amount above watermark', INT)
.option('--node <node_name>', 'Node to get inbound liquidity')
.option('--top', 'Top percentile inbound liquidity in an individual channel')
.option('--with', 'Liquidity with a specific node')
.action((args, options, logger) => {
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
.command('increase-inbound-liquidity', 'Increase node inbound liquidity')
.help('Spend down a channel to get inbound. Fee is an estimate, may be more')
.option('--address <out_address>', 'Out chain address to send funds out to')
.option('--avoid <pubkey>', 'Avoid forwarding through node', REPEATABLE)
.option('--amount <amount>', 'Amount to increase liquidity', INT, 5e5)
.option('--confs <confs>', 'Confs to consider reorg safe', INT, 1)
.option('--dryrun', 'Only show cost estimate for increase')
.option('--fast', 'Request swap server avoid batching delay')
.option('--max-fee <max_fee>', 'Maximum estimated fee tokens', INT, 3000)
.option('--max-hours <max_hours>', 'Maximum hours to wait', INT, 24)
.option('--no-color', 'Mute all colors')
.option('--node <node_name>', 'Increase inbound liquidity on saved node')
.option('--recovery <recovery>', 'Recover in-progress swap')
.option('--show-raw-recovery', 'Show raw recovery transactions')
.option('--with <peer>', 'Public key of peer to increase liquidity from')
.action((args, options, logger) => {
return new Promise(async (resolve, reject) => {
try {
return swapOut({
logger,
avoid: flatten([options.avoid].filter(n => !!n)),
confs: options.confs,
is_fast: options.fast || false,
is_raw_recovery_shown: options.showRawRecovery || undefined,
is_dry_run: options.dryrun || false,
lnd: (await authenticatedLnd({logger, node: options.node})).lnd,
max_fee: options.maxFee,
max_wait_blocks: Math.ceil((options.maxHours) * 60 / 10),
node: options.node || undefined,
out_address: options.address || undefined,
peer: options.with || undefined,
recovery: options.recovery,
timeout: 1000 * 60 * 60 * 10,
tokens: options.amount,
},
returnObject({exit, logger, reject, resolve}));
} catch (err) {
return reject(err);
}
});
})
// Increase outbound liquidity
.command('increase-outbound-liquidity')
.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)', 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) => {
return new Promise(async (resolve, reject) => {
try {
return openChannel({
logger,
request,
chain_fee_rate: options.feeRate || undefined,
is_dry_run: options.dryrun,
lnd: (await authenticatedLnd({logger, node: options.node})).lnd,
peer: options.with || undefined,
tokens: options.amount,
},
returnObject({logger, reject, resolve}));
} catch (err) {
return reject(err);
}
});
})
// Get the price for liquidity
.command('liquidity-cost', 'Get the price of liquidity')
.argument('<type>', 'Liquidity direction', swapTypes)
.argument('<amount>', 'Amount of liquidity to get quote for', INT)
.option('--above <tokens>', 'Return amount above watermark', INT)
.option('--node <node_name>', 'Node to get liquidity cost')
.action((args, options, logger) => {
return new Promise(async (resolve, reject) => {
return getSwapCost({
above: options.above,
service: (await getSwapService({logger, node: options.node})).service,
tokens: args.amount,
type: args.type,
},
returnNumber({logger, reject, resolve, number: 'cost'}));
});
})
// Get the history of prices
.command('market', 'Get the history of prices on a market')
.help('A chart of historic prices. Note: SuredBits price history has a fee!')
.help(`Pairs: ${Array.from(new Set(marketPairs.map(n => n.toUpperCase())))}`)
.help(`Markets: ${Array.from(new Set(exchanges.map(n => n.toLowerCase())))}`)
.argument('[pair]', 'Market pair', marketPairs, 'BTCUSD')
.argument('[exchange]', 'Exchange', exchanges, 'kraken')
.option('--max-fee <max_fee>', 'Maximum amount to pay for data', INT, 5)
.option('--node <node_name>', 'Node to use to pay for price data')
.action((args, options, logger) => {
return new Promise((resolve, reject) => {
return getPriceChart({
exchange: args.exchange.toLowerCase(),
fee: options.maxFee,
node: options.node,
pair: args.pair.toLowerCase(),
},
returnChart({logger, reject, resolve, data: 'prices'}));
});
})
// Saved nodes
.command('nodes', 'List and edit saved nodes')
.help('Locking and unlocking requires existing installation of GPG')
.argument('[node]', 'Specify a saved node')
.option('--add', 'Add a new node')
.option('--lock <id>', 'Encrypt node authentication to GPG key>', REPEATABLE)
.option('--remove', 'Remove saved node')
.option('--unlock', 'Remove encryption from auth macaroon')
.action((args, options, logger) => {
return new Promise((resolve, reject) => {
return adjustSavedNodes({
logger,
spawn,
ask: (n, cbk) => inquirer.prompt([n]).then(res => cbk(res)),
fs: {
writeFile,
getDirectoryFiles: readdir,
getFile: readFile,
getFileStatus: lstat,
makeDirectory: mkdir,
removeDirectory: rmdir,
removeFile: unlink,
},
is_registering: options.add || undefined,
is_removing: options.remove || undefined,
is_unlocking: options.unlock || undefined,
lock_credentials_to: flatten([options.lock].filter(n => !!n)),
node: args.node || undefined,
},
returnObject({logger, reject, resolve}));
});
})
// Get outbound liquidity information: available outbound off-chain tokens
.command('outbound-liquidity', 'Get outbound liquidity size')
.option('--above <tokens>', 'Return amount above watermark', INT)
.option('--below <tokens>', 'Return amount above watermark', INT)
.option('--node <node_name>', 'Node to get outbound liquidity amount')
.option('--top', 'Top percentile inbound liquidity in an individual channel')
.option('--with', 'Liquidity with a specific node')
.action((args, options, logger) => {
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
.command('pay', 'Pay a payment request, probing first')
.argument('<request>', 'Payment Request')
.option('--avoid <pubkey>', 'Avoid forwarding through node', REPEATABLE)
.option('--in <public_key>', 'Route through specific peer of destination')
.option('--max-fee <max_fee>', 'Maximum fee to pay', INT, 1337)
.option('--no-color', 'Mute all colors')
.option('--node <node_name>', 'Node to use to pay payment request')
.option('--out <public_key>', 'Make first hop through specific peer')
.action((args, options, logger) => {
return new Promise(async (resolve, reject) => {
try {
return probeDestination({
logger,
ignore: ignoreFromAvoid({avoid: options.avoid}).ignore,
in_through: options.in,
is_real_payment: true,
lnd: (await authenticatedLnd({logger, node: options.node})).lnd,
max_fee: options.maxFee,
node: options.node,
out_through: options.out,
request: args.request,
},
returnObject({logger, reject, resolve}));
} catch (err) {
return reject(err);
}
});
})
// Get a list of channel-connected peers
.command('peers', 'Get a list of channel-connected peers')
.option('--active', 'Only active peer channels')
.option('--fee-days <past_days>', 'Include fees earned over n days', INT)
.option('--inbound-below <amount>', 'Inbound liquidity below amount')
.option('--node <node_name>', 'Node to get peers for')
.option('--offline', 'Only offline peer channels')
.option('--outbound-below <amount>', 'Outbound liquidity below amount', INT)
.option('--no-color', 'Mute all colors')
.option('--private', 'Only private channels')
.option('--public', 'Only peers with public channels')
.option('--sort', 'Sort results by peer attribute')
.action((args, options, logger) => {
return new Promise(async (resolve, reject) => {
try {
return getPeers({
earnings_days: options.feeDays,
inbound_liquidity_below: options.inboundBelow,
is_active: !!options.active,
is_offline: !!options.offline,
is_private: !!options.private,
is_public: !!options.public,
lnd: (await authenticatedLnd({logger, node: options.node})).lnd,
node: options.node,
outbound_liquidity_below: options.outboundBelow,
sort_by: options.sort,
},
returnObject({logger, reject, resolve}));
} catch (err) {
return reject(err);
}
});
})
// Get the current fiat exchange price
.command('price', 'Get the price')
.argument('[symbols...]', 'Desired fiat tickers')
.option('--file <path>', 'Write the output to a JSON file at desired path')
.option('--no-color', 'Mute all colors')
.action((args, options, logger) => {
return new Promise((resolve, reject) => {
return getExchangeRates({
symbols: args.symbols.map(n => n.toUpperCase()),
},
returnObject({
logger,
reject,
resolve,
file: options.file,
write: writeFile,
}));
});
})
// Determine if a payment request is sendable
.command('probe', 'Check if a payment request is sendable')
.help('Simulate paying a payment request without actually paying it')
.argument('<to>', 'Payment request or node public key')
.argument('[amount]', 'Amount to probe, default: request amount', INT)
.option('--avoid <pubkey>', 'Avoid forwarding through node', REPEATABLE)
.option('--find-max', 'Find the maximum routeable amount on success route')
.option('--in <public_key>', 'Route through specific peer of destination')
.option('--no-color', 'Mute all colors')
.option('--node <node_name>', 'Node to use for payment request check')
.option('--out <public_key>', 'Make first hop through specific peer')
.action((args, options, logger) => {
return new Promise(async (resolve, reject) => {
try {
return probeDestination({
logger,
destination: args.to.length === 66 ? args.to : undefined,
find_max: !!options.findMax ? 1e7 : undefined,
ignore: ignoreFromAvoid({avoid: options.avoid}).ignore,
in_through: options.in || undefined,
lnd: (await authenticatedLnd({logger, node: options.node})).lnd,
node: options.node || undefined,
out_through: options.out || undefined,
request: args.to.length !== 66 ? args.to : undefined,
tokens: args.amount || undefined,
},
returnObject({logger, reject, resolve}));
} catch (err) {
return reject(err);
}
});
})
// Rebalance between peers
.command('rebalance', 'Rebalance funds between peers')
.help('Change the liquidity profile of two peers')
.option('--avoid <pubkey>', 'Avoid forwarding through node', REPEATABLE)
.option('--in <public_key>', 'Route through specific peer of destination')
.option('--max-fee <max_fee>', 'Maximum fee to pay', INT)
.option('--max-fee-rate <max_fee_rate>', 'Max fee rate to pay', INT)
.option('--no-color', 'Mute all colors')
.option('--node <node_name>', 'Node to use for payment request check')
.option('--out <public_key>', 'Make first hop through specific peer')
.action((args, options, logger) => {
return new Promise(async (resolve, reject) => {
try {
return rebalance({
logger,
avoid: flatten([options.avoid].filter(n => !!n)),
in_through: options.in || undefined,
lnd: (await authenticatedLnd({logger, node: options.node})).lnd,
max_fee: options.maxFee,
max_fee_rate: options.maxFeeRate,
node: options.node || undefined,
out_through: options.out || undefined,
},
returnObject({exit, logger, reject, resolve}));
} catch (err) {
return reject(err);
}
});
})
// See a general report about the node
.command('report', 'Report about the node')
.option('--node <node_name>', 'Node to get report for')
.option('--styled', 'Add unicode styling to report')
.action((args, options, logger) => {
return new Promise((resolve, reject) => {
return getReport({
node: options.node,
style: !!options.styled ? 'styled' : undefined,
},
returnOutput({logger, reject, resolve}));
});
})
// Link up Telegram bot
.command('telegram', 'Post updates to a Telegram bot')
.help('Connect to a Telegram bot. Create bot: tg://resolve?domain=botfather')
.help('Supported updates: forwards, received payments')
.option('--budget <amount>', 'Spending amount to allow', INT, 0)
.option('--connect <connect_code>', 'Connection code', INT)
.option('--node <node_name>', 'Node to connect to Telegram', REPEATABLE)
.action((args, options, logger) => {
return new Promise(async (resolve, reject) => {
try {
const nodes = flatten([options.node].filter(n => !!n)).map(node => {
return authenticatedLnd({logger, node});
});
return startTelegramBot({
logger,
request,
fs: {writeFile, getFile: readFile, makeDirectory: mkdir},
id: options.connect,
lnds: (await Promise.all(nodes)).map(({lnd}) => lnd),
payments: {limit: options.budget}
},
returnObject({logger, reject, resolve}));
} catch (err) {
return reject(err);
}
});
})
// Unlock wallet
.command('unlock', 'Unlock wallet if locked')
.help('Check if the wallet is locked, if so use a password file to unlock')
.argument('<path_to_password_file>', 'Path to password file')
.option('--no-color', 'Mute all colors')
.option('--node <node_name>', 'Node to unlock')
.action((args, options, logger) => {
return new Promise((resolve, reject) => {
return unlockWallet({
node: options.node,
path_to_password_file: args.pathToPasswordFile,
},
returnObject({logger, reject, resolve}));
});
})
// Get utxos
.command('utxos', 'Get a list of utxos')
.option('--confirmed', 'Return only confirmed utxos')
.option('--count', 'Return the count of utxos')
.option('--count-below <below>', 'Return only count below number', INT)
.option('--no-color', 'Mute all colors')
.option('--node <node_name>', 'Node to get utxos for')
.option('--size', 'Utxos of size greater than or equal to specified amount')
.action((args, options, logger) => {
return new Promise(async (resolve, reject) => {
try {
return getUtxos({
count_below: options.countBelow,
is_confirmed: !!options.confirmed,
is_count: !!options.count,
lnd: (await authenticatedLnd({logger, node: options.node})).lnd,
min_tokens: options.size || undefined,
node: options.node,
},
returnObject({logger, reject, resolve}));
} catch (err) {
return reject(err);
}
});
});
prog.parse(process.argv);