mirror of
https://github.com/alexbosworth/balanceofsatoshis.git
synced 2026-08-13 12:33:37 +02:00
add price increase rejection
This commit is contained in:
parent
9e9538e456
commit
f9266e4efc
6 changed files with 157 additions and 67 deletions
|
|
@ -1,5 +1,9 @@
|
|||
# Versions
|
||||
|
||||
## 15.5.0
|
||||
|
||||
- `invoice`: Add `--reject-on-amount-increase` to reject on price moves
|
||||
|
||||
## 15.4.3
|
||||
|
||||
- `accounting`: Improve `invoices` report generation time on LND 0.16.0+
|
||||
|
|
|
|||
3
bos
3
bos
|
|
@ -1043,12 +1043,14 @@ prog
|
|||
.help(`Fiat rate providers: ${priceProviders.join(', ')}`)
|
||||
.help('--virtual invoices cannot be used with payers who probe before pay')
|
||||
.help('Only one --virtual invoice can be active at a time')
|
||||
.help('--reject-on-amount-increase can only be used with --virtual invoices')
|
||||
.argument('[amount]', 'Amount for invoice', STRING, '0')
|
||||
.option('--for <description>', 'What is the invoice requesting payment for')
|
||||
.option('--hours <expires_in>', 'Hours invoice is valid for', INT)
|
||||
.option('--include-hints', 'Include the default set of hop hint channels')
|
||||
.option('--node <node_name>', 'Use saved node to create invoice')
|
||||
.option('--rate-provider <rate_provider>', 'Rate provider', priceProviders)
|
||||
.option('--reject-on-amount-increase', 'Reject if required amount increases')
|
||||
.option('--select-hints', 'Select hop hints to be added to the request')
|
||||
.option('--virtual', 'Request payment over a virtual channel')
|
||||
.option('--virtual-fee-rate <pm>', 'Fee rate to use on virtual channel', INT)
|
||||
|
|
@ -1062,6 +1064,7 @@ prog
|
|||
description: options.for,
|
||||
expires_in: options.hours,
|
||||
is_hinting: options.includeHints || undefined,
|
||||
is_rejecting_option: !!options.rejectOnAmountIncrease || undefined,
|
||||
is_selecting_hops: options.selectHints || undefined,
|
||||
is_virtual: options.virtual || undefined,
|
||||
lnd: (await lnd.authenticatedLnd({logger, node: options.node})).lnd,
|
||||
|
|
|
|||
|
|
@ -7,29 +7,24 @@ const {getChannel} = require('ln-service');
|
|||
const {getIdentity} = require('ln-service');
|
||||
const {getNetwork} = require('ln-sync');
|
||||
const {getNodeAlias} = require('ln-sync');
|
||||
const {getPrices} = require('@alexbosworth/fiat');
|
||||
const {parseAmount} = require('ln-accounting');
|
||||
const {parsePaymentRequest} = require('ln-service');
|
||||
const {returnResult} = require('asyncjs-util');
|
||||
const {subscribeToForwardRequests} = require('ln-service');
|
||||
|
||||
const getInvoiceAmount = require('./get_invoice_amount');
|
||||
const signPaymentRequest = require('./sign_payment_request');
|
||||
|
||||
const coins = ['BTC'];
|
||||
const defaultFiatRateProvider = 'coinbase';
|
||||
const defaultInvoiceDescription = '';
|
||||
const defaultTimeoutCheckMs = 1000 * 60 * 3;
|
||||
const fiats = ['EUR', 'USD'];
|
||||
const hasFiat = n => /(eur|usd)/gim.test(n);
|
||||
const hoursFromNow = h => new Date(Date.now() + (h * 3600000)).toISOString();
|
||||
const interval = 3000;
|
||||
const {isArray} = Array;
|
||||
const {isInteger} = Number;
|
||||
const isNumber = n => !isNaN(n);
|
||||
const mtokensAsBigUnit = n => (Number(n / BigInt(1000)) / 1e8).toFixed(8);
|
||||
const networks = {btc: 'BTC', btctestnet: 'BTC', btcregtest: 'BTC'};
|
||||
const parseRequest = request => parsePaymentRequest({request});
|
||||
const rateAsTokens = rate => 1e10 / rate;
|
||||
const times = 20 * 60 * 24;
|
||||
const tokensAsBigUnit = tokens => (tokens / 1e8).toFixed(8);
|
||||
const uniq = arr => Array.from(new Set(arr));
|
||||
|
|
@ -42,6 +37,7 @@ const uniq = arr => Array.from(new Set(arr));
|
|||
[description]: <Invoice Description String>
|
||||
[expires_in]: <Invoice Expires In Hours Number>
|
||||
[is_hinting]: <Include Private Channels Bool>
|
||||
[is_rejecting_option]: <Is Rejecting Amount Increases Bool>
|
||||
[is_selecting_hops]: <Is Selecting Hops Bool>
|
||||
[is_virtual]: <Is Using Virtual Channel for Invoice Bool>
|
||||
lnd: <Authenticated LND API Object>
|
||||
|
|
@ -81,6 +77,10 @@ module.exports = (args, cbk) => {
|
|||
return cbk([400, 'CannotUseDefaultHintsAndAlsoSelectHints']);
|
||||
}
|
||||
|
||||
if (!!args.is_rejecting_option && !args.is_virtual) {
|
||||
return cbk([501, 'RejectingAmountChangesOnlySupportedWhenVirtual']);
|
||||
}
|
||||
|
||||
if (!!args.is_virtual && !!args.is_hinting) {
|
||||
return cbk([400, 'UsingHopHintsIsUnsupportedWithVirtualChannels']);
|
||||
}
|
||||
|
|
@ -114,21 +114,6 @@ module.exports = (args, cbk) => {
|
|||
return cbk(null, hoursFromNow(args.expires_in));
|
||||
}],
|
||||
|
||||
// Get the current price of BTC in USD/EUR
|
||||
getFiatPrice: ['validate', ({}, cbk) => {
|
||||
// Exit early when no fiat is referenced
|
||||
if (!hasFiat(args.amount)) {
|
||||
return cbk();
|
||||
}
|
||||
|
||||
return getPrices({
|
||||
from: args.rate_provider || defaultFiatRateProvider,
|
||||
request: args.request,
|
||||
symbols: [].concat(fiats),
|
||||
},
|
||||
cbk);
|
||||
}],
|
||||
|
||||
// Get channels to allow for selecting individual hop hints
|
||||
getChannels: ['validate', ({}, cbk) => {
|
||||
// Exit early when not selecting hop hints
|
||||
|
|
@ -165,48 +150,15 @@ module.exports = (args, cbk) => {
|
|||
// Get wallet info
|
||||
getId: ['validate', ({}, cbk) => getIdentity({lnd: args.lnd}, cbk)],
|
||||
|
||||
// Fiat rates
|
||||
rates: [
|
||||
'getFiatPrice',
|
||||
'getNetwork',
|
||||
({getFiatPrice, getNetwork}, cbk) =>
|
||||
{
|
||||
// Exit early when there is no fiat
|
||||
if (!getFiatPrice) {
|
||||
return cbk();
|
||||
}
|
||||
|
||||
if (!networks[getNetwork.network]) {
|
||||
return cbk([400, 'UnsupportedNetworkForFiatPriceConversion']);
|
||||
}
|
||||
|
||||
const rates = fiats.map(fiat => {
|
||||
const {rate} = getFiatPrice.tickers.find(n => n.ticker === fiat);
|
||||
|
||||
return {fiat, unit: rateAsTokens(rate)};
|
||||
});
|
||||
|
||||
return cbk(null, rates);
|
||||
}],
|
||||
|
||||
// Parse the amount
|
||||
parseAmount: ['rates', ({rates}, cbk) => {
|
||||
const eur = !!rates ? rates.find(n => n.fiat === 'EUR') : null;
|
||||
const usd = !!rates ? rates.find(n => n.fiat === 'USD') : null;
|
||||
|
||||
// Variables to use in amount
|
||||
const variables = {
|
||||
eur: !!eur ? eur.unit : undefined,
|
||||
usd: !!usd ? usd.unit : undefined,
|
||||
};
|
||||
|
||||
try {
|
||||
const {tokens} = parseAmount({variables, amount: args.amount});
|
||||
|
||||
return cbk(null, {tokens});
|
||||
} catch (err) {
|
||||
return cbk([400, 'FailedToParseAmount', {err}]);
|
||||
}
|
||||
// Get tokens to invoice from the amount
|
||||
parseAmount: ['getNetwork', ({getNetwork}, cbk) => {
|
||||
return getInvoiceAmount({
|
||||
amount: args.amount,
|
||||
network: getNetwork.network,
|
||||
provider: args.rate_provider || defaultFiatRateProvider,
|
||||
request: args.request,
|
||||
},
|
||||
cbk);
|
||||
}],
|
||||
|
||||
// Select hop hint channels
|
||||
|
|
@ -296,7 +248,11 @@ module.exports = (args, cbk) => {
|
|||
}],
|
||||
|
||||
// Intercept virtual invoice forwards
|
||||
interceptVirtualInvoice: ['addInvoice', ({addInvoice}, cbk) => {
|
||||
interceptVirtualInvoice: [
|
||||
'addInvoice',
|
||||
'getNetwork',
|
||||
({addInvoice, getNetwork}, cbk) =>
|
||||
{
|
||||
// Exit early when not intercepting the virtual forward
|
||||
if (!args.is_virtual) {
|
||||
return cbk();
|
||||
|
|
@ -350,6 +306,32 @@ module.exports = (args, cbk) => {
|
|||
return forward.reject({});
|
||||
}
|
||||
|
||||
// Check for optionality
|
||||
if (!!args.is_rejecting_option) {
|
||||
try {
|
||||
const {tokens} = await getInvoiceAmount({
|
||||
amount: args.amount,
|
||||
network: getNetwork.network,
|
||||
provider: args.rate_provider || defaultFiatRateProvider,
|
||||
request: args.request,
|
||||
});
|
||||
|
||||
// Exit early and reject when the received tokens is too low
|
||||
if (tokens > addInvoice.tokens) {
|
||||
args.logger.error({
|
||||
rejected: true,
|
||||
invoice_acceptable_amount_increased: tokens,
|
||||
});
|
||||
|
||||
return forward.reject({});
|
||||
}
|
||||
} catch (err) {
|
||||
args.logger.error({failed_to_get_invoice_amount: err});
|
||||
|
||||
return forward.reject({});
|
||||
}
|
||||
}
|
||||
|
||||
args.logger.info({accepting_payment: true});
|
||||
|
||||
forward.settle({secret: addInvoice.secret});
|
||||
|
|
|
|||
101
offchain/get_invoice_amount.js
Normal file
101
offchain/get_invoice_amount.js
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
const asyncAuto = require('async/auto');
|
||||
const {getPrices} = require('@alexbosworth/fiat');
|
||||
const {parseAmount} = require('ln-accounting');
|
||||
const {returnResult} = require('asyncjs-util');
|
||||
|
||||
const defaultFiatRateProvider = 'coinbase';
|
||||
const fiats = ['EUR', 'USD'];
|
||||
const hasFiat = n => /(eur|usd)/gim.test(n);
|
||||
const networks = {btc: 'BTC', btctestnet: 'BTC', btcregtest: 'BTC'};
|
||||
const rateAsTokens = rate => 1e10 / rate;
|
||||
|
||||
/** Get an amount to invoice
|
||||
|
||||
{
|
||||
amount: <Invoice Amount String>
|
||||
lnd: <Authenticated LND API Object>
|
||||
[provider]: <Fiat Rate Provider String>
|
||||
request: <Request Function>
|
||||
}
|
||||
|
||||
@returns via cbk or Promise
|
||||
{
|
||||
tokens: <Invoice Tokens Number>
|
||||
}
|
||||
*/
|
||||
module.exports = ({amount, network, provider, request}, cbk) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
return asyncAuto({
|
||||
// Check arguments
|
||||
validate: cbk => {
|
||||
if (!amount) {
|
||||
return cbk([400, 'ExpectedAmountValueToGetInvoiceAmount']);
|
||||
}
|
||||
|
||||
if (!network) {
|
||||
return cbk([400, 'ExpectedNetworkNameToGetInvoiceAmount']);
|
||||
}
|
||||
|
||||
if (!request) {
|
||||
return cbk([400, 'ExpectedRequestFunctionToGetInvoicePrice']);
|
||||
}
|
||||
|
||||
return cbk();
|
||||
},
|
||||
|
||||
// Get the current price of BTC in USD/EUR
|
||||
getFiatPrice: ['validate', ({}, cbk) => {
|
||||
// Exit early when no fiat is referenced
|
||||
if (!hasFiat(amount)) {
|
||||
return cbk();
|
||||
}
|
||||
|
||||
return getPrices({
|
||||
request,
|
||||
from: provider || defaultFiatRateProvider,
|
||||
symbols: [].concat(fiats),
|
||||
},
|
||||
cbk);
|
||||
}],
|
||||
|
||||
// Fiat rates
|
||||
rates: ['getFiatPrice', ({getFiatPrice}, cbk) => {
|
||||
// Exit early when there is no fiat
|
||||
if (!getFiatPrice) {
|
||||
return cbk();
|
||||
}
|
||||
|
||||
if (!networks[network]) {
|
||||
return cbk([400, 'UnsupportedNetworkForFiatPriceConversion']);
|
||||
}
|
||||
|
||||
const rates = fiats.map(fiat => {
|
||||
const {rate} = getFiatPrice.tickers.find(n => n.ticker === fiat);
|
||||
|
||||
return {fiat, unit: rateAsTokens(rate)};
|
||||
});
|
||||
|
||||
return cbk(null, rates);
|
||||
}],
|
||||
|
||||
// Parse the amount
|
||||
parseAmount: ['rates', ({rates}, cbk) => {
|
||||
const eur = !!rates ? rates.find(n => n.fiat === 'EUR') : null;
|
||||
const usd = !!rates ? rates.find(n => n.fiat === 'USD') : null;
|
||||
|
||||
// Variables to use in amount
|
||||
const variables = {
|
||||
eur: !!eur ? eur.unit : undefined,
|
||||
usd: !!usd ? usd.unit : undefined,
|
||||
};
|
||||
|
||||
try {
|
||||
return cbk(null, {tokens: parseAmount({amount, variables}).tokens});
|
||||
} catch (err) {
|
||||
return cbk([400, 'FailedToParseAmount', {err}]);
|
||||
}
|
||||
}],
|
||||
},
|
||||
returnResult({reject, resolve, of: 'parseAmount'}, cbk));
|
||||
});
|
||||
};
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "balanceofsatoshis",
|
||||
"version": "15.4.3",
|
||||
"version": "15.5.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "balanceofsatoshis",
|
||||
"version": "15.4.3",
|
||||
"version": "15.5.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@alexbosworth/caporal": "1.4.4",
|
||||
|
|
|
|||
|
|
@ -83,5 +83,5 @@
|
|||
"postpublish": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t alexbosworth/balanceofsatoshis -t alexbosworth/balanceofsatoshis:$npm_package_version --push .",
|
||||
"test": "tap --branches=1 --functions=1 --lines=1 --statements=1 -t 60 test/arrays/*.js test/balances/*.js test/chain/*.js test/display/*.js test/encryption/*.js test/lnd/*.js test/network/*.js test/nodes/*.js test/peers/*.js test/responses/*.js test/routing/*.js test/services/*.js test/swaps/*.js test/tags/*.js test/telegram/*.js test/wallets/*.js"
|
||||
},
|
||||
"version": "15.4.3"
|
||||
"version": "15.5.0"
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue