paid-services 3.13.2 → 3.14.2
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 +8 -0
- package/p2p/encode_peer_response.js +1 -2
- package/package.json +2 -1
- package/test/trades/test_encode_open_trade.js +48 -0
- package/test/trades/test_encode_trade.js +24 -1
- package/trades/accept_trade.js +83 -33
- package/trades/buy_channel.js +251 -0
- package/trades/buy_preimage.js +3 -2
- package/trades/connect_to_seller.js +183 -0
- package/trades/convert_fiat.js +112 -0
- package/trades/create_anchored_trade.js +22 -13
- package/trades/create_channel_sale.js +181 -77
- package/trades/create_trade.js +115 -20
- package/trades/decode_anchored_trade.js +39 -11
- package/trades/decode_open_trade.js +6 -0
- package/trades/decode_trade.js +16 -4
- package/trades/decode_trade_payment.js +45 -0
- package/trades/encode_anchored_trade.js +25 -5
- package/trades/encode_trade.js +1 -1
- package/trades/encode_trade_payment.js +38 -0
- package/trades/encode_trade_secret.js +1 -1
- package/trades/finalize_trade.js +244 -0
- package/trades/find_trade.js +14 -175
- package/trades/get_anchored_trade.js +10 -6
- package/trades/manage_trade.js +2 -8
- package/trades/manage_trades.js +44 -37
- package/trades/request_trade_by_id.js +3 -18
- package/trades/service_anchored_trades.js +5 -1
- package/trades/service_open_trade.js +24 -17
- package/trades/service_trade_requests.js +51 -31
- package/trades/trade_from_invoice.js +11 -7
- package/trades/finalize_trade_secret.js +0 -158
- package/trades/open_channel.js +0 -60
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
const {decodeBigSize} = require('bolt01');
|
|
1
2
|
const {decodeTlvStream} = require('bolt01');
|
|
2
3
|
|
|
3
4
|
const anchorPrefix = 'anchor-trade-secret:';
|
|
4
5
|
const base64AsHex = base64 => Buffer.from(base64, 'base64').toString('hex');
|
|
6
|
+
const decodeNumber = encoded => Number(decodeBigSize({encoded}).decoded);
|
|
7
|
+
const findChannel = records => records.find(n => n.type === '3');
|
|
5
8
|
const findDescription = records => records.find(n => n.type === '1');
|
|
6
9
|
const findSecret = records => records.find(n => n.type === '0');
|
|
10
|
+
const findPrice = records => records.find(n => n.type === '2');
|
|
7
11
|
const hexAsUtf = n => !n ? '' : Buffer.from(n.value, 'hex').toString('utf8');
|
|
8
12
|
|
|
9
13
|
/** Decode an anchored trade data blob
|
|
@@ -11,8 +15,10 @@ const hexAsUtf = n => !n ? '' : Buffer.from(n.value, 'hex').toString('utf8');
|
|
|
11
15
|
Data blobs look like anchored-trade-secret:<base64-encoded-data>
|
|
12
16
|
|
|
13
17
|
Inside the base64 is encoded TLV data:
|
|
14
|
-
0: <Trade Secret>
|
|
18
|
+
[0]: <Trade Secret>
|
|
15
19
|
[1]: <Trade Description>
|
|
20
|
+
[2]: <Price Expression>
|
|
21
|
+
[3]: <Channel Sale Tokens>
|
|
16
22
|
|
|
17
23
|
{
|
|
18
24
|
encoded: <Encoded Trade Data String>
|
|
@@ -21,8 +27,10 @@ const hexAsUtf = n => !n ? '' : Buffer.from(n.value, 'hex').toString('utf8');
|
|
|
21
27
|
@returns
|
|
22
28
|
{
|
|
23
29
|
[trade]: {
|
|
24
|
-
|
|
25
|
-
|
|
30
|
+
[channel]: <Channel Sale Capacity Tokens Number>
|
|
31
|
+
[description]: <Description of Trade String>
|
|
32
|
+
[price]: <Trade Price String>
|
|
33
|
+
[secret]: <Trade Secret String>
|
|
26
34
|
}
|
|
27
35
|
}
|
|
28
36
|
*/
|
|
@@ -41,16 +49,36 @@ module.exports = ({encoded}) => {
|
|
|
41
49
|
|
|
42
50
|
const {records} = decodeTlvStream({encoded: base64AsHex(data)});
|
|
43
51
|
|
|
52
|
+
const channel = findChannel(records);
|
|
53
|
+
const description = findDescription(records);
|
|
54
|
+
const price = findPrice(records);
|
|
44
55
|
const secret = findSecret(records);
|
|
45
56
|
|
|
46
|
-
if (
|
|
47
|
-
|
|
57
|
+
if (!!channel) {
|
|
58
|
+
// Make sure the channel value is reasonable
|
|
59
|
+
try {
|
|
60
|
+
decodeNumber(channel.value);
|
|
61
|
+
} catch {
|
|
62
|
+
return {};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
trade: {
|
|
67
|
+
channel: decodeNumber(channel.value),
|
|
68
|
+
price: !!price ? hexAsUtf(price) : undefined,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (!!description && !!secret) {
|
|
74
|
+
return {
|
|
75
|
+
trade: {
|
|
76
|
+
description: hexAsUtf(description),
|
|
77
|
+
price: !!price ? hexAsUtf(price) : undefined,
|
|
78
|
+
secret: hexAsUtf(secret),
|
|
79
|
+
},
|
|
80
|
+
};
|
|
48
81
|
}
|
|
49
82
|
|
|
50
|
-
return {
|
|
51
|
-
trade: {
|
|
52
|
-
description: hexAsUtf(findDescription(records)),
|
|
53
|
-
secret: hexAsUtf(secret),
|
|
54
|
-
},
|
|
55
|
-
};
|
|
83
|
+
return {};
|
|
56
84
|
};
|
|
@@ -55,6 +55,12 @@ module.exports = ({network, records}) => {
|
|
|
55
55
|
throw new Error('ExpectedNodesRecordToDecodeOpenTradeDetails');
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
try {
|
|
59
|
+
decodeTlvStream({encoded: nodesRecord.value});
|
|
60
|
+
} catch (err) {
|
|
61
|
+
throw new Error('ExpectedValidNodesTlvStreamToDecodeOpenTradeDetails');
|
|
62
|
+
}
|
|
63
|
+
|
|
58
64
|
const nodeRecords = decodeTlvStream({encoded: nodesRecord.value}).records;
|
|
59
65
|
|
|
60
66
|
if (!nodeRecords.length) {
|
package/trades/decode_trade.js
CHANGED
|
@@ -2,9 +2,11 @@ const {decodeBigSize} = require('bolt01');
|
|
|
2
2
|
const {decodeTlvStream} = require('bolt01');
|
|
3
3
|
|
|
4
4
|
const decodeOpenTrade = require('./decode_open_trade');
|
|
5
|
+
const decodeTradePayment = require('./decode_trade_payment');
|
|
5
6
|
const decodeTradeSecret = require('./decode_trade_secret');
|
|
6
7
|
const networkFromNetworkRecord = require('./network_from_network_record');
|
|
7
8
|
|
|
9
|
+
const findDetailsRecord = records => records.find(n => n.type === '3');
|
|
8
10
|
const findNetwork = records => records.find(n => n.type === '1');
|
|
9
11
|
const findVer = records => records.find(n => n.type === '0') || {value: '00'};
|
|
10
12
|
const findRequestRecord = records => records.find(n => n.type === '2');
|
|
@@ -39,6 +41,9 @@ const tradeData = trade => trade.slice('626f73ff'.length);
|
|
|
39
41
|
}
|
|
40
42
|
}]
|
|
41
43
|
}
|
|
44
|
+
[payment]: {
|
|
45
|
+
request: <BOLT 11 Payment Request String>
|
|
46
|
+
}
|
|
42
47
|
[secret]: {
|
|
43
48
|
auth: <Encrypted Payload Auth Hex String>
|
|
44
49
|
payload: <Preimage Encrypted Payload Hex String>
|
|
@@ -81,10 +86,17 @@ module.exports = ({trade}) => {
|
|
|
81
86
|
// Decode the network record
|
|
82
87
|
const {network} = networkFromNetworkRecord({value: networkRecord.value});
|
|
83
88
|
|
|
84
|
-
|
|
85
|
-
|
|
89
|
+
const requestRecord = findRequestRecord(records);
|
|
90
|
+
|
|
91
|
+
// Exit early when this is a secret to trade
|
|
92
|
+
if (!!requestRecord && !!findDetailsRecord(records)) {
|
|
86
93
|
return {secret: decodeTradeSecret({network, records})};
|
|
87
|
-
} else {
|
|
88
|
-
return {connect: decodeOpenTrade({network, records})};
|
|
89
94
|
}
|
|
95
|
+
|
|
96
|
+
// Exit early when this is just a payment request
|
|
97
|
+
if (!!requestRecord) {
|
|
98
|
+
return {payment: decodeTradePayment({network, records})};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {connect: decodeOpenTrade({network, records})};
|
|
90
102
|
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const {requestRecordsAsRequest} = require('./../records');
|
|
2
|
+
|
|
3
|
+
const findRequest = records => records.find(n => n.type === '2');
|
|
4
|
+
const {isArray} = Array;
|
|
5
|
+
|
|
6
|
+
/** Decode a trade payment request
|
|
7
|
+
|
|
8
|
+
2: <Payment Request Record>
|
|
9
|
+
|
|
10
|
+
{
|
|
11
|
+
network: <Network Name String>
|
|
12
|
+
records: [{
|
|
13
|
+
type: <Type Number String>
|
|
14
|
+
value: <Hex Encoded Value String>
|
|
15
|
+
}]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@throws
|
|
19
|
+
<Error>
|
|
20
|
+
|
|
21
|
+
@returns
|
|
22
|
+
{
|
|
23
|
+
request: <BOLT 11 Payment Request String>
|
|
24
|
+
}
|
|
25
|
+
*/
|
|
26
|
+
module.exports = ({network, records}) => {
|
|
27
|
+
if (!network) {
|
|
28
|
+
throw new Error('ExpectedNetworkNameToDecodeTradePayment');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!isArray(records)) {
|
|
32
|
+
throw new Error('ExpectedArrayOfRecordsToDecodeTradePayment');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Get the payment request record
|
|
36
|
+
const requestRecord = findRequest(records);
|
|
37
|
+
|
|
38
|
+
if (!requestRecord) {
|
|
39
|
+
throw new Error('ExpectedRequestRecordInTradeDataForTradePayment');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const encoded = requestRecord.value;
|
|
43
|
+
|
|
44
|
+
return {request: requestRecordsAsRequest({encoded, network}).request};
|
|
45
|
+
};
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
const {encodeBigSize} = require('bolt01');
|
|
1
2
|
const {encodeTlvStream} = require('bolt01');
|
|
2
3
|
|
|
3
4
|
const anchorPrefix = 'anchor-trade-secret:';
|
|
4
5
|
const descriptionRecord = value => !!value ? ({value, type: '1'}) : undefined;
|
|
5
|
-
const
|
|
6
|
+
const priceRecord = value => !!value ? ({value, type: '2'}) : undefined;
|
|
7
|
+
const secretRecord = value => !!value ? ({value, type: '0'}) : undefined;
|
|
8
|
+
const typeForChannelSale = '3';
|
|
6
9
|
const utf8AsHex = utf8 => Buffer.from(utf8, 'utf8').toString('hex');
|
|
7
10
|
const withPrefix = (pre, v) => pre + Buffer.from(v, 'hex').toString('base64');
|
|
8
11
|
|
|
@@ -11,12 +14,16 @@ const withPrefix = (pre, v) => pre + Buffer.from(v, 'hex').toString('base64');
|
|
|
11
14
|
This will look like anchored-trade-secret:<base64-encoded-data>
|
|
12
15
|
|
|
13
16
|
The data encodes a TLV:
|
|
14
|
-
0: <Trade Secret>
|
|
17
|
+
[0]: <Trade Secret>
|
|
15
18
|
[1]: <Trade Description>
|
|
19
|
+
[2]: <Price Expression>
|
|
20
|
+
[3]: <Channel Sale Tokens>
|
|
16
21
|
|
|
17
22
|
{
|
|
23
|
+
[channel]: <Channel Sale Tokens Number>
|
|
18
24
|
[description]: <Open Trade Description String>
|
|
19
|
-
|
|
25
|
+
[price]: <Trade Price String>
|
|
26
|
+
[secret]: <Open Trade Secret String>
|
|
20
27
|
}
|
|
21
28
|
|
|
22
29
|
@returns
|
|
@@ -24,11 +31,24 @@ const withPrefix = (pre, v) => pre + Buffer.from(v, 'hex').toString('base64');
|
|
|
24
31
|
encoded: <Encoded Anchor Trade Data Blob String>
|
|
25
32
|
}
|
|
26
33
|
*/
|
|
27
|
-
module.exports = ({description, secret}) => {
|
|
28
|
-
const
|
|
34
|
+
module.exports = ({channel, description, price, secret}) => {
|
|
35
|
+
const elements = [
|
|
36
|
+
secretRecord(secret),
|
|
37
|
+
descriptionRecord(description),
|
|
38
|
+
priceRecord(price),
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const records = elements
|
|
29
42
|
.filter(n => !!n)
|
|
30
43
|
.map(({type, value}) => ({type, value: utf8AsHex(value)}));
|
|
31
44
|
|
|
45
|
+
if (!!channel) {
|
|
46
|
+
records.push({
|
|
47
|
+
type: typeForChannelSale,
|
|
48
|
+
value: encodeBigSize({number: channel.toString()}).encoded,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
32
52
|
const {encoded} = encodeTlvStream({records});
|
|
33
53
|
|
|
34
54
|
return {encoded: withPrefix(anchorPrefix, encoded)};
|
package/trades/encode_trade.js
CHANGED
|
@@ -44,7 +44,7 @@ module.exports = ({connect, secret}) => {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
if (!!connect && !!secret) {
|
|
47
|
-
throw new Error('
|
|
47
|
+
throw new Error('ExpectedOnlyOneConnectDetailsOrTradeSecretNotBoth');
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
// Exit early when this is an open ended trade
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const {encodeTlvStream} = require('bolt01');
|
|
2
|
+
|
|
3
|
+
const networkRecordFromRequest = require('./network_record_from_request');
|
|
4
|
+
const {requestAsRequestRecords} = require('./../records');
|
|
5
|
+
|
|
6
|
+
const asTrade = hex => `626f73ff${hex}`;
|
|
7
|
+
|
|
8
|
+
/** Encode a trade for a regular payment
|
|
9
|
+
|
|
10
|
+
[1]: <Network Name>
|
|
11
|
+
2: <Payment Request Record>
|
|
12
|
+
|
|
13
|
+
{
|
|
14
|
+
request: <BOLT 11 Payment Request String>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@returns
|
|
18
|
+
{
|
|
19
|
+
trade: <Hex Encoded Trade String>
|
|
20
|
+
}
|
|
21
|
+
*/
|
|
22
|
+
module.exports = ({request}, cbk) => {
|
|
23
|
+
// Encode the request into a trade record
|
|
24
|
+
const tradeRecords = [{
|
|
25
|
+
type: '2',
|
|
26
|
+
value: requestAsRequestRecords({request}).encoded,
|
|
27
|
+
}];
|
|
28
|
+
|
|
29
|
+
// Add a network name record if required
|
|
30
|
+
if (!!networkRecordFromRequest({request}).value) {
|
|
31
|
+
tradeRecords.push({
|
|
32
|
+
type: '1',
|
|
33
|
+
value: networkRecordFromRequest({request}).value,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return {trade: asTrade(encodeTlvStream({records: tradeRecords}).encoded)};
|
|
38
|
+
};
|
|
@@ -38,7 +38,7 @@ module.exports = ({auth, payload, request}, cbk) => {
|
|
|
38
38
|
records: [{type: '0', value: payload}, {type: '1', value: auth}],
|
|
39
39
|
});
|
|
40
40
|
|
|
41
|
-
// Encode the
|
|
41
|
+
// Encode the encrypted elements of the trade
|
|
42
42
|
const detailsRecords = encodeTlvStream({
|
|
43
43
|
records: [{type: '0', value: encryptionRecords.encoded}],
|
|
44
44
|
});
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
const {createHash} = require('crypto');
|
|
2
|
+
const {randomBytes} = require('crypto');
|
|
3
|
+
|
|
4
|
+
const asyncAuto = require('async/auto');
|
|
5
|
+
const {createHodlInvoice} = require('ln-service');
|
|
6
|
+
const {createInvoice} = require('ln-service');
|
|
7
|
+
const {getInvoice} = require('ln-service');
|
|
8
|
+
const {getChainFeeRate} = require('ln-service');
|
|
9
|
+
const {parsePaymentRequest} = require('ln-service');
|
|
10
|
+
const {returnResult} = require('asyncjs-util');
|
|
11
|
+
|
|
12
|
+
const convertFiat = require('./convert_fiat');
|
|
13
|
+
const decodeAnchoredTrade = require('./decode_anchored_trade');
|
|
14
|
+
const encodeTradePayment = require('./encode_trade_payment');
|
|
15
|
+
const encodeTradeSecret = require('./encode_trade_secret');
|
|
16
|
+
const encryptTradeSecret = require('./encrypt_trade_secret');
|
|
17
|
+
|
|
18
|
+
const asNumber = n => parseFloat(n, 10);
|
|
19
|
+
const bufferAsHex = buffer => buffer.toString('hex');
|
|
20
|
+
const {ceil} = Math;
|
|
21
|
+
const defaultShortInvoiceExpiryMs = 1000 * 60 * 30;
|
|
22
|
+
const estimatedChannelOpenSizeVbytes = 250;
|
|
23
|
+
const futureDate = ms => new Date(Date.now() + ms).toISOString();
|
|
24
|
+
const hexAsBuffer = hex => Buffer.from(hex, 'hex');
|
|
25
|
+
const makePaymentSecret = () => randomBytes(32).toString('hex');
|
|
26
|
+
const sha256 = preimage => createHash('sha256').update(preimage).digest();
|
|
27
|
+
const slowConf = 144;
|
|
28
|
+
const tokensAsBigUnit = tokens => (tokens / 1e8).toFixed(8);
|
|
29
|
+
const utf8AsHex = utf8 => Buffer.from(utf8, 'utf8').toString('hex');
|
|
30
|
+
|
|
31
|
+
/** Create a trade secret for a node
|
|
32
|
+
|
|
33
|
+
{
|
|
34
|
+
[channel]: <Sell Channel With Capacity Tokens Number>
|
|
35
|
+
[description]: <Trade Description String>
|
|
36
|
+
expires_at: <Trade Expires At ISO 8601 Date String>
|
|
37
|
+
[id]: <Trade Anchor Id Hex String>
|
|
38
|
+
is_hold: <Create Hold Invoice Bool>
|
|
39
|
+
lnd: <Authenticated LND API Object>
|
|
40
|
+
[price]: <Trade Price String>
|
|
41
|
+
[request]: <Request Function>
|
|
42
|
+
[secret]: <Secret Payload String>
|
|
43
|
+
to: <To Public Key Hex Encoded String>
|
|
44
|
+
[tokens]: <Price Tokens Number or Fiat>
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@returns via cbk or Promise
|
|
48
|
+
{
|
|
49
|
+
id: <Payment Hash Hex String>
|
|
50
|
+
[secret]: <Hex Encoded Payment Secret String>
|
|
51
|
+
trade: <Hex Encoded Trade String>
|
|
52
|
+
}
|
|
53
|
+
*/
|
|
54
|
+
module.exports = (args, cbk) => {
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
return asyncAuto({
|
|
57
|
+
// Check arguments
|
|
58
|
+
validate: cbk => {
|
|
59
|
+
if (!args.channel && !args.description) {
|
|
60
|
+
return cbk([400, 'ExpectedDescriptionToFinalizeTrade']);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!!args.channel && !args.is_hold) {
|
|
64
|
+
return cbk([400, 'ExpectedChannelAsHoldInvoiceToFinalizeTrade']);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!args.expires_at) {
|
|
68
|
+
return cbk([400, 'ExpectedExpiresAtDateToFinalizeTrade']);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!args.lnd) {
|
|
72
|
+
return cbk([400, 'ExpectedAuthenticatedLndToFinalizeTrade']);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!args.price && !args.tokens) {
|
|
76
|
+
return cbk([400, 'ExpectedTokensPriceToFinalizeTrade']);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!!args.price && !args.request) {
|
|
80
|
+
return cbk([400, 'ExpectedRequestFunctionToFinalizeTrade']);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!args.channel && !args.secret) {
|
|
84
|
+
return cbk([400, 'ExpectedChannelOrSecretValueToFinalizeTrade']);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!args.to) {
|
|
88
|
+
return cbk([400, 'ExpectedToPublicKeyToFinalizeTrade']);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return cbk();
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
// Description for the final invoice
|
|
95
|
+
description: ['validate', ({}, cbk) => {
|
|
96
|
+
if (!args.channel) {
|
|
97
|
+
return cbk(null, args.description);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Make a description for a channel creation
|
|
101
|
+
const capacity = tokensAsBigUnit(args.channel);
|
|
102
|
+
const node = args.to;
|
|
103
|
+
|
|
104
|
+
const description = `Attempt ${capacity} channel creation to ${node}`;
|
|
105
|
+
|
|
106
|
+
return cbk(null, description);
|
|
107
|
+
}],
|
|
108
|
+
|
|
109
|
+
// Encrypt the secret data
|
|
110
|
+
encrypt: ['validate', ({}, cbk) => {
|
|
111
|
+
// Exit early when there is no trade of a secret
|
|
112
|
+
if (!args.secret) {
|
|
113
|
+
return cbk(null, {payment_secret: makePaymentSecret()});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return encryptTradeSecret({
|
|
117
|
+
lnd: args.lnd,
|
|
118
|
+
secret: utf8AsHex(args.secret),
|
|
119
|
+
to: args.to,
|
|
120
|
+
},
|
|
121
|
+
cbk);
|
|
122
|
+
}],
|
|
123
|
+
|
|
124
|
+
// Expiration for the final invoice
|
|
125
|
+
expiry: ['validate', ({}, cbk) => {
|
|
126
|
+
// Exit early when there is no price expression
|
|
127
|
+
if (!args.price) {
|
|
128
|
+
return cbk(null, args.expires_at);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Price expressions get nearer term expiration dates
|
|
132
|
+
const shortDate = futureDate(defaultShortInvoiceExpiryMs);
|
|
133
|
+
|
|
134
|
+
// Check to make sure the short date doesn't go past the expiry
|
|
135
|
+
if (shortDate > args.expires_at) {
|
|
136
|
+
return cbk([410, 'TradeIsExpired']);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return cbk(null, shortDate);
|
|
140
|
+
}],
|
|
141
|
+
|
|
142
|
+
// Get the current chain fee rate to add a charge for channel sales
|
|
143
|
+
getFeeRate: ['validate', ({}, cbk) => {
|
|
144
|
+
// Exit early when this is not a channel sale
|
|
145
|
+
if (!args.channel) {
|
|
146
|
+
return cbk();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return getChainFeeRate({
|
|
150
|
+
confirmation_target: slowConf,
|
|
151
|
+
lnd: args.lnd,
|
|
152
|
+
},
|
|
153
|
+
cbk);
|
|
154
|
+
}],
|
|
155
|
+
|
|
156
|
+
// Get the sale price in tokens
|
|
157
|
+
getTokens: ['validate', ({}, cbk) => {
|
|
158
|
+
// Exit early when there is no price expression
|
|
159
|
+
if (!args.price) {
|
|
160
|
+
return cbk(null, {cost: args.tokens});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Convert the price expression into a tokens cost
|
|
164
|
+
return convertFiat({price: args.price, request: args.request}, cbk);
|
|
165
|
+
}],
|
|
166
|
+
|
|
167
|
+
// Calculate the final sale price
|
|
168
|
+
tokens: ['getFeeRate', 'getTokens', ({getFeeRate, getTokens}, cbk) => {
|
|
169
|
+
// Exit early when there is no need to add chain fee for channel sales
|
|
170
|
+
if (!args.channel) {
|
|
171
|
+
return cbk(null, getTokens.cost);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Create a surcharge for the vbytes cost of a channel open
|
|
175
|
+
const vbytes = estimatedChannelOpenSizeVbytes;
|
|
176
|
+
|
|
177
|
+
const chainFeeCharge = ceil(getFeeRate.tokens_per_vbyte * vbytes);
|
|
178
|
+
|
|
179
|
+
return cbk(null, getTokens.cost + chainFeeCharge);
|
|
180
|
+
}],
|
|
181
|
+
|
|
182
|
+
// Create the final purchasing invoice
|
|
183
|
+
createInvoice: [
|
|
184
|
+
'description',
|
|
185
|
+
'encrypt',
|
|
186
|
+
'expiry',
|
|
187
|
+
'tokens',
|
|
188
|
+
({description, encrypt, expiry, tokens}, cbk) =>
|
|
189
|
+
{
|
|
190
|
+
// Exit early when this is a hold invoice
|
|
191
|
+
if (!!args.is_hold) {
|
|
192
|
+
return createHodlInvoice({
|
|
193
|
+
description,
|
|
194
|
+
tokens,
|
|
195
|
+
expires_at: expiry,
|
|
196
|
+
id: bufferAsHex(sha256(hexAsBuffer(encrypt.payment_secret))),
|
|
197
|
+
lnd: args.lnd,
|
|
198
|
+
secret: encrypt.payment_secret,
|
|
199
|
+
},
|
|
200
|
+
cbk);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return createInvoice({
|
|
204
|
+
description,
|
|
205
|
+
tokens,
|
|
206
|
+
expires_at: args.expires_at,
|
|
207
|
+
lnd: args.lnd,
|
|
208
|
+
secret: encrypt.payment_secret,
|
|
209
|
+
},
|
|
210
|
+
cbk);
|
|
211
|
+
}],
|
|
212
|
+
|
|
213
|
+
// Encode all the trade data into wire format
|
|
214
|
+
encode: ['createInvoice', 'encrypt', ({createInvoice, encrypt}, cbk) => {
|
|
215
|
+
// Exit early when this is a channel trade, there is no secret
|
|
216
|
+
if (!!args.channel) {
|
|
217
|
+
return cbk(null, {
|
|
218
|
+
id: createInvoice.id,
|
|
219
|
+
secret: encrypt.payment_secret,
|
|
220
|
+
trade: encodeTradePayment({request: createInvoice.request}).trade,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
// Encode the trade secret records
|
|
226
|
+
const {trade} = encodeTradeSecret({
|
|
227
|
+
auth: encrypt.trade_auth_tag,
|
|
228
|
+
payload: encrypt.trade_cipher,
|
|
229
|
+
request: createInvoice.request,
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
return cbk(null, {
|
|
233
|
+
trade,
|
|
234
|
+
id: createInvoice.id,
|
|
235
|
+
secret: encrypt.payment_secret,
|
|
236
|
+
});
|
|
237
|
+
} catch (err) {
|
|
238
|
+
return cbk([500, err.message]);
|
|
239
|
+
}
|
|
240
|
+
}],
|
|
241
|
+
},
|
|
242
|
+
returnResult({reject, resolve, of: 'encode'}, cbk));
|
|
243
|
+
});
|
|
244
|
+
};
|