balanceofsatoshis 12.0.2 → 12.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Versions
2
2
 
3
+ ## 12.3.0
4
+
5
+ - `lnurl`: Add support for the `auth` function to authenticate with a node key
6
+
7
+ ## 12.2.0
8
+
9
+ - `swap`: Add utility command for testing submarine swaps
10
+
11
+ ## 12.1.0
12
+
13
+ - `lnurl`: Add support for the `withdraw` function to send a payment request
14
+
3
15
  ## 12.0.2
4
16
 
5
17
  - `open-balanced-channel`: Fix display issue where `undefined` was printed
package/bos CHANGED
@@ -1023,6 +1023,9 @@ prog
1023
1023
  .command('lnurl', 'Collection of lnurl features')
1024
1024
  .argument('<function>', 'lnurl function', keys(lnurlFunctions))
1025
1025
  .help(`Functions: ${keys(lnurlFunctions).join(', ')}`)
1026
+ .help('lnurl auth will request authorization')
1027
+ .help('lnurl pay will request a payment request from a service')
1028
+ .help('lnurl withdraw will create an invoice and send it to a service')
1026
1029
  .option('--avoid <avoid>', 'Avoid forwarding via node/chan/tag', REPEATABLE)
1027
1030
  .option('--max-fee <max_fee>', 'Maximum fee to pay', INT, 1337)
1028
1031
  .option('--max-paths <max_paths>', 'Maximum paths to use', INT, 1)
@@ -1032,7 +1035,7 @@ prog
1032
1035
  .action((args, options, logger) => {
1033
1036
  return new Promise(async (resolve, reject) => {
1034
1037
  try {
1035
- return lnurl.pay({
1038
+ return lnurl.manageLnurl({
1036
1039
  logger,
1037
1040
  ask: (n, cbk) => inquirer.prompt(n).then(n => cbk(n)),
1038
1041
  avoid: flatten([options.avoid].filter(n => !!n)),
@@ -1624,6 +1627,26 @@ prog
1624
1627
  });
1625
1628
  })
1626
1629
 
1630
+ // Submarine swap
1631
+ .command('swap', 'Trade off-chain coins for on-chain via submarine swap')
1632
+ .visible(false)
1633
+ .option('--node <node_name>', 'Node to use for swap')
1634
+ .action((args, options, logger) => {
1635
+ return new Promise(async (resolve, reject) => {
1636
+ try {
1637
+ return paidServices.manageSwap({
1638
+ logger,
1639
+ ask: (n, cbk) => inquirer.prompt([n]).then(res => cbk(res)),
1640
+ lnd: (await lnd.authenticatedLnd({logger, node: options.node})).lnd,
1641
+ request: commands.simpleRequest,
1642
+ },
1643
+ responses.returnObject({exit, logger, reject, resolve}));
1644
+ } catch (err) {
1645
+ return logger.error({err}) && reject();
1646
+ }
1647
+ });
1648
+ })
1649
+
1627
1650
  // Create submarine swap to convert on-chain funds to off-chain
1628
1651
  .command('swap-in', 'Trade on-chain coins for off-chain via submarine swap')
1629
1652
  .argument('[amount]', 'Amount to receive', INT, 1e6)
@@ -8,7 +8,9 @@
8
8
  "payments": "payments"
9
9
  },
10
10
  "lnurlFunctions": {
11
- "pay": "pay"
11
+ "auth": "auth",
12
+ "pay": "pay",
13
+ "withdraw": "withdraw"
12
14
  },
13
15
  "peerSortOptions": [
14
16
  "alias",
package/lnurl/auth.js ADDED
@@ -0,0 +1,178 @@
1
+ const asyncAuto = require('async/auto');
2
+ const {bech32} = require('bech32');
3
+ const {returnResult} = require('asyncjs-util');
4
+ const {signMessage} = require('ln-service');
5
+ const tinysecp = require('tiny-secp256k1');
6
+
7
+ const signAuthChallenge = require('./sign_auth_challenge');
8
+
9
+ const actionKey = 'action';
10
+ const {decode} = bech32;
11
+ const defaultAction = 'authenticate';
12
+ const asLnurl = n => n.substring(n.startsWith('lightning:') ? 10 : 0);
13
+ const bech32CharLimit = 2000;
14
+ const challengeKey = 'k1';
15
+ const errorStatus = 'ERROR';
16
+ const knownActions = ['auth', 'link', 'login', 'register'];
17
+ const okStatus = 'OK';
18
+ const prefix = 'lnurl';
19
+ const tlsProtocol = 'https:';
20
+ const lud13AuthPhrase = 'DO NOT EVER SIGN THIS TEXT WITH YOUR PRIVATE KEYS! IT IS ONLY USED FOR DERIVATION OF LNURL-AUTH HASHING-KEY, DISCLOSING ITS SIGNATURE WILL COMPROMISE YOUR LNURL-AUTH IDENTITY AND MAY LEAD TO LOSS OF FUNDS!';
21
+ const wordsAsUtf8 = n => Buffer.from(bech32.fromWords(n)).toString('utf8');
22
+
23
+ /** Authenticate using lnurl
24
+
25
+ {
26
+ ask: <Ask Function>
27
+ request: <Request Function>
28
+ lnd: <Authenticated LND API Object>
29
+ lnurl: <Lnurl String>
30
+ logger: <Winston Logger Object>
31
+ }
32
+
33
+ @returns via cbk or Promise
34
+ */
35
+ module.exports = (args, cbk) => {
36
+ return new Promise((resolve, reject) => {
37
+ return asyncAuto({
38
+ // Import the ECPair library
39
+ ecp: async () => (await import('ecpair')).ECPairFactory(tinysecp),
40
+
41
+ // Check arguments
42
+ validate: cbk => {
43
+ if (!args.ask) {
44
+ return cbk([400, 'ExpectedAskFunctionToAuthenticateUsingLnurl']);
45
+ }
46
+
47
+ if (!args.lnurl) {
48
+ return cbk([400, 'ExpectedUrlToAuthenticateToLnurl']);
49
+ }
50
+
51
+ try {
52
+ decode(asLnurl(args.lnurl), bech32CharLimit);
53
+ } catch (err) {
54
+ return cbk([400, 'FailedToDecodeLnurlToAuthenticate', {err}]);
55
+ }
56
+
57
+ if (decode(asLnurl(args.lnurl), bech32CharLimit).prefix !== prefix) {
58
+ return cbk([400, 'ExpectedLnUrlPrefixToAuthenticate']);
59
+ }
60
+
61
+ if (!args.lnd) {
62
+ return cbk([400, 'ExpectedLndToAuthenticateUsingLnurl']);
63
+ }
64
+
65
+ if (!args.logger) {
66
+ return cbk([400, 'ExpectedLoggerToAuthenticateUsingLnurl']);
67
+ }
68
+
69
+ if (!args.request) {
70
+ return cbk([400, 'ExpectedRequestFunctionToGetLnurlAuthentication']);
71
+ }
72
+
73
+ return cbk();
74
+ },
75
+
76
+ // Parse the encoded Lnurl
77
+ parse: ['validate', ({}, cbk) => {
78
+ const {words} = decode(asLnurl(args.lnurl), bech32CharLimit);
79
+
80
+ const url = wordsAsUtf8(words);
81
+
82
+ try {
83
+ new URL(url);
84
+ } catch (err) {
85
+ return cbk([400, 'ExpectedValidCallbackUrlInDecodedLnurlForAuth']);
86
+ }
87
+
88
+ const {hostname, protocol, searchParams} = new URL(url);
89
+
90
+ if (protocol !== tlsProtocol) {
91
+ return cbk([501, 'UnsupportedUrlProtocolForLnurlAuthentication']);
92
+ }
93
+
94
+ const action = searchParams.get(actionKey);
95
+
96
+ if (!!action && !knownActions.includes(action)) {
97
+ return cbk([503, 'UnknownAuthenticationActionForLnurlAuth']);
98
+ }
99
+
100
+ const k1 = searchParams.get(challengeKey);
101
+
102
+ if (!k1) {
103
+ return cbk([503, 'ExpectedChallengeK1ValueInDecodedLnurlForAuth']);
104
+ }
105
+
106
+ return cbk(null, {hostname, k1, url, action: action || defaultAction});
107
+ }],
108
+
109
+ // Sign the canonical phrase for LUD-13 signMessage based seed generation
110
+ seed: ['parse', ({}, cbk) => {
111
+ return signMessage({lnd: args.lnd, message: lud13AuthPhrase}, cbk);
112
+ }],
113
+
114
+ // Derive keys and get signatures
115
+ sign: ['ecp', 'parse', 'seed', ({ecp, parse, seed}, cbk) => {
116
+ const sign = signAuthChallenge({
117
+ ecp,
118
+ hostname: parse.hostname,
119
+ k1: parse.k1,
120
+ seed: seed.signature,
121
+ });
122
+
123
+ return cbk(null, {
124
+ public_key: sign.public_key,
125
+ signature: sign.signature,
126
+ });
127
+ }],
128
+
129
+ // Display confirmation dialog with domain name and action
130
+ ok: ['parse', 'sign', ({parse, sign}, cbk) => {
131
+ return args.ask({
132
+ default: true,
133
+ message: `Do you want to ${parse.action} with ${parse.hostname}?`,
134
+ name: 'ok',
135
+ type: 'confirm',
136
+ },
137
+ ({ok}) => cbk(null, ok));
138
+ }],
139
+
140
+ // Transmit authenticating signature and key to the host
141
+ send: ['ok', 'parse', 'sign', ({ok, parse, sign}, cbk) => {
142
+ if (!ok) {
143
+ return cbk([400, 'AuthenticationCanceled']);
144
+ }
145
+
146
+ args.logger.info({sending_authentication: sign.public_key});
147
+
148
+ return args.request({
149
+ json: true,
150
+ qs: {key: sign.public_key, sig: sign.signature},
151
+ url: parse.url,
152
+ },
153
+ (err, r, json) => {
154
+ if (!!err) {
155
+ return cbk([503, 'FailedToGetLnurlAuthenticationData', {err}]);
156
+ }
157
+
158
+ if (!json) {
159
+ return cbk([503, 'ExpectedJsonReturnedInLnurlResponseForAuth']);
160
+ }
161
+
162
+ if (json.status === errorStatus) {
163
+ return cbk([503, 'LnurlAuthenticationFail', {err: json.reason}]);
164
+ }
165
+
166
+ if (json.status !== okStatus) {
167
+ return cbk([503, 'ExpectedOkStatusInLnurlResponseJsonForAuth']);
168
+ }
169
+
170
+ args.logger.info({is_authenticated: true});
171
+
172
+ return cbk();
173
+ });
174
+ }],
175
+ },
176
+ returnResult({reject, resolve}, cbk));
177
+ });
178
+ };
@@ -0,0 +1,37 @@
1
+ const asDer = n => (n[0]&128)?Buffer.concat([Buffer.alloc(1),n],1+n.length):n;
2
+ const bufferAsHex = buffer => buffer.toString('hex');
3
+ const {concat} = Buffer;
4
+ const decomposeSignature = sig => [sig.slice(0, 32), sig.slice(32, 64)];
5
+ const {from} = Buffer;
6
+ const header = 0x30;
7
+ const hexAsBuffer = hex => Buffer.from(hex, 'hex');
8
+ const int = 0x02;
9
+
10
+ /** DER encode a signature given r and s values
11
+
12
+ {
13
+ signature: <Signature Buffer Object>
14
+ }
15
+
16
+ @returns
17
+ {
18
+ encoded: <DER Encoded Signature Buffer Object>
19
+ }
20
+ */
21
+ module.exports = ({signature}) => {
22
+ // Split the signature for DER encoding
23
+ const [r, s] = decomposeSignature(hexAsBuffer(signature)).map(asDer);
24
+
25
+ const encoded = bufferAsHex(concat([
26
+ from([header]), // Header byte indicating compound structure
27
+ from([r.length + s.length + [int, int, r.length, s.length].length]), // Len
28
+ from([int]), // Integer indicator
29
+ from([r.length]), // Length of data
30
+ r,
31
+ from([int]), // Integer indicator
32
+ from([s.length]), // Length of data
33
+ s,
34
+ ]));
35
+
36
+ return {encoded};
37
+ };
package/lnurl/index.js CHANGED
@@ -1,3 +1,3 @@
1
- const pay = require('./pay');
1
+ const manageLnurl = require('./manage_lnurl');
2
2
 
3
- module.exports = {pay};
3
+ module.exports = {manageLnurl};
@@ -0,0 +1,127 @@
1
+ const asyncAuto = require('async/auto');
2
+ const {returnResult} = require('asyncjs-util');
3
+
4
+ const auth = require('./auth');
5
+ const pay = require('./pay');
6
+ const withdraw = require('./withdraw');
7
+
8
+ const functionAuth = 'auth';
9
+ const functionPay = 'pay';
10
+ const functionWithdraw = 'withdraw';
11
+ const {isArray} = Array;
12
+
13
+ /** Manage Lnurl functions
14
+
15
+ {
16
+ ask: <Ask Function>
17
+ avoid: [<Avoid Forwarding Through String>]
18
+ function: <Lnurl Function String>
19
+ request: <Request Function>
20
+ lnd: <Authenticated LND API Object>
21
+ lnurl: <Lnurl String>
22
+ logger: <Winston Logger Object>
23
+ [max_fee]: <Max Fee Tokens Number>
24
+ [max_paths]: <Maximum Paths Number>
25
+ out: [<Out Through Peer With Public Key Hex String>]
26
+ }
27
+
28
+ @returns via cbk or Promise
29
+ */
30
+ module.exports = (args, cbk) => {
31
+ return new Promise((resolve, reject) => {
32
+ return asyncAuto({
33
+ // Check arguments
34
+ validate: cbk => {
35
+ if (!args.ask) {
36
+ return cbk([400, 'ExpectedAskFunctionToManageLnurl']);
37
+ }
38
+
39
+ if (!isArray(args.avoid)) {
40
+ return cbk([400, 'ExpectedArrayOfAvoidsToManageLnurl']);
41
+ }
42
+
43
+ if (![functionAuth, functionPay, functionWithdraw].includes(args.function)) {
44
+ return cbk([400, 'ExpectedLnurlFunctionToManageLnurl']);
45
+ }
46
+
47
+ if (!args.lnd) {
48
+ return cbk([400, 'ExpectedLndToManageLnurl']);
49
+ }
50
+
51
+ if (!args.lnurl) {
52
+ return cbk([400, 'ExpectedUrlStringToManageLnurl']);
53
+ }
54
+
55
+ if (!args.logger) {
56
+ return cbk([400, 'ExpectedLoggerToManageLnurl']);
57
+ }
58
+
59
+ if (!isArray(args.out)) {
60
+ return cbk([400, 'ExpectedArrayOfOutPeersToManageLnurl']);
61
+ }
62
+
63
+ if (!args.request) {
64
+ return cbk([400, 'ExpectedRequestFunctionToManageLnurl']);
65
+ }
66
+
67
+ return cbk();
68
+ },
69
+
70
+ // Authenticate using lnurl
71
+ auth: ['validate', ({}, cbk) => {
72
+ // Exit early if not lnurl auth
73
+ if (args.function !== functionAuth) {
74
+ return cbk();
75
+ }
76
+
77
+ return auth({
78
+ ask: args.ask,
79
+ lnd: args.lnd,
80
+ lnurl: args.lnurl,
81
+ logger: args.logger,
82
+ request: args.request,
83
+ },
84
+ cbk);
85
+ }],
86
+
87
+ // Pay to lnurl
88
+ pay: ['validate', ({}, cbk) => {
89
+ // Exit early if not lnurl pay
90
+ if (args.function !== functionPay) {
91
+ return cbk();
92
+ }
93
+
94
+ return pay({
95
+ ask: args.ask,
96
+ avoid: args.avoid,
97
+ lnd: args.lnd,
98
+ lnurl: args.lnurl,
99
+ logger: args.logger,
100
+ max_fee: args.max_fee,
101
+ max_paths: args.max_paths,
102
+ out: args.out,
103
+ request: args.request,
104
+ },
105
+ cbk);
106
+ }],
107
+
108
+ // Withdraw from lnurl
109
+ withdraw: ['validate', ({}, cbk) => {
110
+ // Exit early if not lnurl withdraw
111
+ if (args.function !== functionWithdraw) {
112
+ return cbk();
113
+ }
114
+
115
+ return withdraw({
116
+ ask: args.ask,
117
+ lnd: args.lnd,
118
+ lnurl: args.lnurl,
119
+ logger: args.logger,
120
+ request: args.request,
121
+ },
122
+ cbk);
123
+ }],
124
+ },
125
+ returnResult({reject, resolve}, cbk));
126
+ });
127
+ };
package/lnurl/pay.js CHANGED
@@ -36,13 +36,13 @@ const wordsAsUtf8 = n => Buffer.from(bech32.fromWords(n)).toString('utf8');
36
36
  {
37
37
  ask: <Ask Function>
38
38
  avoid: [<Avoid Forwarding Through String>]
39
- fetch: <Fetch Function>
40
39
  lnd: <Authenticated LND API Object>
41
40
  lnurl: <Lnurl String>
42
41
  logger: <Winston Logger Object>
43
42
  max_fee: <Max Fee Tokens Number>
44
43
  max_paths: <Maximum Paths Number>
45
44
  out: [<Out Through Peer With Public Key Hex String>]
45
+ request: <Request Function>
46
46
  }
47
47
  */
48
48
  module.exports = (args, cbk) => {
@@ -58,10 +58,6 @@ module.exports = (args, cbk) => {
58
58
  return cbk([400, 'ExpectedAvoidArrayToGetPaymentRequestFromLnurl']);
59
59
  }
60
60
 
61
- if (!args.request) {
62
- return cbk([400, 'ExpectedRequestFunctionToGetLnurlData']);
63
- }
64
-
65
61
  if (!args.lnurl) {
66
62
  return cbk([400, 'ExpectedUrlToGetPaymentRequestFromLnurl']);
67
63
  }
@@ -96,6 +92,10 @@ module.exports = (args, cbk) => {
96
92
  return cbk([400, 'ExpectedArrayOfOutPeersToPayViaLnurl']);
97
93
  }
98
94
 
95
+ if (!args.request) {
96
+ return cbk([400, 'ExpectedRequestFunctionToGetLnurlData']);
97
+ }
98
+
99
99
  return cbk();
100
100
  },
101
101
 
@@ -0,0 +1,46 @@
1
+ const {createHash} = require('crypto');
2
+ const {createHmac} = require('crypto');
3
+
4
+ const derEncodeSignature = require('./der_encode_signature');
5
+
6
+ const asDer = n => (n[0]&128)?Buffer.concat([Buffer.alloc(1),n],1+n.length):n;
7
+ const bufferAsHex = buffer => buffer.toString('hex');
8
+ const {from} = Buffer;
9
+ const hexAsBuffer = hex => Buffer.from(hex, 'hex');
10
+ const hmacSha256 = (pk, url) => createHmac('sha256', pk).update(url).digest();
11
+ const sha256 = n => createHash('sha256').update(n).digest();
12
+ const utf8AsBuffer = utf8 => Buffer.from(utf8, 'utf8');
13
+
14
+ /** Sign an authentication challenge for LNURL Auth
15
+
16
+ {
17
+ ecp: <ECPair Object>
18
+ hostname: <Domain for Authentication Challenge String>
19
+ k1: <Challenge Nonce String>
20
+ seed: <Seed Signature String>
21
+ }
22
+
23
+ @returns
24
+ {
25
+ public_key: <Signing Identity Public Key Hex String>
26
+ signature: <Signature For Authentication Challenge Hex String>
27
+ }
28
+ */
29
+ module.exports = ({ecp, hostname, k1, seed}) => {
30
+ // LUD-13: LN wallet defines hashingKey as sha256(signature)
31
+ const hashingKey = sha256(utf8AsBuffer(seed));
32
+
33
+ // LUD-13: linkingPrivKey is defined as hmacSha256(hashingKey, domain)
34
+ const linkingPrivKey = hmacSha256(hashingKey, utf8AsBuffer(hostname));
35
+
36
+ // Instantiate the key pair from this derived private key
37
+ const linkingKey = ecp.fromPrivateKey(linkingPrivKey);
38
+
39
+ // Using the host-specific linking key, sign the challenge k1 value
40
+ const signature = bufferAsHex(from(linkingKey.sign(hexAsBuffer(k1))));
41
+
42
+ return {
43
+ public_key: bufferAsHex(linkingKey.publicKey),
44
+ signature: derEncodeSignature({signature}).encoded,
45
+ };
46
+ };
@@ -0,0 +1,226 @@
1
+ const asyncAuto = require('async/auto');
2
+ const {bech32} = require('bech32');
3
+ const {createInvoice} = require('ln-service');
4
+ const {returnResult} = require('asyncjs-util');
5
+
6
+ const {decode} = bech32;
7
+ const asLnurl = n => n.substring(n.startsWith('lightning:') ? 10 : 0);
8
+ const bech32CharLimit = 2000;
9
+ const errorStatus = 'ERROR';
10
+ const isNumber = n => !isNaN(n);
11
+ const minWithdrawable = 1;
12
+ const mtokensAsTokens = n => Math.floor(n / 1000);
13
+ const prefix = 'lnurl';
14
+ const {round} = Math;
15
+ const sslProtocol = 'https:';
16
+ const tag = 'withdrawRequest';
17
+ const tokensAsMillitokens = n => n * 1000;
18
+ const wordsAsUtf8 = n => Buffer.from(bech32.fromWords(n)).toString('utf8');
19
+
20
+ /** Withdraw from lnurl
21
+
22
+ {
23
+ ask: <Ask Function>
24
+ request: <Request Function>
25
+ lnd: <Authenticated LND API Object>
26
+ lnurl: <Lnurl String>
27
+ logger: <Winston Logger Object>
28
+ }
29
+
30
+ @returns via cbk or Promise
31
+ */
32
+ module.exports = (args, cbk) => {
33
+ return new Promise((resolve, reject) => {
34
+ return asyncAuto({
35
+ // Check arguments
36
+ validate: cbk => {
37
+ if (!args.ask) {
38
+ return cbk([400, 'ExpectedAskFunctionToWithdrawFromLnurl']);
39
+ }
40
+
41
+ if (!args.lnurl) {
42
+ return cbk([400, 'ExpectedUrlToWithdrawFromLnurl']);
43
+ }
44
+
45
+ try {
46
+ decode(asLnurl(args.lnurl), bech32CharLimit);
47
+ } catch (err) {
48
+ return cbk([400, 'FailedToDecodeLnurlToWithdraw', {err}]);
49
+ }
50
+
51
+ if (decode(asLnurl(args.lnurl), bech32CharLimit).prefix !== prefix) {
52
+ return cbk([400, 'ExpectedLnUrlPrefixToWithdraw']);
53
+ }
54
+
55
+ if (!args.lnd) {
56
+ return cbk([400, 'ExpectedLndToWithdrawFromLnurl']);
57
+ }
58
+
59
+ if (!args.logger) {
60
+ return cbk([400, 'ExpectedLoggerToWithdrawFromLnurl']);
61
+ }
62
+
63
+ if (!args.request) {
64
+ return cbk([400, 'ExpectedRequestFunctionToGetLnurlWithdrawData']);
65
+ }
66
+
67
+ return cbk();
68
+ },
69
+
70
+ // Get accepted terms from the encoded url
71
+ getTerms: ['validate', ({}, cbk) => {
72
+ const {words} = decode(asLnurl(args.lnurl), bech32CharLimit);
73
+
74
+ const url = wordsAsUtf8(words);
75
+
76
+ return args.request({url, json: true}, (err, r, json) => {
77
+ if (!!err) {
78
+ return cbk([503, 'FailureGettingLnUrlDataFromUrl', {err}]);
79
+ }
80
+
81
+ if (!json) {
82
+ return cbk([503, 'ExpectedJsonObjectReturnedInLnurlResponse']);
83
+ }
84
+
85
+ if (json.status === errorStatus) {
86
+ return cbk([503, 'LnurlWithdrawReturnedErr', {err: json.reason}]);
87
+ }
88
+
89
+ if (!json.callback) {
90
+ return cbk([503, 'ExpectedCallbackInLnurlResponseJson']);
91
+ }
92
+
93
+ try {
94
+ new URL(json.callback);
95
+ } catch (err) {
96
+ return cbk([503, 'ExpectedValidCallbackUrlInLnurlResponseJson']);
97
+ }
98
+
99
+ if ((new URL(json.callback)).protocol !== sslProtocol) {
100
+ return cbk([400, 'LnurlsThatSpecifyNonSslUrlsAreUnsupported']);
101
+ }
102
+
103
+ if (!json.k1) {
104
+ return cbk([503, 'ExpectedK1InLnurlResponseJson']);
105
+ }
106
+
107
+ if (!json.tag) {
108
+ return cbk([503, 'ExpectedTagInLnurlResponseJson']);
109
+ }
110
+
111
+ if (json.tag !== tag) {
112
+ return cbk([503, 'ExpectedTagToBeWithdrawRequestInLnurlResponse']);
113
+ }
114
+
115
+ if (!isNumber(json.minWithdrawable)) {
116
+ return cbk([503, 'ExpectedNumericValueForMinWithdrawable']);
117
+ }
118
+
119
+ if (!isNumber(json.maxWithdrawable)) {
120
+ return cbk([503, 'ExpectedNumericValueForMaxWithdrawable']);
121
+ }
122
+
123
+ if (json.minWithdrawable < minWithdrawable) {
124
+ return cbk([400, 'MinWithdrawableIsLowerThanSupportedValue']);
125
+ }
126
+
127
+ if (json.minWithdrawable > json.maxWithdrawable) {
128
+ return cbk([400, 'MinWithdrawableIsHigherThanMaxWithdrawable']);
129
+ }
130
+
131
+ return cbk(null, {
132
+ description: json.defaultDescription,
133
+ k1: json.k1,
134
+ max: mtokensAsTokens(json.maxWithdrawable),
135
+ min: mtokensAsTokens(json.minWithdrawable),
136
+ url: json.callback,
137
+ });
138
+ });
139
+ }],
140
+
141
+ // Ask amount for invoice
142
+ askAmount: ['getTerms', ({getTerms}, cbk) => {
143
+ const {max} = getTerms;
144
+ const {min} = getTerms;
145
+
146
+ return args.ask({
147
+ default: min,
148
+ message: `Amount to withdraw? (min: ${min}, max: ${max})`,
149
+ name: 'amount',
150
+ type: 'input',
151
+ validate: input => {
152
+ if (!input) {
153
+ return false;
154
+ }
155
+
156
+ // The amount should be numeric
157
+ if (!isNumber(input)) {
158
+ return false;
159
+ }
160
+
161
+ if (round(input) !== Number(input)) {
162
+ return 'Fractional amounts are not supported';
163
+ }
164
+
165
+ if (Number(input) > max) {
166
+ return `Service max withdrawable is ${max}, try a lower amount?`;
167
+ }
168
+
169
+ if (Number(input) < min) {
170
+ return `Service min withdrawable is ${min}, try higher amount?`;
171
+ }
172
+
173
+ return true;
174
+ },
175
+ },
176
+ ({amount}) => cbk(null, tokensAsMillitokens(Number(amount))));
177
+ }],
178
+
179
+ // Create a new payment request for withdrawl
180
+ createInvoice: ['askAmount', ({askAmount}, cbk) => {
181
+ return createInvoice({lnd: args.lnd, mtokens: askAmount}, cbk);
182
+ }],
183
+
184
+ // Send the withdraw request
185
+ withdraw: [
186
+ 'createInvoice',
187
+ 'getTerms',
188
+ ({createInvoice, getTerms}, cbk) =>
189
+ {
190
+ args.logger.info({invoice: createInvoice.request});
191
+
192
+ const {url} = getTerms;
193
+ const {k1} = getTerms;
194
+
195
+ const qs = {k1, pr: createInvoice.request};
196
+
197
+ return args.request({url, qs, json: true}, (err, r, json) => {
198
+ if (!!err) {
199
+ return cbk([503, 'UnexpectedErrorRequestingLnurlWithdraw', {err}]);
200
+ }
201
+
202
+ if (!json) {
203
+ return cbk([503, 'ExpectedJsonObjectReturnedInWithdrawResponse']);
204
+ }
205
+
206
+ if (!json.status) {
207
+ return cbk([503, 'ExpectedStatusInLnurlWithdrawResponseJson']);
208
+ }
209
+
210
+ if (json.status === errorStatus) {
211
+ return cbk([503, 'LnurlWithdrawReqFailed', {err: json.reason}]);
212
+ }
213
+
214
+ if (json.status !== 'OK') {
215
+ return cbk([503, 'ExpectedStatusToBeOkInLnurlResponseJson']);
216
+ }
217
+
218
+ args.logger.info({withdrawal_request_sent: true});
219
+
220
+ return cbk();
221
+ });
222
+ }],
223
+ },
224
+ returnResult({reject, resolve}, cbk));
225
+ });
226
+ };
@@ -7,7 +7,7 @@ const {getChannels} = require('ln-service');
7
7
  const {getIdentity} = require('ln-service');
8
8
  const {getNode} = require('ln-service');
9
9
  const moment = require('moment');
10
- const {parsePaymentRequest} = require('invoices');
10
+ const {parsePaymentRequest} = require('ln-service');
11
11
  const {payViaRoutes} = require('ln-service');
12
12
  const {returnResult} = require('asyncjs-util');
13
13
  const {signBytes} = require('ln-service');
package/package.json CHANGED
@@ -28,31 +28,33 @@
28
28
  "colorette": "2.0.16",
29
29
  "crypto-js": "4.1.1",
30
30
  "csv-parse": "5.0.4",
31
- "goldengate": "11.1.1",
32
- "grammy": "1.7.3",
31
+ "ecpair": "2.0.1",
32
+ "goldengate": "11.2.1",
33
+ "grammy": "1.8.0",
33
34
  "hot-formula-parser": "4.0.0",
34
35
  "import-lazy": "4.0.0",
35
36
  "ini": "3.0.0",
36
37
  "inquirer": "8.2.2",
37
- "invoices": "2.0.5",
38
38
  "ln-accounting": "5.0.6",
39
39
  "ln-service": "53.11.0",
40
40
  "ln-sync": "3.12.0",
41
41
  "ln-telegram": "3.21.1",
42
- "moment": "2.29.2",
43
- "paid-services": "3.14.5",
42
+ "moment": "2.29.3",
43
+ "paid-services": "3.15.1",
44
44
  "probing": "2.0.5",
45
45
  "psbt": "2.0.1",
46
46
  "qrcode-terminal": "0.12.0",
47
47
  "sanitize-filename": "1.6.3",
48
48
  "socks-proxy-agent": "6.2.0-beta.0",
49
49
  "table": "6.8.0",
50
+ "tiny-secp256k1": "2.2.1",
50
51
  "update-notifier": "5.1.0",
51
52
  "window-size": "1.1.1"
52
53
  },
53
54
  "description": "Lightning balance CLI",
54
55
  "devDependencies": {
55
56
  "@alexbosworth/tap": "15.0.11",
57
+ "invoices": "2.0.6",
56
58
  "ln-docker-daemons": "2.2.7",
57
59
  "mock-lnd": "1.4.1",
58
60
  "tiny-secp256k1": "2.2.1"
@@ -82,5 +84,5 @@
82
84
  "postpublish": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t alexbosworth/balanceofsatoshis --push .",
83
85
  "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"
84
86
  },
85
- "version": "12.0.2"
87
+ "version": "12.3.0"
86
88
  }
@@ -3,7 +3,7 @@ const asyncFilter = require('async/filter');
3
3
  const {balancedOpenRequest} = require('paid-services');
4
4
  const {getInvoices} = require('ln-service');
5
5
  const {getPayment} = require('ln-service');
6
- const {parsePaymentRequest} = require('invoices');
6
+ const {parsePaymentRequest} = require('ln-service');
7
7
  const {returnResult} = require('asyncjs-util');
8
8
 
9
9
  const {balancedChannelKeyTypes} = require('./service_key_types');
@@ -2,7 +2,7 @@ const asyncAuto = require('async/auto');
2
2
  const {formatTokens} = require('ln-sync');
3
3
  const {getNodeAlias} = require('ln-sync');
4
4
  const {getPayment} = require('ln-service');
5
- const {parsePaymentRequest} = require('invoices');
5
+ const {parsePaymentRequest} = require('ln-service');
6
6
  const {payViaPaymentRequest} = require('ln-service');
7
7
  const {returnResult} = require('asyncjs-util');
8
8
 
@@ -8,7 +8,7 @@ const {getSwapOutTerms} = require('goldengate');
8
8
  const {lightningLabsSwapAuth} = require('goldengate');
9
9
  const {lightningLabsSwapService} = require('goldengate');
10
10
  const {paidMacaroon} = require('goldengate');
11
- const {parsePaymentRequest} = require('invoices');
11
+ const {parsePaymentRequest} = require('ln-service');
12
12
  const {payViaPaymentRequest} = require('ln-service');
13
13
  const {returnResult} = require('asyncjs-util');
14
14
  const {swapUserId} = require('goldengate');