UNPKG

lightning

Version:
174 lines (155 loc) 6.63 kB
const {attemptStates} = require('./constants'); const rpcAttemptHtlcAsAttempt = require('./rpc_attempt_htlc_as_attempt'); const {safeTokens} = require('./../bolt00'); const {isArray} = Array; const is256Hex = n => !!n && /^[0-9A-F]{64}$/i.test(n); const {max} = Math; const mtokensAsTokens = mtokens => safeTokens({mtokens}).tokens; const nsAsDate = ns => new Date(Number(BigInt(ns) / BigInt(1e6))); /** Calculate total payment details from RPC payment HTLC elements The `route` attribute only returns the first route, there may be more due to payment splitting { creation_date: <Creation Date Epoch Time Seconds String> creation_time_ns: <Creation Date Epoch Time Nanoseconds String> failure_reason: <Payment Failure Reason String> fee_msat: <Fee Paid in Millitokens String> fee_sat: <Fee Paid in Tokens String> htlcs: [{ attempt_time_ns: <HTLC Sent At Epoch Time Nanoseconds String> resolve_time_ns: <HTLC Resolved At Epoch Time Nanoseconds String> route: { hops: [{ amt_to_forward: <Tokens to Forward String> amt_to_forward_msat: <Millitokens to Forward String> chan_id: <Numeric Format Channel Id String> chan_capacity: <Channel Capacity Tokens String> custom_records: { <UInt64 String>: <Record Data Buffer> } expiry: <Timeout Chain Height Number> fee: <Fee in Tokens String> fee_msat: <Fee in Millitokens String> [mpp_record]: { payment_addr: <Payment Identifier Buffer> total_amt_msat: <Total Payment Millitokens Amount String> } [pub_key]: <Next Hop Public Key Hex String> tlv_payload: <Has Extra TLV Data Bool> }] total_amt: <Total Tokens String> total_amt_msat: <Route Total Millitokens String> total_fees: <Route Fee Tokens String> total_fees_msat: <Route Total Fees Millitokens String> total_time_lock: <Route Total Timelock Number> } status: <HTLC Status String> }] path: [<Hop Public Key Hex String>] payment_hash: <Preimage SHA256 Hash Hex String> payment_index: <Payment Index String> payment_preimage: <Payment Secret Preimage Hex String> payment_request: <BOLT 11 Payment Request String> status: <Payment State String> value: <Tokens String> value_msat: <Paid Tokens Without Routing Fees Millitokens String> value_sat: <Paid Tokens Without Routing Fees String> } @throws <Error> @returns { confirmed_at: <Payment Confirmed At ISO 8601 Date String> created_at: <Payment Created At ISO 8601 Date String> destination: <Payment Destination Public Key Hex String> fee: <Total Fee Tokens Paid Rounded Down Number> fee_mtokens: <Total Fee Millitokens Paid String> hops: [{ channel: <First Path Standard Format Channel Id String> channel_capacity: <First Path Channel Capacity Tokens Number> fee: <First Route Fee Path Rounded Down Number> fee_mtokens: <First Path Fee Millitokens String> forward: <First Path Forward Tokens Number> forward_mtokens: <First Path Forward Millitokens String> public_key: <First Path Public Key Hex String> timeout: <First Path Timeout Block Height Number> }] id: <Payment Hash Hex String> index: <Payment Index Offset Number String> mtokens: <Total Millitokens Paid String> paths: [{ fee: <Total Fee Tokens Paid Number> fee_mtokens: <Total Fee Millitokens Paid String> hops: [{ channel: <Standard Format Channel Id String> channel_capacity: <Channel Capacity Tokens Number> fee: <Fee Tokens Rounded Down Number> fee_mtokens: <Fee Millitokens String> forward: <Forward Tokens Number> forward_mtokens: <Forward Millitokens String> public_key: <Public Key Hex String> timeout: <Timeout Block Height Number> }] mtokens: <Total Millitokens Paid String> safe_fee: <Total Fee Tokens Paid Rounded Up Number> safe_tokens: <Total Tokens Paid, Rounded Up Number> timeout: <Expiration Block Height Number> }] [request]: <BOLT 11 Encoded Payment Request String> safe_fee: <Total Fee Tokens Paid Rounded Up Number> safe_tokens: <Total Tokens Paid, Rounded Up Number> secret: <Payment Preimage Hex String> [timeout]: <Expiration Block Height Number> tokens: <Total Tokens Paid Rounded Down Number> } */ module.exports = payment => { if (!payment) { throw new Error('ExpectedConfirmedPaymentToDeriveConfirmationDetails'); } if (!payment.creation_time_ns) { throw new Error('ExpectedPaymentCreationDateToDerivePaymentDetails'); } if (!payment.fee_msat) { throw new Error('ExpectedPaymentFeeMillitokensAmountForPayment'); } if (!isArray(payment.htlcs)) { throw new Error('ExpectedArrayOfPaymentHtlcsInConfirmedPayment'); } if (!payment.htlcs.find(n => n.status === attemptStates.confirmed)) { throw new Error('ExpectedSuccessHtlcInConfirmedPayment'); } if (!is256Hex(payment.payment_hash)) { throw new Error('ExpectedPaymentHashForPaymentAsConfirmedPayment'); } if (!is256Hex(payment.payment_preimage)) { throw new Error('ExpectedPaymentPreimageForPaymentAsConfirmedPayment'); } if (mtokensAsTokens(payment.value_msat) !== Number(payment.value_sat)) { throw new Error('ExpectedValueOfTokensAndMillitokensToBeConsistent'); } const attempts = payment.htlcs.map(htlc => rpcAttemptHtlcAsAttempt(htlc)); const mtokens = BigInt(payment.value_msat) + BigInt(payment.fee_msat); const successes = attempts.filter(n => n.is_confirmed); const [confirmedAt] = successes.map(n => n.confirmed_at).sort().reverse(); const [success] = successes; const [destination] = success.route.hops.map(n => n.public_key).reverse(); return { destination, confirmed_at: confirmedAt, created_at: nsAsDate(payment.creation_time_ns).toISOString(), fee: safeTokens({mtokens: payment.fee_msat}).tokens, fee_mtokens: payment.fee_msat, hops: success.route.hops, id: payment.payment_hash, index: payment.payment_index, mtokens: mtokens.toString(), paths: successes.map(n => n.route), request: payment.payment_request || undefined, safe_fee: safeTokens({mtokens: payment.fee_msat}).safe, safe_tokens: safeTokens({mtokens: mtokens.toString()}).safe, secret: payment.payment_preimage, timeout: max(...successes.map(n => n.route.timeout).filter(n => !!n)), tokens: safeTokens({mtokens: mtokens.toString()}).tokens, }; };