mirror of
https://github.com/alexbosworth/balanceofsatoshis.git
synced 2026-08-13 12:33:37 +02:00
add nodes balance lookup
This commit is contained in:
parent
9d7d28bc3e
commit
b300f85d72
10 changed files with 606 additions and 1338 deletions
|
|
@ -1,5 +1,10 @@
|
|||
# Versions
|
||||
|
||||
## 13.11.0
|
||||
|
||||
- `balance`: Add support for multiple nodes when using --detailed balance
|
||||
- `telegram`: Add support for showing balance information with /balance command
|
||||
|
||||
## 13.10.7
|
||||
|
||||
- `invoice`: Fix issue creating `--virtual` invoices
|
||||
|
|
|
|||
|
|
@ -1,93 +0,0 @@
|
|||
const {Transaction} = require('bitcoinjs-lib');
|
||||
|
||||
const inputAsOutpoint = (txId, outputIndex) => `${txId}:${outputIndex}`;
|
||||
const {fromHex} = Transaction;
|
||||
const sumOf = arr => arr.reduce((sum, n) => sum + n, Number());
|
||||
const txIdFromHash = hash => hash.slice().reverse().toString('hex');
|
||||
const uniq = arr => Array.from(new Set(arr));
|
||||
|
||||
/** Derive conflicted on-chain pending balances where funds are double spent or
|
||||
multiple versions of the spend exist
|
||||
|
||||
{
|
||||
transactions: [{
|
||||
is_confirmed: <Transaction is Confirmed Bool>
|
||||
transaction: <Raw Transaction Hex String>
|
||||
}]
|
||||
utxos: [{
|
||||
confirmation_count: <UTXO Confirmation Count Number>
|
||||
tokens: <UTXO Tokens Number>
|
||||
transaction_id: <Outpoint Transaction Id Hex String>
|
||||
}]
|
||||
}
|
||||
|
||||
@returns
|
||||
{
|
||||
conflicting_pending_balance: <Conflicting Pending Balance Tokens Number>
|
||||
invalid_pending_balance: <Invalid Pending Balance Tokens Number>
|
||||
}
|
||||
*/
|
||||
module.exports = ({transactions, utxos}) => {
|
||||
const conflictingUtxos = [];
|
||||
const invalidUtxos = [];
|
||||
const spends = {}
|
||||
|
||||
// Look at unconfirmed UTXOs and collect spends of outpoints
|
||||
utxos.filter(n => !n.confirmation_count).forEach((utxo, i) => {
|
||||
const tx = transactions.find(n => n.id === utxo.transaction_id);
|
||||
|
||||
// Exit early when the raw transaction is not known
|
||||
if (!tx || !tx.transaction) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Register all the inputs into the spends map
|
||||
return fromHex(tx.transaction).ins.forEach(input => {
|
||||
const outpoint = inputAsOutpoint(txIdFromHash(input.hash), input.index);
|
||||
|
||||
const existing = spends[outpoint];
|
||||
|
||||
// When existing UTXO spends the same input this is a conflict
|
||||
if (!!existing) {
|
||||
conflictingUtxos.push(i);
|
||||
}
|
||||
|
||||
// Collect spends of the outpoint
|
||||
const spending = !existing ? [i] : [].concat(existing).concat(i);
|
||||
|
||||
return spends[outpoint] = spending;
|
||||
});
|
||||
});
|
||||
|
||||
// Look at confirmed txs and see if any unspents spend a confirmed outpoint
|
||||
transactions.forEach(tx => {
|
||||
// Exit early when there is no confirmed tx
|
||||
if (!tx.transaction || !tx.is_confirmed) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Look for pending inputs that are conflicted with a confirmed tx
|
||||
return fromHex(tx.transaction).ins.forEach(input => {
|
||||
const outpoint = inputAsOutpoint(txIdFromHash(input.hash), input.index);
|
||||
|
||||
// Exit early when nothing pending spends this outpoint
|
||||
if (!spends[outpoint]) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Pending things that spend a confirmed input are invalid
|
||||
return spends[outpoint].forEach(n => invalidUtxos.push(n));
|
||||
});
|
||||
});
|
||||
|
||||
const conflictingTokens = uniq(conflictingUtxos)
|
||||
.filter(utxoIndex => !invalidUtxos.includes(utxoIndex))
|
||||
.map(n => utxos[n].tokens);
|
||||
|
||||
const invalidTokens = uniq(invalidUtxos).map(n => utxos[n].tokens);
|
||||
|
||||
return {
|
||||
conflicting_pending_balance: sumOf(conflictingTokens),
|
||||
invalid_pending_balance: sumOf(invalidTokens),
|
||||
};
|
||||
};
|
||||
|
|
@ -1,188 +0,0 @@
|
|||
const conflictingBalances = require('./conflicting_balances');
|
||||
|
||||
const {ceil} = Math;
|
||||
const flatten = arr => [].concat(...arr);
|
||||
const inputsCounterVBytesLength = 3;
|
||||
const inputSequenceVByteLength = 4;
|
||||
const nestedPublicKeyAddressType = 'np2wpkh';
|
||||
const nestedPublicKeyVByteLength = 22;
|
||||
const outputCounterVBytesLength = 1;
|
||||
const outputValueVBytesLength = 8;
|
||||
const outputScriptCounterVBytesLength = 1;
|
||||
const outputScriptVBytesLength = 34;
|
||||
const sumOf = arr => arr.reduce((sum, n) => sum + n, Number());
|
||||
const transactionIdVByteLength = 32;
|
||||
const transactionLockTimeVBytesLength = 4;
|
||||
const transactionOutputIndexVByteLength = 4;
|
||||
const transactionVersionVBytesLength = 4;
|
||||
const witnessElementsCounterVByteLength = 0.25;
|
||||
const witnessPublicKeySizeVByteLength = 0.25;
|
||||
const witnessPublicKeyVByteLength = 8.25;
|
||||
const witnessSignatureVByteLength = 18;
|
||||
const witnessSizeCounterVByteLength = 0.25;
|
||||
|
||||
/** Determine balances from components
|
||||
|
||||
{
|
||||
channels: [{
|
||||
commit_transaction_fee: <Commitment Transaction Fee Tokens Number>
|
||||
is_partner_initiated: <Partner Responsible For Chain Fees Bool>
|
||||
local_balance: <Local Channel Balance Tokens Number>
|
||||
pending_payments: [{
|
||||
is_outgoing: <Payment is Sending Out Bool>
|
||||
tokens: <Payment Size Tokens Number>
|
||||
}]
|
||||
}]
|
||||
locked: [{
|
||||
tokens: <Unspent Tokens Number>
|
||||
}]
|
||||
pending: [{
|
||||
is_opening: <Channel is Pending Bool>
|
||||
is_partner_initiated: <Partner Responsible For Chain Fees Bool>
|
||||
local_balance: <Channel Local Balance Tokens Number>
|
||||
[pending_payments]: [{
|
||||
is_outgoing: <Payment is Sending Out Bool>
|
||||
tokens: <Payment Size Tokens Number>
|
||||
}]
|
||||
[recovered_tokens]: <Already Recovered Balance Tokens Number>
|
||||
[transaction_fee]: <Commitment Transaction Fee Tokens Number>
|
||||
}]
|
||||
transactions: [{
|
||||
is_confirmed: <Is Confirmed Bool>
|
||||
is_outgoing: <Transaction Outbound Bool>
|
||||
output_addresses: [<Address String>]
|
||||
}]
|
||||
utxos: [{
|
||||
address: <Chain Address String>
|
||||
address_format: <Chain Address Format String>
|
||||
confirmation_count: <Confirmation Count Number>
|
||||
tokens: <Unspent Tokens Number>
|
||||
}]
|
||||
}
|
||||
|
||||
@returns
|
||||
{
|
||||
closing_balance: <Balance of Tokens Moving Out Of Channels Tokens Number>
|
||||
conflicted_pending: <Conflicting Pending Balance Tokens Number>
|
||||
invalid_pending: <Invalid Pending Balance Tokens Number>
|
||||
offchain_balance: <Balance of Owned Tokens In Channels Tokens Number>
|
||||
offchain_pending: <Total Pending Local Balance Tokens Number>
|
||||
onchain_balance: <Balance of Transaction Outputs Number>
|
||||
onchain_vbytes: <Estimated Virtual Bytes to Spend On-Chain Funds Number>
|
||||
}
|
||||
*/
|
||||
module.exports = ({channels, locked, pending, transactions, utxos}) => {
|
||||
const channelBalances = channels.map(n => n.local_balance);
|
||||
const confirmedUtxos = utxos.filter(n => !!n.confirmation_count);
|
||||
|
||||
// Unconfirmed addresses in outgoing transactions
|
||||
const outgoingAddresses = flatten(transactions
|
||||
.filter(n => !n.is_confirmed && n.is_outgoing)
|
||||
.map(n => n.output_addresses));
|
||||
|
||||
// Unconfirmed change UTXOs
|
||||
const changeUtxos = utxos
|
||||
.filter(n => !n.confirmation_count)
|
||||
.filter(n => outgoingAddresses.includes(n.address));
|
||||
|
||||
// Calculate the local tokens that are still in the process of opening
|
||||
const opening = pending
|
||||
.filter(n => n.is_partner_initiated === false && n.is_opening)
|
||||
.map(n => n.local_balance);
|
||||
|
||||
// Calculate the balances coming back in closing
|
||||
const closing = pending
|
||||
.filter(n => n.is_closing)
|
||||
.map(n => n.local_balance - (n.recovered_tokens || Number()));
|
||||
|
||||
// For in-flight payments assume refund/timeout resolutions will happen
|
||||
const channelHtlcs = channels.map(chan => {
|
||||
return chan.pending_payments.filter(n => n.is_outgoing).map(n => n.tokens);
|
||||
});
|
||||
|
||||
// Some in-flight payments will be in on-chain HTLCs
|
||||
const pendingHtlcs = pending
|
||||
.map(chan => {
|
||||
return (chan.pending_payments || [])
|
||||
.filter(n => n.is_outgoing)
|
||||
.map(n => n.tokens);
|
||||
});
|
||||
|
||||
// Initiator commitment fees are deducted from channel local balances
|
||||
const commitFees = channels
|
||||
.filter(n => n.is_partner_initiated === false)
|
||||
.map(n => n.commit_transaction_fee);
|
||||
|
||||
// Pending channels also have commit transaction fees
|
||||
const pendingCommitFees = pending
|
||||
.filter(n => n.is_opening && n.is_partner_initiated === false)
|
||||
.map(n => n.transaction_fee || Number());
|
||||
|
||||
// Total balance to consider owned on-chain
|
||||
const chainBalance = sumOf([]
|
||||
.concat(confirmedUtxos)
|
||||
.concat(changeUtxos)
|
||||
.concat(locked)
|
||||
.map(n => n.tokens)
|
||||
);
|
||||
|
||||
// Input element virtual bytes
|
||||
const inputElements = flatten([]
|
||||
.concat(confirmedUtxos)
|
||||
.concat(changeUtxos)
|
||||
.map(utxo => {
|
||||
const inputData = []
|
||||
.concat(transactionIdVByteLength) // Previous outpoint tx id
|
||||
.concat(transactionOutputIndexVByteLength) // Previous tx out index
|
||||
.concat(inputSequenceVByteLength) // Input sequence number
|
||||
.concat(witnessElementsCounterVByteLength) // Witness elements count
|
||||
.concat(witnessSizeCounterVByteLength) // Witness sig size counter
|
||||
.concat(witnessSignatureVByteLength) // Witness signature
|
||||
.concat(witnessPublicKeySizeVByteLength) // Public key size counter
|
||||
.concat(witnessPublicKeyVByteLength); // Witness public key
|
||||
|
||||
// Exit early with nested data
|
||||
if (utxo.address_format === nestedPublicKeyAddressType) {
|
||||
return inputData.concat(nestedPublicKeyVByteLength);
|
||||
}
|
||||
|
||||
return inputData;
|
||||
}));
|
||||
|
||||
// Total balance to consider owned in channels
|
||||
const channelBalance = sumOf(flatten([]
|
||||
.concat(channelBalances)
|
||||
.concat(commitFees)
|
||||
.concat(flatten(channelHtlcs))
|
||||
));
|
||||
|
||||
// Total pending balance to consider owned
|
||||
const pendingBalance = sumOf(flatten([]
|
||||
.concat(opening)
|
||||
.concat(pendingCommitFees)
|
||||
.concat(flatten(pendingHtlcs))
|
||||
));
|
||||
|
||||
// Estimate the virtual bytes size of a tx that spends all inputs
|
||||
const vbyteComponents = !inputElements.length ? [] : flatten([]
|
||||
.concat(transactionVersionVBytesLength)
|
||||
.concat(inputsCounterVBytesLength)
|
||||
.concat(outputCounterVBytesLength)
|
||||
.concat(outputValueVBytesLength)
|
||||
.concat(outputScriptCounterVBytesLength)
|
||||
.concat(outputScriptVBytesLength)
|
||||
.concat(transactionLockTimeVBytesLength)
|
||||
.concat(inputElements));
|
||||
|
||||
const conflicts = conflictingBalances({transactions, utxos});
|
||||
|
||||
return {
|
||||
closing_balance: sumOf(closing),
|
||||
conflicted_pending: conflicts.conflicting_pending_balance,
|
||||
invalid_pending: conflicts.invalid_pending_balance,
|
||||
offchain_balance: channelBalance,
|
||||
offchain_pending: pendingBalance,
|
||||
onchain_balance: chainBalance,
|
||||
onchain_vbytes: ceil(sumOf(vbyteComponents)),
|
||||
};
|
||||
};
|
||||
|
|
@ -1,22 +1,17 @@
|
|||
const asyncAuto = require('async/auto');
|
||||
const asyncMap = require('async/map');
|
||||
const {formatTokens} = require('ln-sync');
|
||||
const {getChainTransactions} = require('ln-service');
|
||||
const {getChannels} = require('ln-service');
|
||||
const {getLockedUtxos} = require('ln-service');
|
||||
const {getPendingChannels} = require('ln-service');
|
||||
const {getUtxos} = require('ln-service');
|
||||
const {getNodeFunds} = require('ln-sync');
|
||||
const {returnResult} = require('asyncjs-util');
|
||||
const {Transaction} = require('bitcoinjs-lib');
|
||||
|
||||
const detailedBalances = require('./detailed_balances');
|
||||
|
||||
const {fromHex} = Transaction;
|
||||
const format = tokens => formatTokens({tokens}).display.trim();
|
||||
const {isArray} = Array;
|
||||
|
||||
/** Get a detailed balance that categorizes balance of tokens on the node
|
||||
|
||||
{
|
||||
[is_confirmed]: <Only Consider Confirmed Transactions Bool>
|
||||
lnd: <Authenticated LND API Object>
|
||||
lnds: [<Authenticated LND API Object>]
|
||||
}
|
||||
|
||||
@returns via cbk or Promise
|
||||
|
|
@ -37,104 +32,48 @@ module.exports = (args, cbk) => {
|
|||
return asyncAuto({
|
||||
// Check arguments
|
||||
validate: cbk => {
|
||||
if (!args.lnd) {
|
||||
return cbk([400, 'ExpectedAuthenticatedLndToGetDetailedBalance']);
|
||||
if (!isArray(args.lnds)) {
|
||||
return cbk([400, 'ExpectedAuthenticatedLndsToGetDetailedBalance']);
|
||||
}
|
||||
|
||||
return cbk();
|
||||
},
|
||||
|
||||
// Get the channels
|
||||
getChannels: ['validate', ({}, cbk) => {
|
||||
return getChannels({lnd: args.lnd}, cbk);
|
||||
// Get info about the funds on the node
|
||||
getFunds: ['validate', ({}, cbk) => {
|
||||
return asyncMap(args.lnds, (lnd, cbk) => {
|
||||
return getNodeFunds({lnd, is_confirmed: args.is_confirmed}, cbk);
|
||||
},
|
||||
cbk);
|
||||
}],
|
||||
|
||||
// Get locked UTXOs
|
||||
getLocked: ['validate', ({}, cbk) => {
|
||||
return getLockedUtxos({lnd: args.lnd}, (err, res) => {
|
||||
// Ignore errors
|
||||
if (!!err) {
|
||||
return cbk(null, []);
|
||||
}
|
||||
|
||||
return cbk(null, res.utxos);
|
||||
// Return a formatted balance summary
|
||||
balance: ['getFunds', ({getFunds}, cbk) => {
|
||||
// Sum balances from all nodes
|
||||
const balances = getFunds.reduce((sum, n) => {
|
||||
return {
|
||||
closing_balance: sum.closing_balance + n.closing_balance,
|
||||
conflicted_pending: sum.conflicted_pending + n.conflicted_pending,
|
||||
invalid_pending: sum.invalid_pending + n.invalid_pending,
|
||||
offchain_balance: sum.offchain_balance + n.offchain_balance,
|
||||
offchain_pending: sum.offchain_pending + n.offchain_pending,
|
||||
onchain_confirmed: sum.onchain_confirmed + n.onchain_confirmed,
|
||||
onchain_pending: sum.onchain_pending + n.onchain_pending,
|
||||
onchain_vbytes: sum.onchain_vbytes + n.onchain_vbytes,
|
||||
utxos_count: sum.utxos_count + n.utxos_count,
|
||||
};
|
||||
},
|
||||
{
|
||||
closing_balance: Number(),
|
||||
conflicted_pending: Number(),
|
||||
invalid_pending: Number(),
|
||||
offchain_balance: Number(),
|
||||
offchain_pending: Number(),
|
||||
onchain_confirmed: Number(),
|
||||
onchain_pending: Number(),
|
||||
onchain_vbytes: Number(),
|
||||
utxos_count: Number(),
|
||||
});
|
||||
}],
|
||||
|
||||
// Get pending channels
|
||||
getPending: ['validate', ({}, cbk) => {
|
||||
return getPendingChannels({lnd: args.lnd}, cbk);
|
||||
}],
|
||||
|
||||
// Get the chain transactions
|
||||
getTx: ['validate', ({}, cbk) => {
|
||||
return getChainTransactions({lnd: args.lnd}, cbk);
|
||||
}],
|
||||
|
||||
// Get the UTXOs
|
||||
getUtxos: ['validate', ({}, cbk) => getUtxos({lnd: args.lnd}, cbk)],
|
||||
|
||||
// Cross reference locked transactions to UTXO data
|
||||
locked: ['getLocked', 'getTx', ({getLocked, getTx}, cbk) => {
|
||||
const {transactions} = getTx;
|
||||
|
||||
const utxos = getLocked.map(locked => {
|
||||
// Exit early when the lock is expired
|
||||
if (locked.lock_expires_at < new Date().toISOString()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tx = transactions.find(n => n.id === locked.transaction_id);
|
||||
|
||||
// Exit early when there is no related confirmed transaction
|
||||
if (!tx || !tx.transaction || !tx.confirmation_count) {
|
||||
return;
|
||||
}
|
||||
|
||||
const output = fromHex(tx.transaction).outs[locked.transaction_vout];
|
||||
|
||||
// Exit early when the output is not found in the transaction
|
||||
if (!output) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {tokens: output.value};
|
||||
});
|
||||
|
||||
return cbk(null, utxos.filter(n => !!n));
|
||||
}],
|
||||
|
||||
// Calculate balance
|
||||
balance: [
|
||||
'getChannels',
|
||||
'getPending',
|
||||
'getTx',
|
||||
'getUtxos',
|
||||
'locked',
|
||||
({getChannels, getPending, getTx, getUtxos, locked}, cbk) =>
|
||||
{
|
||||
const confUtxos = getUtxos.utxos.filter(n => !!n.confirmation_count);
|
||||
const confirmedTx = getTx.transactions.filter(n => !!n.is_confirmed);
|
||||
const format = tokens => formatTokens({tokens}).display.trim();
|
||||
|
||||
const confirmed = detailedBalances({
|
||||
locked,
|
||||
channels: getChannels.channels,
|
||||
pending: getPending.pending_channels,
|
||||
transactions: confirmedTx,
|
||||
utxos: confUtxos,
|
||||
});
|
||||
|
||||
const unconfirmed = detailedBalances({
|
||||
locked,
|
||||
channels: getChannels.channels,
|
||||
pending: getPending.pending_channels,
|
||||
transactions: getTx.transactions,
|
||||
utxos: getUtxos.utxos,
|
||||
});
|
||||
|
||||
const balances = !!args.is_confirmed ? confirmed : unconfirmed;
|
||||
const limbo = unconfirmed.onchain_balance - confirmed.onchain_balance;
|
||||
|
||||
return cbk(null, {
|
||||
closing_balance: format(balances.closing_balance) || undefined,
|
||||
|
|
@ -142,10 +81,10 @@ module.exports = (args, cbk) => {
|
|||
invalid_pending: format(balances.invalid_pending) || undefined,
|
||||
offchain_balance: format(balances.offchain_balance) || undefined,
|
||||
offchain_pending: format(balances.offchain_pending) || undefined,
|
||||
onchain_confirmed: format(confirmed.onchain_balance) || undefined,
|
||||
onchain_pending: format(limbo) || undefined,
|
||||
onchain_confirmed: format(balances.onchain_confirmed) || undefined,
|
||||
onchain_pending: format(balances.onchain_pending) || undefined,
|
||||
onchain_vbytes: balances.onchain_vbytes || undefined,
|
||||
utxos_count: getUtxos.utxos.length || undefined,
|
||||
utxos_count: balances.utxos_count || undefined,
|
||||
});
|
||||
}],
|
||||
},
|
||||
|
|
|
|||
9
bos
9
bos
|
|
@ -167,34 +167,33 @@ prog
|
|||
// Get local balance information
|
||||
.command('balance', 'Get total tokens')
|
||||
.help('Sums balances on-chain, in channels, and pending, plus commit fees')
|
||||
.help('Multiple --node arguments are supported to sum across nodes')
|
||||
.option('--above <tokens>', 'Return tokens above watermark', INT)
|
||||
.option('--below <tokens>', 'Return tokens below watermark', INT)
|
||||
.option('--confirmed', 'Return confirmed funds only')
|
||||
.option('--detailed', 'Return detailed balance information')
|
||||
.option('--node <node_name>', 'Node to get balance for')
|
||||
.option('--node <node_name>', 'Node to get balance for', REPEATABLE)
|
||||
.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 {
|
||||
const {lnd} = await lndForNode(logger, options.node);
|
||||
|
||||
// Exit early when detailed balance details are requested
|
||||
if (!!options.detailed) {
|
||||
return balances.getDetailedBalance({
|
||||
lnd,
|
||||
lnds: (await lnd.getLnds({logger, nodes: options.node})).lnds,
|
||||
is_confirmed: options.confirmed,
|
||||
},
|
||||
responses.returnObject({logger, reject, resolve}));
|
||||
}
|
||||
|
||||
return balances.getBalance({
|
||||
lnd,
|
||||
above: options.above,
|
||||
below: options.below,
|
||||
is_confirmed: !!options.confirmed,
|
||||
is_offchain_only: !!options.offchain,
|
||||
is_onchain_only: !!options.onchain,
|
||||
lnd: (await lndForNode(logger, options.node)).lnd,
|
||||
},
|
||||
responses.returnNumber({logger, reject, resolve, number: 'balance'}));
|
||||
} catch (err) {
|
||||
|
|
|
|||
1300
package-lock.json
generated
1300
package-lock.json
generated
File diff suppressed because it is too large
Load diff
10
package.json
10
package.json
|
|
@ -30,15 +30,15 @@
|
|||
"csv-parse": "5.3.3",
|
||||
"ecpair": "2.1.0",
|
||||
"goldengate": "11.4.0",
|
||||
"grammy": "1.12.0",
|
||||
"grammy": "1.12.1",
|
||||
"hot-formula-parser": "4.0.0",
|
||||
"import-lazy": "4.0.0",
|
||||
"ini": "3.0.1",
|
||||
"inquirer": "9.1.4",
|
||||
"ln-accounting": "6.1.1",
|
||||
"ln-service": "54.6.0",
|
||||
"ln-sync": "4.0.5",
|
||||
"ln-telegram": "4.3.1",
|
||||
"ln-service": "54.8.0",
|
||||
"ln-sync": "4.1.0",
|
||||
"ln-telegram": "4.4.0",
|
||||
"moment": "2.29.4",
|
||||
"paid-services": "4.1.0",
|
||||
"probing": "3.0.0",
|
||||
|
|
@ -83,5 +83,5 @@
|
|||
"postpublish": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t alexbosworth/balanceofsatoshis --push .",
|
||||
"test": "tap --branches=1 --functions=1 --lines=1 --statements=1 -t 60 test/arrays/*.js test/balances/*.js test/chain/*.js test/display/*.js test/encryption/*.js test/lnd/*.js test/network/*.js test/nodes/*.js test/peers/*.js test/responses/*.js test/routing/*.js test/services/*.js test/swaps/*.js test/tags/*.js test/telegram/*.js test/wallets/*.js"
|
||||
},
|
||||
"version": "13.10.7"
|
||||
"version": "13.11.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ const {getNetwork} = require('ln-service');
|
|||
const {getTransactionRecord} = require('ln-sync');
|
||||
const {getWalletInfo} = require('ln-service');
|
||||
const {handleBackupCommand} = require('ln-telegram');
|
||||
const {handleBalanceCommand} = require('ln-telegram');
|
||||
const {handleBlocknotifyCommand} = require('ln-telegram');
|
||||
const {handleButtonPush} = require('ln-telegram');
|
||||
const {handleConnectCommand} = require('ln-telegram');
|
||||
|
|
@ -198,6 +199,21 @@ module.exports = (args, cbk) => {
|
|||
}
|
||||
});
|
||||
|
||||
// Handle lookup of total funds
|
||||
args.bot.command('balance', async ctx => {
|
||||
try {
|
||||
await handleBalanceCommand({
|
||||
from: ctx.message.from.id,
|
||||
id: connectedId,
|
||||
nodes: (await getLnds(args.logger, names, args.nodes)).nodes,
|
||||
reply: (n, opt) => ctx.reply(n, opt),
|
||||
working: () => ctx.replyWithChatAction('typing'),
|
||||
});
|
||||
} catch (err) {
|
||||
args.logger.error({err});
|
||||
}
|
||||
});
|
||||
|
||||
// Handle command to get notified on the next block
|
||||
args.bot.command('blocknotify', ctx => {
|
||||
handleBlocknotifyCommand({
|
||||
|
|
@ -528,6 +544,7 @@ module.exports = (args, cbk) => {
|
|||
setCommands: ['validate', async ({}) => {
|
||||
return await args.bot.api.setMyCommands([
|
||||
{command: 'backup', description: 'Get node backup file'},
|
||||
{command: 'balance', description: 'Show funds on the node'},
|
||||
{command: 'blocknotify', description: 'Get notified on next block'},
|
||||
{command: 'connect', description: 'Get connect code for the bot'},
|
||||
{command: 'costs', description: 'Show costs over the week'},
|
||||
|
|
|
|||
|
|
@ -1,133 +0,0 @@
|
|||
const {test} = require('@alexbosworth/tap');
|
||||
|
||||
const detailedBalances = require('./../../balances/detailed_balances');
|
||||
|
||||
const tests = [
|
||||
{
|
||||
args: {
|
||||
channels: [
|
||||
{
|
||||
commit_transaction_fee: 1,
|
||||
is_partner_initiated: true,
|
||||
local_balance: 2,
|
||||
pending_payments: [{is_outgoing: true, tokens: 3}],
|
||||
},
|
||||
{
|
||||
commit_transaction_fee: 4,
|
||||
is_partner_initiated: false,
|
||||
local_balance: 5,
|
||||
pending_payments: [{is_outgoing: false, tokens: 6}],
|
||||
},
|
||||
],
|
||||
locked: [{
|
||||
tokens: 1,
|
||||
}],
|
||||
pending: [
|
||||
{
|
||||
is_closing: true,
|
||||
local_balance: 2,
|
||||
recovered_tokens: 1,
|
||||
},
|
||||
{
|
||||
is_opening: true,
|
||||
is_partner_initiated: true,
|
||||
local_balance: 0,
|
||||
transaction_fee: 1,
|
||||
},
|
||||
{
|
||||
is_opening: true,
|
||||
is_partner_initiated: true,
|
||||
local_balance: 2,
|
||||
pending_payments: [{is_outgoing: true, tokens: 3}],
|
||||
transaction_fee: 1,
|
||||
},
|
||||
{
|
||||
is_opening: false,
|
||||
is_partner_initiated: false,
|
||||
local_balance: 2,
|
||||
pending_payments: [{is_outgoing: true, tokens: 3}],
|
||||
transaction_fee: 1,
|
||||
},
|
||||
{
|
||||
is_opening: true,
|
||||
is_partner_initiated: false,
|
||||
local_balance: 5,
|
||||
pending_payments: [{is_outgoing: false, tokens: 6}],
|
||||
},
|
||||
{
|
||||
is_opening: true,
|
||||
is_partner_initiated: false,
|
||||
local_balance: 7,
|
||||
pending_payments: [{is_outgoing: true, tokens: 8}],
|
||||
transaction_fee: 9,
|
||||
},
|
||||
],
|
||||
transactions: [
|
||||
{
|
||||
is_confirmed: false,
|
||||
is_outgoing: true,
|
||||
output_addresses: ['change-address'],
|
||||
},
|
||||
],
|
||||
utxos: [
|
||||
{
|
||||
address: 'address',
|
||||
address_format: 'np2wpkh',
|
||||
confirmation_count: 1,
|
||||
tokens: 1,
|
||||
},
|
||||
{
|
||||
address: 'change-address',
|
||||
address_format: 'p2wpkh',
|
||||
confirmation_count: 0,
|
||||
tokens: 2,
|
||||
},
|
||||
{
|
||||
address: 'address2',
|
||||
address_format: 'p2wpkh',
|
||||
confirmation_count: 0,
|
||||
tokens: 2,
|
||||
},
|
||||
],
|
||||
},
|
||||
description: 'Balance totals are calculated',
|
||||
expected: {
|
||||
closing_balance: 1,
|
||||
conflicted_pending: 0,
|
||||
invalid_pending: 0,
|
||||
offchain_balance: 14,
|
||||
offchain_pending: 35,
|
||||
onchain_balance: 4,
|
||||
onchain_vbytes: 211,
|
||||
},
|
||||
},
|
||||
{
|
||||
args: {channels: [], locked: [], pending: [], transactions: [], utxos: []},
|
||||
description: 'Balance totals are calculated when there are no funds',
|
||||
expected: {
|
||||
closing_balance: 0,
|
||||
conflicted_pending: 0,
|
||||
invalid_pending: 0,
|
||||
offchain_balance: 0,
|
||||
offchain_pending: 0,
|
||||
onchain_balance: 0,
|
||||
onchain_vbytes: 0,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
tests.forEach(({args, description, error, expected}) => {
|
||||
return test(description, ({end, equal, strictSame, throws}) => {
|
||||
if (!!error) {
|
||||
throws(() => detailedBalances(args), new Error(error));
|
||||
|
||||
return end();
|
||||
}
|
||||
|
||||
const balances = detailedBalances(args);
|
||||
|
||||
strictSame(balances, expected, 'Got expected balances');
|
||||
|
||||
return end();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
const {test} = require('@alexbosworth/tap');
|
||||
const {makeLnd} = require('mock-lnd');
|
||||
|
||||
const method = require('./../../balances/get_detailed_balance');
|
||||
const {listChannelsResponse} = require('./../fixtures');
|
||||
|
||||
const tests = [
|
||||
{
|
||||
args: {},
|
||||
description: 'LND is required',
|
||||
error: [400, 'ExpectedAuthenticatedLndToGetDetailedBalance'],
|
||||
},
|
||||
{
|
||||
args: {lnd: makeLnd({})},
|
||||
description: 'Detailed balance is returned',
|
||||
expected: '7b226f6666636861696e5f62616c616e6365223a225c75303031625b326d302e30303030303030325c75303031625b32326d222c226f6e636861696e5f636f6e6669726d6564223a225c75303031625b326d302e30303030303030315c75303031625b32326d222c226f6e636861696e5f766279746573223a3134342c227574786f735f636f756e74223a317d',
|
||||
},
|
||||
{
|
||||
args: {
|
||||
lnd: makeLnd({
|
||||
getChannels: ({}, cbk) => cbk(null, {channels: []}),
|
||||
getUtxos: ({}, cbk) => cbk(null, {utxos: []}),
|
||||
}),
|
||||
},
|
||||
description: 'No balance is returned',
|
||||
expected: '7b7d',
|
||||
},
|
||||
];
|
||||
|
||||
tests.forEach(({args, description, error, expected}) => {
|
||||
return test(description, async ({end, equal, rejects}) => {
|
||||
if (!!error) {
|
||||
await rejects(method(args), error, 'Got expected error');
|
||||
} else {
|
||||
const res = await method(args);
|
||||
|
||||
const encoded = Buffer.from(JSON.stringify(res)).toString('hex');
|
||||
|
||||
equal(encoded, expected, 'Got expected result');
|
||||
}
|
||||
|
||||
return end();
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue