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
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
const addPeer = require('ln-service');
|
|
2
|
+
const asyncAuto = require('async/auto');
|
|
3
|
+
const asyncDetect = require('async/detect');
|
|
4
|
+
const asyncDetectSeries = require('async/detectSeries');
|
|
5
|
+
const asyncMap = require('async/map');
|
|
6
|
+
const {getChannel} = require('ln-service');
|
|
7
|
+
const {getIdentity} = require('ln-service');
|
|
8
|
+
const {getNode} = require('ln-service');
|
|
9
|
+
const {getPeers} = require('ln-service');
|
|
10
|
+
const {returnResult} = require('asyncjs-util');
|
|
11
|
+
|
|
12
|
+
const {isArray} = Array;
|
|
13
|
+
const uniq = arr => Array.from(new Set(arr));
|
|
14
|
+
|
|
15
|
+
/** Connect to a seller
|
|
16
|
+
|
|
17
|
+
{
|
|
18
|
+
lnd: <Authenticated LND API Object>
|
|
19
|
+
logger: <Winston Logger Object>
|
|
20
|
+
nodes: [{
|
|
21
|
+
[high_channel]: <High Key Channel Id String>
|
|
22
|
+
[low_channel]: <Low Key Channel Id String>
|
|
23
|
+
[node]: {
|
|
24
|
+
id: <Node Public Key Id Hex String>
|
|
25
|
+
sockets: [<Peer Socket String>]
|
|
26
|
+
}
|
|
27
|
+
}]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@returns via cbk or Promise
|
|
31
|
+
{
|
|
32
|
+
id: <Seller Public Key Hex String>
|
|
33
|
+
}
|
|
34
|
+
*/
|
|
35
|
+
module.exports = ({lnd, logger, nodes}, cbk) => {
|
|
36
|
+
return new Promise((resolve, reject) => {
|
|
37
|
+
return asyncAuto({
|
|
38
|
+
// Check arguments
|
|
39
|
+
validate: cbk => {
|
|
40
|
+
if (!logger) {
|
|
41
|
+
return cbk([400, 'ExpectedWinstonLoggerToConnectToSeller']);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!lnd) {
|
|
45
|
+
return cbk([400, 'ExpectedLndToConnectToSeller']);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!isArray(nodes)) {
|
|
49
|
+
return cbk([400, 'ExpectedArrayOfNodesToConnectToSeller']);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return cbk();
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
// Derive the self public key
|
|
56
|
+
getIdentity: ['validate', ({}, cbk) => getIdentity({lnd}, cbk)],
|
|
57
|
+
|
|
58
|
+
// Get the list of connected peers
|
|
59
|
+
getPeers: ['validate', ({}, cbk) => getPeers({lnd}, cbk)],
|
|
60
|
+
|
|
61
|
+
// Find node connect info to connect to
|
|
62
|
+
getNodes: ['getIdentity', ({getIdentity}, cbk) => {
|
|
63
|
+
return asyncMap(nodes, (connect, cbk) => {
|
|
64
|
+
// Exit early when the node is self
|
|
65
|
+
if (!!connect.node && connect.node.id === getIdentity.public_key) {
|
|
66
|
+
return cbk();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Exit early when the node id and sockets is specified directly
|
|
70
|
+
if (!!connect.node) {
|
|
71
|
+
return getNode({
|
|
72
|
+
lnd,
|
|
73
|
+
is_omitting_channels: true,
|
|
74
|
+
public_key: connect.node.id,
|
|
75
|
+
},
|
|
76
|
+
(err, res) => {
|
|
77
|
+
if (!!err) {
|
|
78
|
+
return cbk(null, connect.node);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const sockets = res.sockets.map(n => n.socket);
|
|
82
|
+
|
|
83
|
+
return cbk(null, {
|
|
84
|
+
id: connect.node.id,
|
|
85
|
+
sockets: uniq([].concat(sockets).concat(connect.node.sockets)),
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Exit early when there is no landmark channel
|
|
91
|
+
if (!connect.high_channel && !connect.low_channel) {
|
|
92
|
+
return cbk();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const channelId = connect.high_channel || connect.low_channel;
|
|
96
|
+
|
|
97
|
+
// Get the public keys of the landmark channel
|
|
98
|
+
return getChannel({lnd, id: channelId}, (err, res) => {
|
|
99
|
+
// Exit early when the channel cannot be fetched
|
|
100
|
+
if (!!err) {
|
|
101
|
+
return cbk();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const [low, high] = res.policies.map(n => n.public_key);
|
|
105
|
+
|
|
106
|
+
// The node key is either the lower or the higher public key
|
|
107
|
+
const nodeId = !!connect.low_channel ? low : high;
|
|
108
|
+
|
|
109
|
+
// Exit early when the node is self
|
|
110
|
+
if (nodeId === getIdentity.public_key) {
|
|
111
|
+
return cbk();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Find the sockets for the node
|
|
115
|
+
return getNode({
|
|
116
|
+
lnd,
|
|
117
|
+
is_omitting_channels: true,
|
|
118
|
+
public_key: nodeId,
|
|
119
|
+
},
|
|
120
|
+
(err, res) => {
|
|
121
|
+
if (!!err) {
|
|
122
|
+
return cbk();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return cbk(null, {
|
|
126
|
+
id: nodeId,
|
|
127
|
+
sockets: res.sockets.map(n => n.socket),
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
},
|
|
132
|
+
cbk);
|
|
133
|
+
}],
|
|
134
|
+
|
|
135
|
+
// Try and connect to a node in order to do p2p messaging
|
|
136
|
+
connect: ['getNodes', 'getPeers', ({getNodes, getPeers}, cbk) => {
|
|
137
|
+
const connected = getPeers.peers.map(n => n.public_key);
|
|
138
|
+
|
|
139
|
+
// Look for a node that is already a connected peer
|
|
140
|
+
const [alreadyConnected] = getNodes.filter(node => {
|
|
141
|
+
return !!node && connected.includes(node.id);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// Exit early when already connected to a node
|
|
145
|
+
if (!!alreadyConnected) {
|
|
146
|
+
return cbk(null, alreadyConnected);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Try and connect to a referenced node
|
|
150
|
+
return asyncDetect(getNodes.filter(n => !!n), (node, cbk) => {
|
|
151
|
+
logger.info({connecting_to: node.id});
|
|
152
|
+
|
|
153
|
+
// Attempt referenced sockets to establish p2p connection
|
|
154
|
+
return asyncDetectSeries(node.sockets, (socket, cbk) => {
|
|
155
|
+
return addPeer({lnd, socket, public_key: node.id}, err => {
|
|
156
|
+
// Stop trying sockets when there is no error
|
|
157
|
+
return cbk(null, !err);
|
|
158
|
+
});
|
|
159
|
+
},
|
|
160
|
+
(err, socket) => {
|
|
161
|
+
if (!!err) {
|
|
162
|
+
return cbk(err);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// The node is connected if one of the sockets worked
|
|
166
|
+
return cbk(null, !!socket);
|
|
167
|
+
});
|
|
168
|
+
},
|
|
169
|
+
cbk);
|
|
170
|
+
}],
|
|
171
|
+
|
|
172
|
+
// Connected to seller
|
|
173
|
+
connected: ['connect', ({connect}, cbk) => {
|
|
174
|
+
if (!connect) {
|
|
175
|
+
return cbk([503, 'FailedToConnectToNode']);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return cbk(null, {id: connect.id});
|
|
179
|
+
}],
|
|
180
|
+
},
|
|
181
|
+
returnResult({reject, resolve, of: 'connected'}, cbk));
|
|
182
|
+
});
|
|
183
|
+
};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
const asyncAuto = require('async/auto');
|
|
2
|
+
const asyncRetry = require('async/retry');
|
|
3
|
+
const {getPrices} = require('@alexbosworth/fiat');
|
|
4
|
+
const {returnResult} = require('asyncjs-util');
|
|
5
|
+
|
|
6
|
+
const cached = {};
|
|
7
|
+
const convert = (price, rate) => Math.round((price * 1e8) / (rate / 100));
|
|
8
|
+
const defaultFiatRateProvider = 'coingecko';
|
|
9
|
+
const interval = 1000;
|
|
10
|
+
const isFresh = date => Date.now() - date > 1000 * 60 * 10;
|
|
11
|
+
let isLocked = false;
|
|
12
|
+
const isNumber = n => !isNaN(n);
|
|
13
|
+
const {now} = Date;
|
|
14
|
+
const parseFiat = n => n.split('*')[1];
|
|
15
|
+
const parseFiatPrice = n => Number(n.split('*')[0]);
|
|
16
|
+
const removeSpaces = s => s.replace(/\s/g, '');
|
|
17
|
+
|
|
18
|
+
/** Converts fiat to tokens
|
|
19
|
+
|
|
20
|
+
A fiat price expression looks like AMOUNT*USD
|
|
21
|
+
|
|
22
|
+
{
|
|
23
|
+
price: <Fiat Price Expression String>
|
|
24
|
+
request: <Request Function>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@returns
|
|
28
|
+
{
|
|
29
|
+
cost: <Price of Trade in Tokens>
|
|
30
|
+
}
|
|
31
|
+
*/
|
|
32
|
+
module.exports = ({price, request}, cbk) => {
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
return asyncAuto({
|
|
35
|
+
// Check arguments
|
|
36
|
+
validate: cbk => {
|
|
37
|
+
if (!price) {
|
|
38
|
+
return cbk([400, 'ExpectedFiatPriceToConvertFiat']);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!request) {
|
|
42
|
+
return cbk([400, 'ExpectedRequestFunctionToConvertFiat']);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!isNumber(parseFiatPrice(price))) {
|
|
46
|
+
return cbk([400, 'ExpectedFiatPriceNumberToConvert']);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return cbk();
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
// Wait for request lock to unlock
|
|
53
|
+
waitForLock: ['validate', ({}, cbk) => {
|
|
54
|
+
return asyncRetry({interval}, cbk => {
|
|
55
|
+
if (isLocked) {
|
|
56
|
+
return cbk([503, 'FailedToGetFiatRatesDueToBusyRateLookups']);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return cbk();
|
|
60
|
+
},
|
|
61
|
+
cbk);
|
|
62
|
+
}],
|
|
63
|
+
|
|
64
|
+
// Get the current exchange rate
|
|
65
|
+
getRate: ['waitForLock', ({}, cbk) => {
|
|
66
|
+
const fiat = parseFiat(removeSpaces(price));
|
|
67
|
+
|
|
68
|
+
// Exit early when the rate has a fresh cache
|
|
69
|
+
if (!!cached[fiat] && isFresh(cached[fiat].date)) {
|
|
70
|
+
return cbk(null, cached[fiat].rate);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Enable the request lock to avoid making simultaneous requests
|
|
74
|
+
isLocked = true;
|
|
75
|
+
|
|
76
|
+
return getPrices({
|
|
77
|
+
request,
|
|
78
|
+
from: defaultFiatRateProvider,
|
|
79
|
+
symbols: [fiat],
|
|
80
|
+
},
|
|
81
|
+
(err, res) => {
|
|
82
|
+
// Disable the request lock to allow another request through
|
|
83
|
+
isLocked = false;
|
|
84
|
+
|
|
85
|
+
if (!!err) {
|
|
86
|
+
return cbk(err);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const [{rate}] = res.tickers;
|
|
90
|
+
|
|
91
|
+
if (!rate) {
|
|
92
|
+
return cbk([503, 'ExpectedFiatRateFromRateProvider']);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Update the value cache to avoid hammering the service
|
|
96
|
+
cached[fiat] = {rate, date: now()};
|
|
97
|
+
|
|
98
|
+
// Return the fresh value
|
|
99
|
+
return cbk(null, rate);
|
|
100
|
+
});
|
|
101
|
+
}],
|
|
102
|
+
|
|
103
|
+
// Convert the price into a tokens cost
|
|
104
|
+
cost: ['getRate', ({getRate}, cbk) => {
|
|
105
|
+
const cost = convert(parseFiatPrice(price), getRate);
|
|
106
|
+
|
|
107
|
+
return cbk(null, {cost});
|
|
108
|
+
}],
|
|
109
|
+
},
|
|
110
|
+
returnResult({reject, resolve, of: 'cost'}, cbk));
|
|
111
|
+
});
|
|
112
|
+
};
|
|
@@ -4,17 +4,23 @@ const {returnResult} = require('asyncjs-util');
|
|
|
4
4
|
|
|
5
5
|
const encodeAnchoredTrade = require('./encode_anchored_trade');
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const maxDescLength = 100;
|
|
8
8
|
const maxSecretLength = 100;
|
|
9
9
|
|
|
10
10
|
/** Create an anchored trade
|
|
11
11
|
|
|
12
|
+
Either a channel or description and secret is required
|
|
13
|
+
|
|
14
|
+
Either a price or tokens is required
|
|
15
|
+
|
|
12
16
|
{
|
|
13
|
-
|
|
17
|
+
[channel]: <Channel Sale Capacity Tokens Number>
|
|
18
|
+
[description]: <Trade Description String>
|
|
14
19
|
expires_at: <Trade Expires at ISO 8601 Date String>
|
|
15
20
|
lnd: <Authenticated LND API Object>
|
|
16
|
-
|
|
17
|
-
|
|
21
|
+
[price]: <Trade Price String>
|
|
22
|
+
[secret]: <Trade Secret String>
|
|
23
|
+
[tokens]: <Trade Price Tokens Number>
|
|
18
24
|
}
|
|
19
25
|
|
|
20
26
|
@returns via cbk or Promise
|
|
@@ -27,11 +33,15 @@ module.exports = (args, cbk) => {
|
|
|
27
33
|
return asyncAuto({
|
|
28
34
|
// Check arguments
|
|
29
35
|
validate: cbk => {
|
|
30
|
-
if (args.
|
|
36
|
+
if (!args.channel && !args.description) {
|
|
31
37
|
return cbk([400, 'ExpectedTradeDescriptionToCreateAnchoredTrade']);
|
|
32
38
|
}
|
|
33
39
|
|
|
34
|
-
if (args.
|
|
40
|
+
if (!args.channel && !args.secret) {
|
|
41
|
+
return cbk([400, 'ExpectedEitherChannelOrSecretToTrade']);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!!args.description && args.description.length > maxDescLength) {
|
|
35
45
|
return cbk([400, 'ExpectedShorterTradeDescriptionToCreateTrade']);
|
|
36
46
|
}
|
|
37
47
|
|
|
@@ -47,28 +57,27 @@ module.exports = (args, cbk) => {
|
|
|
47
57
|
return cbk([400, 'ExpectedAuthenticatedLndToCreateAnchoredTrade']);
|
|
48
58
|
}
|
|
49
59
|
|
|
50
|
-
if (!args.
|
|
51
|
-
return cbk([400, '
|
|
60
|
+
if (!args.price && !args.tokens) {
|
|
61
|
+
return cbk([400, 'ExpectedTokensPriceToCreateAnchoredTrade']);
|
|
52
62
|
}
|
|
53
63
|
|
|
54
|
-
if (args.secret.length > maxSecretLength) {
|
|
64
|
+
if (!!args.secret && args.secret.length > maxSecretLength) {
|
|
55
65
|
return cbk([400, 'ExpectedShorterTradeSecretToCreateAnchoredTrade']);
|
|
56
66
|
}
|
|
57
67
|
|
|
58
|
-
if (!args.tokens) {
|
|
59
|
-
return cbk([400, 'ExpectedTokensPriceToCreateAnchoredTrade']);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
68
|
return cbk();
|
|
63
69
|
},
|
|
64
70
|
|
|
65
71
|
// Create the trade
|
|
66
72
|
create: ['validate', ({}, cbk) => {
|
|
67
73
|
const {encoded} = encodeAnchoredTrade({
|
|
74
|
+
channel: args.channel,
|
|
68
75
|
description: args.description,
|
|
76
|
+
price: args.price,
|
|
69
77
|
secret: args.secret,
|
|
70
78
|
});
|
|
71
79
|
|
|
80
|
+
// The anchor invoice description will contain the trade details
|
|
72
81
|
return createInvoice({
|
|
73
82
|
description: encoded,
|
|
74
83
|
expires_at: args.expires_at,
|