Skip to content

Commit

Permalink
Implement estimateRouteFee #167
Browse files Browse the repository at this point in the history
  • Loading branch information
bnonni committed Jan 4, 2025
1 parent 0bdd2a7 commit 6c50d1e
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 0 deletions.
2 changes: 2 additions & 0 deletions lnd_methods/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ const {getPublicKey} = require('./address');
const {getRouteConfidence} = require('./generic');
const {getRouteThroughHops} = require('./offchain');
const {getRouteToDestination} = require('./info');
const {getRoutingFeeEstimate} = require('./offchain');
const {getSettlementStatus} = require('./offchain');
const {getSweepTransactions} = require('./onchain');
const {getTowerServerInfo} = require('./info');
Expand Down Expand Up @@ -237,6 +238,7 @@ module.exports = {
getRouteConfidence,
getRouteThroughHops,
getRouteToDestination,
getRoutingFeeEstimate,
getSettlementStatus,
getSweepTransactions,
getTowerServerInfo,
Expand Down
32 changes: 32 additions & 0 deletions lnd_methods/offchain/get_routing_fee_estimate.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {AuthenticatedLnd} from '../../lnd_grpc';
import {
AuthenticatedLightningArgs,
AuthenticatedLightningMethod
} from '../../typescript';

export type GetRoutingFeeEstimateRequest = AuthenticatedLightningArgs<{
lnd: AuthenticatedLnd;
/** BOLT 11 Encoded Payment Request */
request: string;
/** Optional Timeout in Milliseconds */
timeout: number;
}>;

export type GetRoutingFeeEstimateResponse = {
/** Sats (Routing Fee Sats) */
fee: number;
/** Timeout (Time Lock Delay) */
timeout: string;
};

/**
* Estimate routing fees based on an invoice.
*
* Requires `offchain:read` permission
*
* This method is not supported before LND 0.18.4
*/
export const getRoutingFeeEstimate: AuthenticatedLightningMethod<
GetRoutingFeeEstimateRequest,
GetRoutingFeeEstimateResponse
>;
90 changes: 90 additions & 0 deletions lnd_methods/offchain/get_routing_fee_estimate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
const asyncAuto = require('async/auto');
const {returnResult} = require('asyncjs-util');

/**
* Estimate routing fees based on an invoice.
*
* Requires `offchain:read` permission
*
* This method is not supported before LND 0.18.4
*
@argument
{
lnd: <Authenticated LND API Object>
request: <BOLT 11 Payment Request String>
timeout: <Optional Timeout in Milliseconds Number>
}
@returns via cbk or Promise
{
fee: <Route Fee Sats Number>
timeout: <Time Lock Block Height Delay String>
}
*/
module.exports = ({lnd, request, timeout}, cbk) => {
return new Promise((resolve, reject) => {
return asyncAuto({
// Check arguments
validate: cbk => {
if (!lnd || !lnd.router) {
return cbk([400, 'ExpectedAuthenticatedLndForGetRoutingFeeEstimate']);
}

if (!request) {
return cbk([400, 'ExpectedPaymentRequestStringForGetRoutingFeeEstimate']);
}

if (timeout > 86400000) {
return cbk([400, 'ExpectedTimeoutLessThanOneDayForGetRoutingFeeEstimate']);
}

if (timeout < 1) {
return cbk([400, 'ExpectedTimeoutGreaterThanZeroForGetRoutingFeeEstimate']);
}

timeout = !timeout ? 60 : timeout / 1000;

return cbk();
},

// Estimate route fee and return a successful routing fee and timeout or failure reason
getEstimate: ['validate', ({}, cbk) => {
return lnd.router.estimateRouteFee({request, timeout},
(err, res) => {
if (err) {
return cbk([503, 'UnexpectedGetRoutingFeeEstimateError', {err}]);
}

if (!res) {
return cbk([503, 'ExpectedGetRoutingFeeEstimateResponse']);
}

const mtokenFee = Number(res.fee);
if (!mtokenFee) {
return cbk([503, 'ExpectedFeeInGetRoutingFeeEstimateResponse']);
}

if (isNaN(mtokenFee)) {
return cbk([503, 'ExpectedFeeInGetRoutingFeeEstimateResponseToBeNumber']);
}

if (!isFinite(mtokenFee)) {
return cbk([503, 'ExpectedFeeInGetRoutingFeeEstimateResponseToBeFinite']);
}

if (!res.timeout) {
return cbk([503, 'ExpectedTimeoutInGetRoutingFeeEstimateResponse']);
}

if (res.failure_reason !== 'FAILURE_REASON_NONE') {
return cbk([404, 'GetRoutingFeeEstimateFailed', {failure: res.failure_reason}])
}

const fee = mtokenFee > 0 ? mtokenFee / 1000 : mtokenFee;
return cbk(null, {fee, timeout: res.timeout});
});
}],
},
returnResult({reject, resolve, of: 'getEstimate'}, cbk));
});
};
1 change: 1 addition & 0 deletions lnd_methods/offchain/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export * from './get_payments';
export * from './get_pending_channels';
export * from './get_pending_payments';
export * from './get_route_through_hops';
export * from './get_routing_fee_estimate';
export * from './get_settlement_status';
export * from './is_destination_payable';
export * from './pay_via_payment_details';
Expand Down
2 changes: 2 additions & 0 deletions lnd_methods/offchain/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const getPayments = require('./get_payments');
const getPendingChannels = require('./get_pending_channels');
const getPendingPayments = require('./get_pending_payments');
const getRouteThroughHops = require('./get_route_through_hops');
const getRoutingFeeEstimate = require('./get_routing_fee_estimate');
const getSettlementStatus = require('./get_settlement_status');
const isDestinationPayable = require('./is_destination_payable');
const pay = require('./pay');
Expand Down Expand Up @@ -86,6 +87,7 @@ module.exports = {
getPendingChannels,
getPendingPayments,
getRouteThroughHops,
getRoutingFeeEstimate,
getSettlementStatus,
isDestinationPayable,
pay,
Expand Down
70 changes: 70 additions & 0 deletions test/lnd_methods/offchain/test_get_routing_fee_estimate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
const {rejects, deepStrictEqual} = require('node:assert').strict;
const test = require('node:test');
const getRoutingFeeEstimate = require('../../../lnd_methods/offchain/get_routing_fee_estimate');

const request = 'lnbcrt500u1pnh3r5dpp57cppte59jvmxnaunh03ecy6wchq8e0zh70n0nzsamaxztqxevcusdqqcqzzsxqyz5vqsp587ua488ttsts8cs97ekt9axdla3jmq4mj2h7xj7g6rw37fu65yqs9qxpqysgql27u5p9m2xv0r0pjykzvcgs88azzfkywzw2xw5q6u86qnwzrut94mks86zxelhltdtn6vnqgd8hay433wwq7uvple709gp7pmwmtzwcqakyevc';

/** Function */
const makeLnd = ({err, res}) => {
const response = {
fee: 1050,
timeout: '3520',
failure_reason: 'FAILURE_REASON_NONE'
};
return {
router: {
estimateRouteFee: ({}, cbk) => cbk(err, res !== undefined ? res : response),
}
};
};

const makeArgs = override => {
const args = {request, timeout: 60000, lnd: makeLnd({})};
Object.keys(override || {}).forEach(key => args[key] = override[key]);
return args;
};

const tests = [
{
args: makeArgs({lnd: undefined}),
description: 'LND is required',
error: [400, 'ExpectedAuthenticatedLndForGetRoutingFeeEstimate'],
},
{
args: makeArgs({request: undefined}),
description: 'Request is required',
error: [400, 'ExpectedPaymentRequestStringForGetRoutingFeeEstimate'],
},
{
args: makeArgs({timeout: 86400001}),
description: 'Timeout must be less than or equal to 86400000 milliseconds',
error: [400, 'ExpectedTimeoutLessThanOneDayForGetRoutingFeeEstimate'],
},
{
args: makeArgs({timeout: 0}),
description: 'Timeout must be greater than 0 milliseconds',
error: [400, 'ExpectedTimeoutGreaterThanZeroForGetRoutingFeeEstimate'],
},
{
args: makeArgs({request, lnd: makeLnd({})}),
description: 'A route fee number in sats is expected for default timeout 60000 milliseconds',
expected: {fee: 1.05, timeout: '3520'}
},
{
args: makeArgs({request, timeout: 86400000, lnd: makeLnd({})}),
description: 'A route fee number in sats is expected for timeout 86400000 milliseconds',
expected: {fee: 1.05, timeout: '3520'}
}
];

tests.forEach(({args, description, error, expected}) => {
return test(description, async () => {
if (!!error) {
await rejects(getRoutingFeeEstimate(args), error, 'Got expected error');
} else {
deepStrictEqual(await getRoutingFeeEstimate(args), expected, 'Got result');
}

return;
});
});

0 comments on commit 6c50d1e

Please sign in to comment.