Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement estimateRouteFee #193

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ const {getPublicKey} = require('./lnd_methods');
const {getRouteConfidence} = require('./lnd_methods');
const {getRouteThroughHops} = require('./lnd_methods');
const {getRouteToDestination} = require('./lnd_methods');
const {getRoutingFeeEstimate} = require('./lnd_methods');
const {getSettlementStatus} = require('./lnd_methods');
const {getSweepTransactions} = require('./lnd_methods');
const {getTowerServerInfo} = require('./lnd_methods');
Expand Down Expand Up @@ -239,6 +240,7 @@ module.exports = {
getRouteConfidence,
getRouteThroughHops,
getRouteToDestination,
getRoutingFeeEstimate,
getSettlementStatus,
getSweepTransactions,
getTowerServerInfo,
Expand Down
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;
});
});