UNPKG

lightning

Version:
342 lines (296 loc) 12.9 kB
const {channelTypes} = require('./constants'); const {isArray} = Array; const remoteInitiator = 'INITIATOR_REMOTE'; const outpointSeparator = ':'; /** Derive pending channel details from a pending channels response { pending_force_closing_channels: [{ anchor: <Anchor Status String> blocks_til_maturity: <Remaining Blocks to Timelock Height Number> channel: { capacity: <Channel Capacity Tokens Count String> channel_point: <Channel Outpoint String> commitment_type: <Type of Channel String> initiator: <Channel Creator Status String> local_balance: <Local Balance Tokens String> local_chan_reserve_sat: <Local Side Channel Reserve Tokens String> memo: <Channel Description String> remote_balance: <Remote Balance Tokens String> remote_chan_reserve_sat: <Remote Side Channel Reserve Tokens String> remote_node_pub: <Remote Node Public Key Hex String> } closing_tx_hex: <Closing Raw Transaction Hex String> closing_txid: <Closing Transaction Id Hex String> limbo_balance: <Tokens Waiting For Resolution String> maturity_height: <Timelock Height Number> pending_htlcs: [{ amount: <Tokens Amount String> blocks_til_maturity: <Timelock Height Minus Current Height Number> incoming: <Is Incoming Bool> maturity_height: <Timelock Height Number> outpoint: <Commitment Transaction Outpoint String> stage: <Resolution Stage Number> }] recovered_balance: <Recovered Tokens Count String> }] pending_open_channels: [{ channel: { capacity: <Channel Capacity Tokens Count String> channel_point: <Channel Outpoint String> commitment_type: <Type of Channel String> confirmation_height: <Best Chain Block Height Funding Seen Number> confirmations_until_active: <Blocks Remaining to Channel Active Number> initiator: <Channel Creator Status String> local_balance: <Local Balance Tokens String> local_chan_reserve_sat: <Local Side Channel Reserve Tokens String> memo: <Channel Description String> remote_balance: <Remote Balance Tokens String> remote_chan_reserve_sat: <Remote Side Channel Reserve Tokens String> remote_node_pub: <Remote Node Public Key Hex String> } commit_fee: <Commitment Transaction Fee Tokens String> commit_weight: <Commitment Transaction Weight Units String> fee_per_kw: <Fee Tokens Per Thousand Weight Units String> funding_expiry_blocks: <Blocks Until Open Expired Number> }] waiting_close_channels: [{ channel: { capacity: <Channel Capacity Tokens Count String> channel_point: <Channel Outpoint String> commitment_type: <Type of Channel String> initiator: <Channel Creator Status String> local_balance: <Local Balance Tokens String> local_chan_reserve_sat: <Local Side Channel Reserve Tokens String> memo: <Channel Description String> remote_balance: <Remote Balance Tokens String> remote_chan_reserve_sat: <Remote Side Channel Reserve Tokens String> remote_node_pub: <Remote Node Public Key Hex String> } commitments: { local_commit_fee_sat: <Local Commitment Transaction Fee Tokens String> local_txid: <Local Commitment Transaction Id Hex String> remote_commit_fee_sat: <Remote Commitment Fee Tokens String> remote_pending_commit_fee_sat: <Remote Pending Commitment Fee String> remote_pending_txid: <Remote Pending Commitment Tx Id Hex String> remote_txid: <Remote Commitment Transaction Id Hex String> } limbo_balance: <Tokens Waiting for Resolution String> }] } @throws <Error> @returns { pending_channels: [{ [blocks_until_expiry]: <Blocks Until Open Channel Expires Number> capacity: <Channel Capacity Tokens Number> [close_transaction]: <Channel Closing Raw Transaction Hex String> [close_transaction_id]: <Channel Closing Transaction Id String> [description]: <Channel Description String> is_active: <Channel Is Active Bool> is_closing: <Channel Is Closing Bool> is_opening: <Channel Is Opening Bool> is_partner_initiated: <Channel Partner Initiated Channel Bool> is_timelocked: <Channel Local Funds Constrained by Timelock Script Bool> local_balance: <Channel Local Tokens Balance Number> local_reserve: <Channel Local Reserved Tokens Number> [opening_funding_height]: <Funding Seen At Best Block Height Number> [opening_waiting_blocks]: <Open Activation Waiting Blocks Count Number> partner_public_key: <Channel Peer Public Key String> [pending_balance]: <Tokens Pending Recovery Number> [pending_payments]: [{ is_incoming: <Payment Is Incoming Bool> timelock_height: <Payment Timelocked Until Height Number> tokens: <Payment Tokens Number> transaction_id: <Payment Transaction Id String> transaction_vout: <Payment Transaction Vout Number> }] received: <Tokens Received Number> [recovered_tokens]: <Tokens Recovered From Close Number> remote_balance: <Remote Tokens Balance Number> remote_reserve: <Channel Remote Reserved Tokens Number> sent: <Send Tokens Number> [timelock_blocks]: <Timelock Blocks Remaining Number> [timelock_expiration]: <Pending Tokens Block Height Timelock Number> [transaction_fee]: <Commit Transaction Fee Tokens Number> transaction_id: <Channel Funding Transaction Id String> transaction_vout: <Channel Funding Transaction Vout Number> [transaction_weight]: <Commit Transaction Weight Number> [type]: <Channel Commitment Transaction Type String> }] } */ module.exports = args => { if (!args) { throw new Error('ExpectedPendingChannelResponse'); } if (!isArray(args.pending_force_closing_channels)) { throw new Error('ExpectedPendingForceCloseChannels'); } if (!isArray(args.pending_open_channels)) { throw new Error('ExpectedPendingOpenChannels'); } if (!isArray(args.waiting_close_channels)) { throw new Error('ExpectedWaitingCloseChannels'); } const pendingForceClosingChannels = args.pending_force_closing_channels; const forceClosing = pendingForceClosingChannels.reduce((sum, pending) => { if (!pending) { throw new Error('ExpectedPendingForceClosingChannel'); } if (!pending.channel) { throw new Error('ExpectedChannelInPendingForceClosingChannel'); } if (!pending.channel.channel_point) { throw new Error('ExpectedChannelPointInPendingForceClosingChannel'); } if (!pending.closing_txid) { throw new Error('ExpectedPendingForceClosingTransactionId'); } if (!pending.limbo_balance) { throw new Error('ExpectedLimboBalanceInPendingForceCloseTransaction'); } if (pending.maturity_height === undefined) { throw new Error('ExpectedMaturityHeightInPendingForceCloseTransaction'); } if (!isArray(pending.pending_htlcs)) { throw new Error('ExpectedArrayOfPendingHtlcsInPendingForceCloseTx'); } if (!pending.recovered_balance) { throw new Error('ExpectedRecoveredBalanceAmountInPendingForceClose'); } sum[pending.channel.channel_point] = { close_transaction_id: pending.closing_txid, pending_balance: Number(pending.limbo_balance), pending_payments: pending.pending_htlcs.map(htlc => { if (!htlc) { throw new Error('ExpectedPendingHtlcInForceClosePendingHtlcs'); } if (!htlc.amount) { throw new Error('ExpectedPendingForceCloseHtlcAmount'); } if (htlc.incoming === undefined) { throw new Error('ExpectedPendingForceCloseHtlcIncomingStatus'); } if (htlc.maturity_height === undefined) { throw new Error('ExpectedPendingForceCloseHtlcMaturityHeight'); } if (!htlc.outpoint) { throw new Error('ExpectedHtlcOutpointInForceCloseHtlc'); } const [txId, vout] = htlc.outpoint.split(outpointSeparator); if (!txId) { throw new Error('ExpectedOutpointTransactionIdInPendingForceHtlc'); } if (!vout) { throw new Error('ExpectedOutpointOutputIndexInPendingForceHtlc'); } return { is_incoming: htlc.incoming, timelock_height: htlc.maturity_height, tokens: Number(htlc.amount), transaction_id: txId, transaction_vout: Number(vout), }; }), recovered_tokens: Number(pending.recovered_balance), timelock_blocks: pending.blocks_til_maturity, timelock_expiration: pending.maturity_height, }; return sum; }, {}); const opening = args.pending_open_channels.reduce((sum, pending) => { if (!pending) { throw new Error('ExpectedPendingOpenChannelDetailsInPendingOpens'); } if (!pending.channel) { throw new Error('ExpectedChannelDetailsInPendingOpenChannel'); } if (!pending.channel.channel_point) { throw new Error('ExpectedChannelFundingOutpointInPendingOpenChannel'); } if (!pending.commit_fee) { throw new Error('ExpectedPendingOpenChannelCommitmentTransactionFee'); } if (!pending.commit_weight) { throw new Error('ExpectedPendingOpenChannelCommitmentTransactionWeight'); } sum[pending.channel.channel_point] = { funding_expiry: Number(pending.funding_expiry_blocks) || undefined, open_blocks: Number(pending.confirmations_until_active) || undefined, open_height: Number(pending.confirmation_height) || undefined, transaction_fee: Number(pending.commit_fee), transaction_weight: Number(pending.commit_weight), }; return sum; }, {}); const waitClosing = args.waiting_close_channels.reduce((sum, pending) => { if (!pending) { throw new Error('ExpectedPendingDetailsInWaitingCloseChannel'); } if (!pending.channel) { throw new Error('ExpectedPendingChannelInWaitingCloseChannel'); } if (!pending.channel.channel_point) { throw new Error('ExpectedPendingChannelOutpointInWaitingCloseChannel'); } if (!pending.limbo_balance) { throw new Error('ExpectedLimboBalanceInWaitingCloseChannel'); } sum[pending.channel.channel_point] = { close_transaction: pending.closing_tx_hex, close_transaction_id: pending.closing_txid, pending_balance: Number(pending.limbo_balance), }; return sum; }, {}); const channels = [] .concat(args.pending_force_closing_channels) .concat(args.pending_open_channels) .concat(args.waiting_close_channels) .map(({channel}) => channel); const pendingChannels = channels.map(channel => { const chanOpen = opening[channel.channel_point]; const forced = forceClosing[channel.channel_point] || {}; const [txId, vout] = channel.channel_point.split(outpointSeparator); const wait = waitClosing[channel.channel_point] || {}; const endTx = forced.close_transaction_id || wait.close_transaction_id; const pendingTokens = wait.pending_balance || forced.pending_balance; return { blocks_until_expiry: !!chanOpen ? chanOpen.funding_expiry : undefined, capacity: Number(channel.capacity), close_transaction: wait.close_transaction || undefined, close_transaction_id: endTx || undefined, description: channel.memo || undefined, is_active: false, is_closing: !chanOpen, is_opening: !!chanOpen, is_partner_initiated: channel.initiator === remoteInitiator, is_private: channel.private || undefined, is_timelocked: forced.timelock_blocks !== undefined, local_balance: Number(channel.local_balance), local_reserve: Number(channel.local_chan_reserve_sat), opening_funding_height: !chanOpen ? undefined : chanOpen.open_height, opening_waiting_blocks: !chanOpen ? undefined : chanOpen.open_blocks, partner_public_key: channel.remote_node_pub, pending_balance: pendingTokens || undefined, pending_payments: forced.pending_payments || undefined, received: Number(), recovered_tokens: forced.recovered_tokens || undefined, remote_balance: Number(channel.remote_balance), remote_reserve: Number(channel.remote_chan_reserve_sat), sent: Number(), timelock_blocks: forced.timelock_blocks, timelock_expiration: forced.timelock_expiration || undefined, transaction_fee: !chanOpen ? null : chanOpen.transaction_fee, transaction_id: txId, transaction_vout: Number(vout), transaction_weight: !chanOpen ? null : chanOpen.transaction_weight, type: channelTypes[channel.commitment_type], }; }); return {pending_channels: pendingChannels}; };