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
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Versions
|
|
2
2
|
|
|
3
|
+
## Version 3.14.2
|
|
4
|
+
|
|
5
|
+
- `manageTrades`: Fix listing of trades that have fiat-pricing
|
|
6
|
+
|
|
7
|
+
## Version 3.14.1
|
|
8
|
+
|
|
9
|
+
- `manageTrades`: Add support for dynamic fiat-priced trades
|
|
10
|
+
|
|
3
11
|
## Version 3.13.2
|
|
4
12
|
|
|
5
13
|
- `acceptTrade`: Fix `logger` to be documented as an arg and optional
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
const {encodeBigSize} = require('bolt01');
|
|
2
1
|
const {encodeTlvStream} = require('bolt01');
|
|
3
2
|
|
|
4
|
-
const encodeMessage = message => `626f73ff${message}`;
|
|
5
3
|
const encodeResponseCode = require('./encode_response_code');
|
|
6
4
|
|
|
5
|
+
const encodeMessage = message => `626f73ff${message}`;
|
|
7
6
|
const headers = null;
|
|
8
7
|
const params = null;
|
|
9
8
|
const version = null;
|
package/package.json
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
"url": "https://github.com/alexbosworth/paid-services/issues"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
+
"@alexbosworth/fiat": "1.0.1",
|
|
10
11
|
"async": "3.2.3",
|
|
11
12
|
"asyncjs-util": "1.2.8",
|
|
12
13
|
"bolt01": "1.2.3",
|
|
@@ -42,5 +43,5 @@
|
|
|
42
43
|
"integration-tests": "tap -j 2 --branches=1 --functions=1 --lines=1 --statements=1 -t 180 test/integration/*.js",
|
|
43
44
|
"test": "tap --branches=1 --functions=1 --lines=1 --statements=1 -t 60 test/actions/*.js test/balanced/*.js test/capacity/*.js test/client/*.js test/config/*.js test/p2p/*.js test/records/*.js test/respond/*.js test/server/*.js test/server/*.js test/services/*.js test/trades/*.js"
|
|
44
45
|
},
|
|
45
|
-
"version": "3.
|
|
46
|
+
"version": "3.14.2"
|
|
46
47
|
}
|
|
@@ -3,6 +3,54 @@ const {test} = require('@alexbosworth/tap');
|
|
|
3
3
|
const method = require('./../../trades/encode_open_trade');
|
|
4
4
|
|
|
5
5
|
const tests = [
|
|
6
|
+
{
|
|
7
|
+
args: {},
|
|
8
|
+
description: 'A network name is required',
|
|
9
|
+
error: 'ExpectedNetworkNameToEncodeOpenTrade',
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
args: {network: 'btc'},
|
|
13
|
+
description: 'Nodes array is required',
|
|
14
|
+
error: 'ExpectedArrayOfNodesToEncodeOpenTrade',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
args: {network: 'btc', nodes: []},
|
|
18
|
+
description: 'Nodes are required',
|
|
19
|
+
error: 'ExpectedNodeToReferToInOpenTrade',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
args: {
|
|
23
|
+
network: 'btctestnet',
|
|
24
|
+
nodes: [{
|
|
25
|
+
channels: [{
|
|
26
|
+
id: '1x2x3',
|
|
27
|
+
partner_public_key: Buffer.alloc(33, 2).toString('hex'),
|
|
28
|
+
}],
|
|
29
|
+
id: Buffer.alloc(33, 3).toString('hex'),
|
|
30
|
+
sockets: [],
|
|
31
|
+
}],
|
|
32
|
+
},
|
|
33
|
+
description: 'A network record is added',
|
|
34
|
+
expected: {trade: '626f73ff000101010101040c000a01080000010000020003'},
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
args: {
|
|
38
|
+
id: Buffer.alloc(32).toString('hex'),
|
|
39
|
+
network: 'btctestnet',
|
|
40
|
+
nodes: [{
|
|
41
|
+
channels: [{
|
|
42
|
+
id: '1x2x3',
|
|
43
|
+
partner_public_key: Buffer.alloc(33, 2).toString('hex'),
|
|
44
|
+
}],
|
|
45
|
+
id: Buffer.alloc(33, 3).toString('hex'),
|
|
46
|
+
sockets: [],
|
|
47
|
+
}],
|
|
48
|
+
},
|
|
49
|
+
description: 'A trade id record is added',
|
|
50
|
+
expected: {
|
|
51
|
+
trade: '626f73ff000101010101040c000a0108000001000002000305200000000000000000000000000000000000000000000000000000000000000000',
|
|
52
|
+
},
|
|
53
|
+
},
|
|
6
54
|
{
|
|
7
55
|
args: {
|
|
8
56
|
network: 'btc',
|
|
@@ -3,6 +3,29 @@ const {test} = require('@alexbosworth/tap');
|
|
|
3
3
|
const method = require('./../../trades/encode_trade');
|
|
4
4
|
|
|
5
5
|
const tests = [
|
|
6
|
+
{
|
|
7
|
+
args: {},
|
|
8
|
+
description: 'Either connect or secret is required',
|
|
9
|
+
error: 'ExpectedEitherConnectDetailsOrTradeSecret',
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
args: {connect: true, secret: true},
|
|
13
|
+
description: 'Cannot encode both connect and secret',
|
|
14
|
+
error: 'ExpectedOnlyOneConnectDetailsOrTradeSecretNotBoth',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
args: {
|
|
18
|
+
secret: {
|
|
19
|
+
auth: '00',
|
|
20
|
+
payload: '11',
|
|
21
|
+
request: 'lnbc1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdpl2pkx2ctnv5sxxmmwwd5kgetjypeh2ursdae8g6twvus8g6rfwvs8qun0dfjkxaq8rkx3yf5tcsyz3d73gafnh3cax9rn449d9p5uxz9ezhhypd0elx87sjle52x86fux2ypatgddc6k63n7erqz25le42c4u4ecky03ylcqca784w',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
description: 'Trade secret is encoded',
|
|
25
|
+
expected: {
|
|
26
|
+
trade: '626f73ff02960001e801910b25fe64410d00004080c1014181c20240004080c1014181c20240004080c1014181c202404081a1fa83632b0b9b29031b7b739b4b232b91039bab83837b93a34b733903a3434b990383937b532b1ba038ec6891345e204145be8a3a99de38e98a39d6a569434e1845c8af7205afcfcc7f425fcd1463e93c32881ead0d6e356d467ec8c02553f9aab15e5738b11f127f0003080006000111010100',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
6
29
|
{
|
|
7
30
|
args: {
|
|
8
31
|
connect: {
|
|
@@ -17,7 +40,7 @@ const tests = [
|
|
|
17
40
|
}],
|
|
18
41
|
},
|
|
19
42
|
},
|
|
20
|
-
description: 'Trade is encoded',
|
|
43
|
+
description: 'Trade connect is encoded',
|
|
21
44
|
expected: {trade: '626f73ff000101040c000a01080000010000020003'},
|
|
22
45
|
},
|
|
23
46
|
];
|
package/trades/accept_trade.js
CHANGED
|
@@ -1,45 +1,58 @@
|
|
|
1
|
+
const {acceptsChannelOpen} = require('ln-sync');
|
|
1
2
|
const asyncAuto = require('async/auto');
|
|
2
3
|
const asyncEach = require('async/each');
|
|
3
4
|
const {cancelHodlInvoice} = require('ln-service');
|
|
5
|
+
const {getChainFeeRate} = require('ln-service');
|
|
4
6
|
const {getInvoice} = require('ln-service');
|
|
7
|
+
const {openChannel} = require('ln-service');
|
|
5
8
|
const {returnResult} = require('asyncjs-util');
|
|
6
9
|
const {settleHodlInvoice} = require('ln-service');
|
|
7
10
|
|
|
8
|
-
const openChannel = require('./open_channel');
|
|
9
|
-
|
|
10
11
|
const {isArray} = Array;
|
|
11
|
-
const
|
|
12
|
+
const slowConf = 144;
|
|
12
13
|
|
|
13
14
|
/** Accept an open ended trade
|
|
14
15
|
|
|
15
16
|
{
|
|
16
17
|
cancel: [<Alternative Invoice Id Hex String>]
|
|
18
|
+
[channel]: <Channel Capacity Tokens Number>
|
|
19
|
+
from: <Trade With Node Identity Public Key Hex String>
|
|
17
20
|
id: <Trade Id Hex String>
|
|
18
21
|
lnd: <Authenticated LND API Object>
|
|
19
|
-
[logger]: <Winston Logger Object>
|
|
20
22
|
secret: <Invoice to Settle Preimage Hex String>
|
|
21
23
|
}
|
|
22
24
|
|
|
23
25
|
@returns via cbk or Promise
|
|
26
|
+
{
|
|
27
|
+
[opening_channel]: {
|
|
28
|
+
fee_tokens_per_vbyte: <Chain Fee Rate Number>
|
|
29
|
+
transaction_id: <Channel Funding Transaction Id Hex String>
|
|
30
|
+
transaction_vout: <Channel Funding Transaction Output Index Number>
|
|
31
|
+
}
|
|
32
|
+
}
|
|
24
33
|
*/
|
|
25
|
-
module.exports = (
|
|
34
|
+
module.exports = ({cancel, channel, from, id, lnd, secret}, cbk) => {
|
|
26
35
|
return new Promise((resolve, reject) => {
|
|
27
36
|
return asyncAuto({
|
|
28
37
|
// Check arguments
|
|
29
38
|
validate: cbk => {
|
|
30
|
-
if (!isArray(
|
|
39
|
+
if (!isArray(cancel)) {
|
|
31
40
|
return cbk([400, 'ExpectedArrayOfIdsToCancelToAcceptTrade']);
|
|
32
41
|
}
|
|
33
42
|
|
|
34
|
-
if (!
|
|
43
|
+
if (!from) {
|
|
44
|
+
return cbk([400, 'ExpectedWithPeerPublicKeyToAcceptTrade']);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!id) {
|
|
35
48
|
return cbk([400, 'ExpectedAnchorTradeIdToAcceptTrade']);
|
|
36
49
|
}
|
|
37
50
|
|
|
38
|
-
if (!
|
|
51
|
+
if (!lnd) {
|
|
39
52
|
return cbk([400, 'ExpectedAuthenticatedLndToAcceptTrade']);
|
|
40
53
|
}
|
|
41
54
|
|
|
42
|
-
if (!
|
|
55
|
+
if (!secret) {
|
|
43
56
|
return cbk([400, 'ExpectedSettlementPreimageToAcceptTrade']);
|
|
44
57
|
}
|
|
45
58
|
|
|
@@ -47,49 +60,86 @@ module.exports = (args, cbk) => {
|
|
|
47
60
|
},
|
|
48
61
|
|
|
49
62
|
// Fetch the anchor invoice to make sure it's still open
|
|
50
|
-
getAnchor: ['validate', ({}, cbk) => {
|
|
51
|
-
|
|
63
|
+
getAnchor: ['validate', ({}, cbk) => getInvoice({id, lnd}, cbk)],
|
|
64
|
+
|
|
65
|
+
// Get the chain fee rate for channel opening trades
|
|
66
|
+
getFeeRate: ['validate', ({}, cbk) => {
|
|
67
|
+
// Exit early when not trading a channel open
|
|
68
|
+
if (!channel) {
|
|
69
|
+
return cbk();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return getChainFeeRate({lnd, confirmation_target: slowConf}, cbk);
|
|
52
73
|
}],
|
|
53
74
|
|
|
54
75
|
// Cancel alternative invoices so that only one resolves as settled
|
|
55
|
-
cancel: ['getAnchor', ({}, cbk) => {
|
|
56
|
-
|
|
57
|
-
return
|
|
76
|
+
cancel: ['getAnchor', ({getAnchor}, cbk) => {
|
|
77
|
+
if (!!getAnchor.is_canceled) {
|
|
78
|
+
return cbk([404, 'OpenTradeNotFound']);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return asyncEach([].concat(cancel).concat(id), (alternative, cbk) => {
|
|
82
|
+
return cancelHodlInvoice({lnd, id: alternative}, cbk);
|
|
83
|
+
},
|
|
84
|
+
cbk);
|
|
85
|
+
}],
|
|
86
|
+
|
|
87
|
+
// Check opening for channel trades
|
|
88
|
+
acceptsOpen: ['cancel', ({}, cbk) => {
|
|
89
|
+
// Exit early when not opening a channel
|
|
90
|
+
if (!channel) {
|
|
91
|
+
return cbk();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return acceptsChannelOpen({
|
|
95
|
+
lnd,
|
|
96
|
+
capacity: channel,
|
|
97
|
+
partner_public_key: from,
|
|
58
98
|
},
|
|
59
99
|
cbk);
|
|
60
100
|
}],
|
|
61
101
|
|
|
62
102
|
// Settle the held invoice with the preimage
|
|
63
|
-
settle: ['cancel', ({}, cbk) => {
|
|
64
|
-
return settleHodlInvoice({lnd
|
|
103
|
+
settle: ['acceptsOpen', 'cancel', 'getFeeRate', ({}, cbk) => {
|
|
104
|
+
return settleHodlInvoice({lnd, secret}, cbk);
|
|
65
105
|
}],
|
|
66
106
|
|
|
67
107
|
// Open the channel
|
|
68
|
-
openChannel: ['settle', ({}, cbk) => {
|
|
69
|
-
// Exit early when
|
|
70
|
-
if (
|
|
108
|
+
openChannel: ['getFeeRate', 'settle', ({getFeeRate}, cbk) => {
|
|
109
|
+
// Exit early when this is not a channel trade
|
|
110
|
+
if (!channel) {
|
|
71
111
|
return cbk();
|
|
72
112
|
}
|
|
73
113
|
|
|
74
114
|
return openChannel({
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
115
|
+
lnd,
|
|
116
|
+
chain_fee_rate: getFeeRate.tokens_per_vbyte,
|
|
117
|
+
local_tokens: channel,
|
|
118
|
+
partner_public_key: from,
|
|
78
119
|
},
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
return cbk(err);
|
|
82
|
-
}
|
|
120
|
+
cbk);
|
|
121
|
+
}],
|
|
83
122
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
123
|
+
// Trade is settled
|
|
124
|
+
settled: [
|
|
125
|
+
'getFeeRate',
|
|
126
|
+
'openChannel',
|
|
127
|
+
({getFeeRate, openChannel}, cbk) =>
|
|
128
|
+
{
|
|
129
|
+
// Exit early when there is no trade result
|
|
130
|
+
if (!openChannel) {
|
|
131
|
+
return cbk(null, {});
|
|
132
|
+
}
|
|
87
133
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
134
|
+
return cbk(null, {
|
|
135
|
+
opening_channel: {
|
|
136
|
+
fee_tokens_per_vbyte: getFeeRate.tokens_per_vbyte,
|
|
137
|
+
transaction_id: openChannel.transaction_id,
|
|
138
|
+
transaction_vout: openChannel.transaction_vout,
|
|
139
|
+
},
|
|
140
|
+
});
|
|
91
141
|
}],
|
|
92
142
|
},
|
|
93
|
-
returnResult({reject, resolve}, cbk));
|
|
143
|
+
returnResult({reject, resolve, of: 'settled'}, cbk));
|
|
94
144
|
});
|
|
95
145
|
};
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
const {randomBytes} = require('crypto');
|
|
2
|
+
|
|
3
|
+
const asyncAuto = require('async/auto');
|
|
4
|
+
const asyncRetry = require('async/retry');
|
|
5
|
+
const {parsePaymentRequest} = require('ln-service');
|
|
6
|
+
const {returnResult} = require('asyncjs-util');
|
|
7
|
+
|
|
8
|
+
const buyPreimage = require('./buy_preimage');
|
|
9
|
+
const connectToSeller = require('./connect_to_seller');
|
|
10
|
+
const decodeBasicTrade = require('./decode_basic_trade');
|
|
11
|
+
const decodeTrade = require('./decode_trade');
|
|
12
|
+
const {makePeerRequest} = require('./../p2p');
|
|
13
|
+
const {servicePeerRequests} = require('./../p2p');
|
|
14
|
+
const {serviceTypeReceiveChannelSale} = require('./../service_types');
|
|
15
|
+
const {serviceTypeRequestChannelSale} = require('./../service_types');
|
|
16
|
+
|
|
17
|
+
const findBasicRecord = records => records.find(n => n.type === '1');
|
|
18
|
+
const findIdRecord = records => records.find(n => n.type === '0');
|
|
19
|
+
const findTradeRecord = records => records.find(n => n.type === '1');
|
|
20
|
+
const {isArray} = Array;
|
|
21
|
+
const makeRequestId = () => randomBytes(32).toString('hex');
|
|
22
|
+
const requestTradeTimeoutMs = 1000 * 30;
|
|
23
|
+
const requestTradesTimeoutMs = 1000 * 30;
|
|
24
|
+
const tokensAsBigUnit = tokens => (tokens / 1e8).toFixed(8);
|
|
25
|
+
const tradeIdRecordType = '0';
|
|
26
|
+
const tradesRequestIdType = '1';
|
|
27
|
+
|
|
28
|
+
/** Purchase a channel
|
|
29
|
+
|
|
30
|
+
{
|
|
31
|
+
ask: <Ask Function>
|
|
32
|
+
lnd: <Authenticated LND API Object>
|
|
33
|
+
logger: <Winston Logger Object>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@returns via cbk or Promise
|
|
37
|
+
*/
|
|
38
|
+
module.exports = ({ask, lnd, logger}, cbk) => {
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
return asyncAuto({
|
|
41
|
+
// Check arguments
|
|
42
|
+
validate: cbk => {
|
|
43
|
+
if (!ask) {
|
|
44
|
+
return cbk([400, 'ExpectedAskFunctionToBuyChannel']);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!lnd) {
|
|
48
|
+
return cbk([400, 'ExpectedAuthenticatedLndToBuyChannel']);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!logger) {
|
|
52
|
+
return cbk([400, 'ExpectedWinstonLoggerToBuyChannel']);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return cbk();
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
// Get the trade data to decode it
|
|
59
|
+
askForTrade: ['validate', ({}, cbk) => {
|
|
60
|
+
return ask({
|
|
61
|
+
name: 'trade',
|
|
62
|
+
message: 'Enter encoded trade',
|
|
63
|
+
type: 'input',
|
|
64
|
+
validate: trade => {
|
|
65
|
+
if (!trade) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
decodeTrade({trade});
|
|
71
|
+
} catch (err) {
|
|
72
|
+
return 'Failed to parse trade details, try re-entering it?';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// The trade type should be a connect type
|
|
76
|
+
if (!decodeTrade({trade}).connect) {
|
|
77
|
+
return 'Incorrect type of trade, enter a channel sale trade?';
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return true;
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
cbk);
|
|
84
|
+
}],
|
|
85
|
+
|
|
86
|
+
// Decode the connect details
|
|
87
|
+
connectDetails: ['askForTrade', ({askForTrade}, cbk) => {
|
|
88
|
+
try {
|
|
89
|
+
const {connect} = decodeTrade({trade: askForTrade.trade});
|
|
90
|
+
|
|
91
|
+
if (!connect) {
|
|
92
|
+
return cbk([400, 'ExpectedConnectDetailsInTradeInput']);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return cbk(null, connect);
|
|
96
|
+
} catch (err) {
|
|
97
|
+
return cbk([400, err.message]);
|
|
98
|
+
}
|
|
99
|
+
}],
|
|
100
|
+
|
|
101
|
+
// Connect to the selling node
|
|
102
|
+
connectToSeller: ['connectDetails', ({connectDetails}, cbk) => {
|
|
103
|
+
return connectToSeller({
|
|
104
|
+
lnd,
|
|
105
|
+
logger,
|
|
106
|
+
nodes: connectDetails.nodes,
|
|
107
|
+
},
|
|
108
|
+
cbk);
|
|
109
|
+
}],
|
|
110
|
+
|
|
111
|
+
// Get channels being sold
|
|
112
|
+
requestTrade: ['connectToSeller', ({connectToSeller}, cbk) => {
|
|
113
|
+
const requestId = makeRequestId();
|
|
114
|
+
const service = servicePeerRequests({lnd});
|
|
115
|
+
|
|
116
|
+
service.request({type: serviceTypeReceiveChannelSale}, (req, res) => {
|
|
117
|
+
const requestIdRecord = findIdRecord(req.records);
|
|
118
|
+
|
|
119
|
+
// Exit early when this is a request for something else
|
|
120
|
+
if (!requestIdRecord || requestIdRecord.value !== requestId) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Make sure there is a basic trade record in the records
|
|
125
|
+
if (!findBasicRecord(req.records)) {
|
|
126
|
+
return res.failure([400, 'ExpectedBasicTradeRecord']);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Make sure the basic trade record is valid
|
|
130
|
+
try {
|
|
131
|
+
decodeBasicTrade({records: req.records});
|
|
132
|
+
} catch (err) {
|
|
133
|
+
return res.failure([400, 'ExpectedValidBasicTradeRecord']);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const trade = decodeBasicTrade({records: req.records});
|
|
137
|
+
|
|
138
|
+
res.success({});
|
|
139
|
+
|
|
140
|
+
// Don't listen for more channel sales
|
|
141
|
+
service.stop({});
|
|
142
|
+
|
|
143
|
+
return cbk(null, trade);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
logger.info({requesting_trade_details: true});
|
|
147
|
+
|
|
148
|
+
// Once connected, ask for the trade details
|
|
149
|
+
return makePeerRequest({
|
|
150
|
+
lnd,
|
|
151
|
+
records: [{type: tradesRequestIdType, value: requestId}],
|
|
152
|
+
timeout: requestTradesTimeoutMs,
|
|
153
|
+
to: connectToSeller.id,
|
|
154
|
+
type: serviceTypeRequestChannelSale,
|
|
155
|
+
},
|
|
156
|
+
(err, res) => {
|
|
157
|
+
if (!!err) {
|
|
158
|
+
service.stop({});
|
|
159
|
+
|
|
160
|
+
return cbk(err);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// A trade will be pinged back into the listening service
|
|
164
|
+
return;
|
|
165
|
+
});
|
|
166
|
+
}],
|
|
167
|
+
|
|
168
|
+
// Get the specific trade details
|
|
169
|
+
getTrade: [
|
|
170
|
+
'connectToSeller',
|
|
171
|
+
'requestTrade',
|
|
172
|
+
({connectToSeller, requestTrade}, cbk) =>
|
|
173
|
+
{
|
|
174
|
+
return makePeerRequest({
|
|
175
|
+
lnd,
|
|
176
|
+
records: [{type: tradeIdRecordType, value: requestTrade.id}],
|
|
177
|
+
timeout: requestTradeTimeoutMs,
|
|
178
|
+
to: connectToSeller.id,
|
|
179
|
+
type: serviceTypeRequestChannelSale,
|
|
180
|
+
},
|
|
181
|
+
(err, res) => {
|
|
182
|
+
if (!!err) {
|
|
183
|
+
return cbk(err);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const tradeRecord = findTradeRecord(res.records);
|
|
187
|
+
|
|
188
|
+
if (!tradeRecord) {
|
|
189
|
+
return cbk([503, 'ExpectedTradeRecordFromPeer']);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
decodeTrade({trade: tradeRecord.value});
|
|
194
|
+
} catch (err) {
|
|
195
|
+
return cbk([503, err.message]);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const {payment} = decodeTrade({trade: tradeRecord.value});
|
|
199
|
+
|
|
200
|
+
if (!payment) {
|
|
201
|
+
return cbk([503, 'ExpectedTradePaymentInResponseForTradeById']);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return cbk(null, {request: payment.request});
|
|
205
|
+
});
|
|
206
|
+
}],
|
|
207
|
+
|
|
208
|
+
// Parse the trade payment request
|
|
209
|
+
requestDetails: ['getTrade', ({getTrade}, cbk) => {
|
|
210
|
+
try {
|
|
211
|
+
return cbk(null, parsePaymentRequest({request: getTrade.request}));
|
|
212
|
+
} catch (err) {
|
|
213
|
+
return cbk([503, 'FailedToParseBuyChannelPaymentRequest', {err}]);
|
|
214
|
+
}
|
|
215
|
+
}],
|
|
216
|
+
|
|
217
|
+
// Pay and get the preimage through paying for it
|
|
218
|
+
payForPreimage: [
|
|
219
|
+
'getTrade',
|
|
220
|
+
'requestDetails',
|
|
221
|
+
({getTrade, requestDetails}, cbk) =>
|
|
222
|
+
{
|
|
223
|
+
logger.info({
|
|
224
|
+
request: getTrade.request,
|
|
225
|
+
purchase: requestDetails.description,
|
|
226
|
+
price: tokensAsBigUnit(requestDetails.tokens),
|
|
227
|
+
to: requestDetails.destination,
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const {request} = getTrade;
|
|
231
|
+
|
|
232
|
+
return asyncRetry({
|
|
233
|
+
errorFilter: err => !!isArray(err) && err.slice().shift() >= 500,
|
|
234
|
+
},
|
|
235
|
+
cbk => buyPreimage({ask, lnd, logger, request}, cbk), cbk);
|
|
236
|
+
}],
|
|
237
|
+
|
|
238
|
+
// Signal that the payment was made
|
|
239
|
+
finished: ['payForPreimage', ({payForPreimage}, cbk) => {
|
|
240
|
+
logger.info({
|
|
241
|
+
price: payForPreimage.tokens,
|
|
242
|
+
payment_proof: payForPreimage.secret,
|
|
243
|
+
routing_fee: payForPreimage.fee,
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
return cbk();
|
|
247
|
+
}],
|
|
248
|
+
},
|
|
249
|
+
returnResult({reject, resolve}, cbk));
|
|
250
|
+
});
|
|
251
|
+
};
|
package/trades/buy_preimage.js
CHANGED
|
@@ -5,6 +5,7 @@ const {returnResult} = require('asyncjs-util');
|
|
|
5
5
|
const {subscribeToProbeForRoute} = require('ln-service');
|
|
6
6
|
|
|
7
7
|
const cltvDeltaBuffer = 5;
|
|
8
|
+
const tokensAsBigUnit = tokens => (tokens / 1e8).toFixed(8);
|
|
8
9
|
|
|
9
10
|
/** Buy the preimage for a trade
|
|
10
11
|
|
|
@@ -134,9 +135,9 @@ module.exports = ({ask, lnd, logger, request}, cbk) => {
|
|
|
134
135
|
// Final payment
|
|
135
136
|
paid: ['parseRequest', 'pay', ({parseRequest, pay}, cbk) => {
|
|
136
137
|
return cbk(null, {
|
|
137
|
-
fee: pay.fee,
|
|
138
|
+
fee: tokensAsBigUnit(pay.fee),
|
|
138
139
|
secret: pay.secret,
|
|
139
|
-
tokens: parseRequest.tokens,
|
|
140
|
+
tokens: tokensAsBigUnit(parseRequest.tokens),
|
|
140
141
|
});
|
|
141
142
|
}],
|
|
142
143
|
},
|