evernode-js-client 0.5.13 → 0.5.14

Sign up to get free protection for your applications and to get access to all the features.
package/src/xrpl-api.js DELETED
@@ -1,301 +0,0 @@
1
- const xrpl = require('xrpl');
2
- const kp = require('ripple-keypairs');
3
- const { EventEmitter } = require('./event-emitter');
4
- const { DefaultValues } = require('./defaults');
5
- const { TransactionHelper } = require('./transaction-helper');
6
- const { XrplApiEvents } = require('./xrpl-common');
7
-
8
- const MAX_PAGE_LIMIT = 400;
9
- const API_REQ_TYPE = {
10
- NAMESPACE_ENTRIES: 'namespace_entries',
11
- ACCOUNT_OBJECTS: 'account_objects',
12
- LINES: 'lines',
13
- ACCOUNT_NFTS: 'account_nfts',
14
- OFFERS: 'offers',
15
- TRANSACTIONS: 'transactions'
16
- }
17
-
18
- class XrplApi {
19
-
20
- #rippledServer;
21
- #client;
22
- #events = new EventEmitter();
23
- #addressSubscriptions = [];
24
- #maintainConnection = false;
25
-
26
- constructor(rippledServer = null, options = {}) {
27
-
28
- this.#rippledServer = rippledServer || DefaultValues.rippledServer;
29
- this.#initXrplClient(options.xrplClientOptions);
30
- }
31
-
32
- async #initXrplClient(xrplClientOptions = {}) {
33
-
34
- if (this.#client) { // If the client already exists, clean it up.
35
- this.#client.removeAllListeners(); // Remove existing event listeners to avoid them getting called from the old client object.
36
- await this.#client.disconnect();
37
- this.#client = null;
38
- }
39
-
40
- this.#client = new xrpl.Client(this.#rippledServer, xrplClientOptions);
41
-
42
- this.#client.on('error', (errorCode, errorMessage) => {
43
- console.log(errorCode + ': ' + errorMessage);
44
- });
45
-
46
- this.#client.on('disconnected', (code) => {
47
- if (this.#maintainConnection) {
48
- console.log(`Connection failure for ${this.#rippledServer} (code:${code})`);
49
- console.log("Reinitializing xrpl client.");
50
- this.#initXrplClient().then(() => this.#connectXrplClient(true));
51
- }
52
- });
53
-
54
- this.#client.on('ledgerClosed', (ledger) => {
55
- this.ledgerIndex = ledger.ledger_index;
56
- this.#events.emit(XrplApiEvents.LEDGER, ledger);
57
- });
58
-
59
- this.#client.on("transaction", async (data) => {
60
- if (data.validated) {
61
- // NFTokenAcceptOffer transactions does not contain a Destination. So we check whether the accepted offer is created by which subscribed account
62
- if (data.transaction.TransactionType === 'NFTokenAcceptOffer') {
63
- // We take all the offers created by subscribed accounts in previous ledger until we get the respective offer.
64
- for (const subscription of this.#addressSubscriptions) {
65
- const offer = (await this.getNftOffers(subscription.address, { ledger_index: data.ledger_index - 1 }))?.find(o => o.index === (data.transaction.NFTokenSellOffer || data.transaction.NFTokenBuyOffer));
66
- // When we find the respective offer. We populate the destination and offer info and then we break the loop.
67
- if (offer) {
68
- // We populate some sell offer properties to the transaction to be sent with the event.
69
- data.transaction.Destination = subscription.address;
70
- // Replace the offer with the found offer object.
71
- if (data.transaction.NFTokenSellOffer)
72
- data.transaction.NFTokenSellOffer = offer;
73
- else if (data.transaction.NFTokenBuyOffer)
74
- data.transaction.NFTokenBuyOffer = offer;
75
- break;
76
- }
77
- }
78
- }
79
-
80
- const matches = this.#addressSubscriptions.filter(s => s.address === data.transaction.Destination); // Only incoming transactions.
81
- if (matches.length > 0) {
82
- const tx = {
83
- LedgerHash: data.ledger_hash,
84
- LedgerIndex: data.ledger_index,
85
- ...data.transaction
86
- }; // Create an object copy. Otherwise xrpl client will mutate the transaction object,
87
- const eventName = tx.TransactionType.toLowerCase();
88
- // Emit the event only for successful transactions, Otherwise emit error.
89
- if (data.engine_result === "tesSUCCESS") {
90
- tx.Memos = TransactionHelper.deserializeMemos(tx.Memos);
91
- matches.forEach(s => s.handler(eventName, tx));
92
- }
93
- else {
94
- matches.forEach(s => s.handler(eventName, null, data.engine_result_message));
95
- }
96
- }
97
- }
98
- });
99
- }
100
-
101
- async #connectXrplClient(reconnect = false) {
102
-
103
- if (reconnect) {
104
- let attempts = 0;
105
- while (this.#maintainConnection) { // Keep attempting until consumer calls disconnect() manually.
106
- console.log(`Reconnection attempt ${++attempts}`);
107
- try {
108
- await this.#client.connect();
109
- break;
110
- }
111
- catch {
112
- if (this.#maintainConnection) {
113
- const delaySec = 2 * attempts; // Retry with backoff delay.
114
- console.log(`Attempt ${attempts} failed. Retrying in ${delaySec}s...`);
115
- await new Promise(resolve => setTimeout(resolve, delaySec * 1000));
116
- }
117
- }
118
- }
119
- }
120
- else {
121
- // Single attempt and throw error. Used for initial connect() call.
122
- await this.#client.connect();
123
- }
124
-
125
- // After connection established, check again whether maintainConnections has become false.
126
- // This is in case the consumer has called disconnect() while connection is being established.
127
- if (this.#maintainConnection) {
128
- this.ledgerIndex = await this.#client.getLedgerIndex();
129
- this.#subscribeToStream('ledger');
130
-
131
- // Re-subscribe to existing account address subscriptions (in case this is a reconnect)
132
- if (this.#addressSubscriptions.length > 0)
133
- await this.#client.request({ command: 'subscribe', accounts: this.#addressSubscriptions.map(s => s.address) });
134
- }
135
- else {
136
- await this.disconnect();
137
- }
138
- }
139
-
140
- async #requestWithPaging(requestObj, requestType) {
141
- let res = [];
142
- let checked = false;
143
- let resp;
144
- let count = requestObj?.limit;
145
-
146
- while ((!count || count > 0) && (!checked || resp?.result?.marker)) {
147
- checked = true;
148
- requestObj.limit = count ? Math.min(count, MAX_PAGE_LIMIT) : MAX_PAGE_LIMIT;
149
- if (resp?.result?.marker)
150
- requestObj.marker = resp?.result?.marker;
151
- else
152
- delete requestObj.marker;
153
- resp = (await this.#client.request(requestObj));
154
- if (resp?.result && resp?.result[requestType])
155
- res.push(...resp.result[requestType]);
156
- if (count)
157
- count -= requestObj.limit;
158
- }
159
-
160
- return res;
161
- }
162
-
163
- on(event, handler) {
164
- this.#events.on(event, handler);
165
- }
166
-
167
- once(event, handler) {
168
- this.#events.once(event, handler);
169
- }
170
-
171
- off(event, handler = null) {
172
- this.#events.off(event, handler);
173
- }
174
-
175
- async connect() {
176
- if (this.#maintainConnection)
177
- return;
178
-
179
- this.#maintainConnection = true;
180
- await this.#connectXrplClient();
181
- }
182
-
183
- async disconnect() {
184
- this.#maintainConnection = false;
185
-
186
- if (this.#client.isConnected()) {
187
- await this.#client.disconnect().catch(console.error);
188
- }
189
- }
190
-
191
- async isValidKeyForAddress(publicKey, address) {
192
- const info = await this.getAccountInfo(address);
193
- const accountFlags = xrpl.parseAccountRootFlags(info.Flags);
194
- const regularKey = info.RegularKey;
195
- const derivedPubKeyAddress = kp.deriveAddress(publicKey);
196
-
197
- // If the master key is disabled the derived pubkey address should be the regular key.
198
- // Otherwise it could be account address or the regular key
199
- if (accountFlags.lsfDisableMaster)
200
- return regularKey && (derivedPubKeyAddress === regularKey);
201
- else
202
- return derivedPubKeyAddress === address || (regularKey && derivedPubKeyAddress === regularKey);
203
- }
204
-
205
- async isAccountExists(address) {
206
- try {
207
- await this.#client.request({ command: 'account_info', account: address });
208
- return true;
209
- }
210
- catch (e) {
211
- if (e.data.error === 'actNotFound') return false;
212
- else throw e;
213
- }
214
- }
215
-
216
- async getAccountInfo(address) {
217
- const resp = (await this.#client.request({ command: 'account_info', account: address }));
218
- return resp?.result?.account_data;
219
- }
220
-
221
- async getAccountObjects(address, options) {
222
- return this.#requestWithPaging({ command: 'account_objects', account: address, ...options }, API_REQ_TYPE.ACCOUNT_OBJECTS);
223
- }
224
-
225
- async getNamespaceEntries(address, namespaceId, options) {
226
- return this.#requestWithPaging({ command: 'account_namespace', account: address, namespace_id: namespaceId, ...options }, API_REQ_TYPE.NAMESPACE_ENTRIES);
227
- }
228
-
229
- async getNftOffers(address, options) {
230
- const offers = await this.getAccountObjects(address, options);
231
- // TODO: Pass rippled filter parameter when xrpl.js supports it.
232
- return offers.filter(o => o.LedgerEntryType == 'NFTokenOffer');
233
- }
234
-
235
- async getTrustlines(address, options) {
236
- return this.#requestWithPaging({ command: 'account_lines', account: address, ledger_index: "validated", ...options }, API_REQ_TYPE.LINES);
237
- }
238
-
239
- async getAccountTrx(address, options) {
240
- return this.#requestWithPaging({ command: 'account_tx', account: address, ...options }, API_REQ_TYPE.TRANSACTIONS);
241
- }
242
-
243
- async getNfts(address, options) {
244
- return this.#requestWithPaging({ command: 'account_nfts', account: address, ledger_index: "validated", ...options }, API_REQ_TYPE.ACCOUNT_NFTS);
245
- }
246
-
247
- async getOffers(address, options) {
248
- return this.#requestWithPaging({ command: 'account_offers', account: address, ledger_index: "validated", ...options }, API_REQ_TYPE.OFFERS);
249
- }
250
-
251
- async getSellOffers(nfTokenId, options = {}) {
252
- return this.#requestWithPaging({ command: 'nft_sell_offers', nft_id: nfTokenId, ledger_index: "validated", ...options }, API_REQ_TYPE.OFFERS);
253
- }
254
-
255
- async getBuyOffers(nfTokenId, options = {}) {
256
- return this.#requestWithPaging({ command: 'nft_buy_offers', nft_id: nfTokenId, ledger_index: "validated", ...options }, API_REQ_TYPE.OFFERS);
257
- }
258
-
259
- async getLedgerEntry(index, options) {
260
- try {
261
- const resp = (await this.#client.request({ command: 'ledger_entry', index: index, ledger_index: "validated", ...options }));
262
- return resp?.result?.node;
263
-
264
- } catch (e) {
265
- if (e?.data?.error === 'entryNotFound')
266
- return null;
267
- throw e;
268
- }
269
- }
270
-
271
- async submitAndVerify(tx, options) {
272
- return await this.#client.submitAndWait(tx, options);
273
- }
274
-
275
- async subscribeToAddress(address, handler) {
276
- this.#addressSubscriptions.push({ address: address, handler: handler });
277
- await this.#client.request({ command: 'subscribe', accounts: [address] });
278
- }
279
-
280
- async unsubscribeFromAddress(address, handler) {
281
- for (let i = this.#addressSubscriptions.length - 1; i >= 0; i--) {
282
- const sub = this.#addressSubscriptions[i];
283
- if (sub.address === address && sub.handler === handler)
284
- this.#addressSubscriptions.splice(i, 1);
285
- }
286
- await this.#client.request({ command: 'unsubscribe', accounts: [address] });
287
- }
288
-
289
- async getTransactionFee(txBlob) {
290
- const fees = await this.#client.request({ command: 'fee', tx_blob: txBlob });
291
- return fees?.result?.drops?.base_fee;
292
- }
293
-
294
- async #subscribeToStream(streamName) {
295
- await this.#client.request({ command: 'subscribe', streams: [streamName] });
296
- }
297
- }
298
-
299
- module.exports = {
300
- XrplApi
301
- }
@@ -1,17 +0,0 @@
1
- const XrplApiEvents = {
2
- LEDGER: 'ledger',
3
- PAYMENT: 'payment',
4
- NFT_OFFER_CREATE: 'nftokencreateoffer',
5
- NFT_OFFER_ACCEPT: 'nftokenacceptoffer'
6
- }
7
-
8
- const XrplConstants = {
9
- MAX_LEDGER_OFFSET: 10,
10
- XRP: 'XRP',
11
- MIN_XRP_AMOUNT: '1' // drops
12
- }
13
-
14
- module.exports = {
15
- XrplApiEvents,
16
- XrplConstants
17
- }