-
Notifications
You must be signed in to change notification settings - Fork 33
/
pending_from_payment.js
144 lines (127 loc) · 5.18 KB
/
pending_from_payment.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
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>
}]
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
{
created_at: <Payment Created At ISO 8601 Date String>
destination: <Payment Destination Public Key Hex String>
id: <Payment Hash Hex String>
mtokens: <Total Millitokens Pending String>
paths: [{
fee: <Total Fee Tokens Pending Number>
fee_mtokens: <Total Fee Millitokens Pending 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_tokens: <Total Tokens Pending, Rounded Up Number>
[timeout]: <Expiration Block Height Number>
tokens: <Total Tokens Pending Rounded Down Number>
}
*/
module.exports = payment => {
if (!payment) {
throw new Error('ExpectedPendingPaymentToDerivePendingDetails');
}
if (!payment.creation_time_ns) {
throw new Error('ExpectedPaymentCreationDateToDerivePendingDetails');
}
if (!payment.fee_msat) {
throw new Error('ExpectedPaymentFeeMillitokensAmountForPendingPayment');
}
if (!isArray(payment.htlcs)) {
throw new Error('ExpectedArrayOfPaymentHtlcsInPendingPayment');
}
if (!payment.htlcs.find(n => n.status === attemptStates.pending)) {
throw new Error('ExpectedPendingHtlcInPendingPayment');
}
if (!is256Hex(payment.payment_hash)) {
throw new Error('ExpectedPaymentHashForPaymentAsPendingPayment');
}
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 pending = attempts.filter(n => n.is_pending);
const [first] = pending;
const [destination] = first.route.hops.map(n => n.public_key).reverse();
return {
destination,
created_at: nsAsDate(payment.creation_time_ns).toISOString(),
id: payment.payment_hash,
mtokens: mtokens.toString(),
paths: pending.map(n => n.route),
request: payment.payment_request || undefined,
safe_tokens: safeTokens({mtokens: mtokens.toString()}).safe,
timeout: max(...pending.map(n => n.route.timeout).filter(n => !!n)),
tokens: safeTokens({mtokens: mtokens.toString()}).tokens,
};
};