11import { cachedFetcher } from '@/lib/fetch'
22import { toPositiveNumber } from '@/lib/format'
33import { authenticatedLndGrpc } from '@/lib/lnd'
4+ import { randomBytes } from 'crypto'
5+ import { chanNumber } from 'bolt07'
46import { getIdentity , getHeight , getWalletInfo , getNode , getPayment , parsePaymentRequest } from 'ln-service'
57
68const lnd = global . lnd || authenticatedLndGrpc ( {
@@ -20,6 +22,59 @@ getWalletInfo({ lnd }, (err, result) => {
2022 console . log ( 'LND GRPC connection successful' )
2123} )
2224
25+ // we don't use this because it doesn't solve https://github.com/lightningnetwork/lnd/discussions/10427
26+ // due to this bug, real probes cause the channel to be marked as unusable and the real payment fails
27+ // this should be useful in the future though when need more control over probing
28+ export async function rawProbePayment ( { lnd, request, maxFeeMsat, timeoutSeconds, maxCltvDelta } ) {
29+ if ( ! request ) {
30+ throw new Error ( 'Payment request is required' )
31+ }
32+ const inv = parsePaymentRequest ( { request } )
33+ const params = {
34+ allow_self_payment : true ,
35+ amt_msat : inv . mtokens ,
36+ cancelable : true ,
37+ cltv_limit : maxCltvDelta ,
38+ dest : Buffer . from ( inv . destination , 'hex' ) ,
39+ dest_custom_records : undefined ,
40+ dest_features : inv . features . map ( n => n . bit ) ,
41+ fee_limit_msat : maxFeeMsat ,
42+ final_cltv_delta : inv . cltv_delta ,
43+ last_hop_pubkey : undefined ,
44+ max_parts : 1 ,
45+ max_shard_size_msat : undefined ,
46+ no_inflight_updates : true ,
47+ outgoing_chan_id : undefined ,
48+ outgoing_chan_ids : [ ] ,
49+ payment_addr : undefined ,
50+ payment_hash : randomBytes ( 32 ) ,
51+ payment_request : undefined ,
52+ route_hints : inv . routes ?. map ( r => ( {
53+ hop_hints : r . slice ( 1 ) . map ( ( h , i ) => ( {
54+ fee_base_msat : h . base_fee_mtokens ,
55+ fee_proportional_millionths : h . fee_rate ,
56+ chan_id : chanNumber ( { channel : h . channel } ) . number ,
57+ cltv_expiry_delta : h . cltv_delta ,
58+ node_id : r [ i ] . public_key
59+ } ) )
60+ } ) ) ?? [ ] ,
61+ time_pref : 1 ,
62+ timeout_seconds : timeoutSeconds
63+ }
64+ return await new Promise ( ( resolve , reject ) => {
65+ const sub = lnd . router . sendPaymentV2 ( params )
66+ sub . on ( 'data' , ( res ) => {
67+ resolve ( res )
68+ } )
69+ sub . on ( 'error' , ( err ) => {
70+ reject ( err )
71+ } )
72+ sub . on ( 'end' , ( ) => {
73+ reject ( new Error ( 'Payment timed out' ) )
74+ } )
75+ } )
76+ }
77+
2378export async function estimateRouteFee ( { lnd, destination, tokens, mtokens, request, timeout } ) {
2479 // if the payment request includes us as route hint, we needd to use the destination and amount
2580 // otherwise, this will fail with a self-payment error
@@ -39,50 +94,42 @@ export async function estimateRouteFee ({ lnd, destination, tokens, mtokens, req
3994 }
4095 }
4196 }
97+ // XXX we don't use the payment request anymore because it causes the channel to be marked as unusable
98+ // and the real payment fails see: https://github.com/lightningnetwork/lnd/discussions/10427
99+ // without the request, the estimate is a statistical estimate based on past payments
100+ request = false
42101 }
43102
44- let failureReason
45- try {
46- return await new Promise ( ( resolve , reject ) => {
47- const params = { }
48-
49- if ( request ) {
50- console . log ( 'estimateRouteFee using payment request' )
51- params . payment_request = request
52- } else {
53- console . log ( 'estimateRouteFee using destination and amount' )
54- params . dest = Buffer . from ( destination , 'hex' )
55- params . amt_sat = tokens ? toPositiveNumber ( tokens ) : toPositiveNumber ( BigInt ( mtokens ) / BigInt ( 1e3 ) )
56- }
103+ return await new Promise ( ( resolve , reject ) => {
104+ const params = { }
57105
58- lnd . router . estimateRouteFee ( {
59- ...params ,
60- timeout
61- } , ( err , res ) => {
62- if ( err ) {
63- return reject ( err )
64- }
106+ if ( request ) {
107+ console . log ( 'estimateRouteFee using payment request' )
108+ params . payment_request = request
109+ } else {
110+ console . log ( 'estimateRouteFee using destination and amount' )
111+ params . dest = Buffer . from ( destination , 'hex' )
112+ params . amt_sat = tokens ? toPositiveNumber ( tokens ) : toPositiveNumber ( BigInt ( mtokens ) / BigInt ( 1e3 ) )
113+ }
65114
66- if ( res . failure_reason !== 'FAILURE_REASON_NONE' || res . routing_fee_msat < 0 || res . time_lock_delay <= 0 ) {
67- failureReason = res . failure_reason
68- return reject ( new Error ( `Unable to estimate route: ${ failureReason } ` ) )
69- }
115+ lnd . router . estimateRouteFee ( {
116+ ...params ,
117+ timeout
118+ } , ( err , res ) => {
119+ if ( err ) {
120+ return reject ( err )
121+ }
70122
71- resolve ( {
72- routingFeeMsat : toPositiveNumber ( res . routing_fee_msat ) ,
73- timeLockDelay : toPositiveNumber ( res . time_lock_delay )
74- } )
123+ if ( res . failure_reason !== 'FAILURE_REASON_NONE' || res . routing_fee_msat < 0 || res . time_lock_delay <= 0 ) {
124+ return reject ( new Error ( `Unable to estimate route: ${ res . failure_reason } ` ) )
125+ }
126+
127+ resolve ( {
128+ routingFeeMsat : toPositiveNumber ( res . routing_fee_msat ) ,
129+ timeLockDelay : toPositiveNumber ( res . time_lock_delay )
75130 } )
76131 } )
77- } catch ( err ) {
78- if ( request && failureReason === 'FAILURE_REASON_ERROR' ) {
79- // try again without the payment request
80- // there appears to be a compatibility bug when probing ldk nodes with payment requests
81- // https://github.com/lightningnetwork/lnd/discussions/10427
82- return await estimateRouteFee ( { lnd, destination, tokens, mtokens, timeout } )
83- }
84- throw err
85- }
132+ } )
86133}
87134
88135// created_height is the accepted_height, timeout is the expiry height
0 commit comments