evernode-js-client 0.5.12 → 0.5.13

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,396 @@
1
+ const codec = require('ripple-address-codec');
2
+ const { Buffer } = require('buffer');
3
+ const { HookStateKeys, EvernodeConstants } = require('./evernode-common');
4
+ const { XflHelpers } = require('./xfl-helpers');
5
+ const crypto = require("crypto");
6
+
7
+ const NFTOKEN_PREFIX = '00000000';
8
+
9
+ const EPOCH_OFFSET = 0;
10
+ const SAVED_MOMENT_OFFSET = 1;
11
+ const PREV_MOMENT_ACTIVE_HOST_COUNT_OFFSET = 5;
12
+ const CUR_MOMENT_ACTIVE_HOST_COUNT_OFFSET = 9;
13
+ const EPOCH_POOL_OFFSET = 13;
14
+
15
+ const EPOCH_COUNT_OFFSET = 0;
16
+ const FIRST_EPOCH_REWARD_QUOTA_OFFSET = 1;
17
+ const EPOCH_REWARD_AMOUNT_OFFSET = 5;
18
+ const REWARD_START_MOMENT_OFFSET = 9;
19
+
20
+ const TRANSIT_IDX_OFFSET = 0;
21
+ const TRANSIT_MOMENT_SIZE_OFFSET = 8;
22
+ const TRANSIT_MOMENT_TYPE_OFFSET = 10;
23
+
24
+ const MOMENT_BASE_POINT_OFFSET = 0;
25
+ const MOMENT_AT_TRANSITION_OFFSET = 8;
26
+ const MOMENT_TYPE_OFFSET = 12;
27
+
28
+ const HOST_TOKEN_ID_OFFSET = 0;
29
+ const HOST_COUNTRY_CODE_OFFSET = 32;
30
+ const HOST_RESERVED_OFFSET = 34;
31
+ const HOST_DESCRIPTION_OFFSET = 42;
32
+ const HOST_REG_LEDGER_OFFSET = 68;
33
+ const HOST_REG_FEE_OFFSET = 76;
34
+ const HOST_TOT_INS_COUNT_OFFSET = 84;
35
+ const HOST_ACT_INS_COUNT_OFFSET = 88;
36
+ const HOST_HEARTBEAT_LEDGER_IDX_OFFSET = 92;
37
+ const HOST_VERSION_OFFSET = 100;
38
+ const HOST_REG_TIMESTAMP_OFFSET = 103;
39
+ const HOST_TRANSFER_FLAG_OFFSET = 111;
40
+
41
+ const HOST_ADDRESS_OFFSET = 0;
42
+ const HOST_CPU_MODEL_NAME_OFFSET = 20;
43
+ const HOST_CPU_COUNT_OFFSET = 60;
44
+ const HOST_CPU_SPEED_OFFSET = 62;
45
+ const HOST_CPU_MICROSEC_OFFSET = 64;
46
+ const HOST_RAM_MB_OFFSET = 68;
47
+ const HOST_DISK_MB_OFFSET = 72;
48
+ const HOST_EMAIL_ADDRESS_OFFSET = 76;
49
+
50
+ const PREV_HOST_ADDRESS_OFFSET = 0;
51
+ const TRANSFER_LEDGER_IDX_OFFSET = 20;
52
+ const TRANSFERRED_NFT_ID_OFFSET = 28;
53
+
54
+ const STATE_KEY_TYPES = {
55
+ TOKEN_ID: 2,
56
+ HOST_ADDR: 3,
57
+ TRANSFEREE_ADDR: 4
58
+ }
59
+
60
+ const MOMENT_TYPES = {
61
+ LEDGER: 0,
62
+ TIMESTAMP: 1
63
+ }
64
+
65
+ const TRANSFER_STATES = {
66
+ NO_TRANSFER: 0,
67
+ HAS_A_TRANSFER: 1
68
+ }
69
+
70
+ const EVERNODE_PREFIX = 'EVR';
71
+ const HOST_ADDR_KEY_ZERO_COUNT = 8;
72
+ const TRANSFEREE_ADDR_KEY_ZERO_COUNT = 8;
73
+ const HOOK_STATE_LEDGER_TYPE_PREFIX = 118; // Decimal value of ASCII 'v'
74
+ const PENDING_TRANSFER = 1;
75
+ const HOST_EMAIL_ADDRESS_LEN = 40;
76
+
77
+ class StateHelpers {
78
+ static StateTypes = {
79
+ TOKEN_ID: 'tokenId',
80
+ HOST_ADDR: 'hostAddr',
81
+ SIGLETON: 'singleton',
82
+ CONFIGURATION: 'configuration',
83
+ TRANSFEREE_ADDR: 'transfereeAddr'
84
+ }
85
+
86
+ static timeLines = {
87
+ SEC: "SEC"
88
+ }
89
+
90
+ static getStateData(states, key) {
91
+ const state = states.find(s => key === s.key);
92
+ if (!state)
93
+ return null;
94
+
95
+ return state.data;
96
+ }
97
+
98
+ static decodeHostAddressState(stateKeyBuf, stateDataBuf) {
99
+ let data = {
100
+ address: codec.encodeAccountID(stateKeyBuf.slice(12)),
101
+ nfTokenId: stateDataBuf.slice(HOST_TOKEN_ID_OFFSET, HOST_COUNTRY_CODE_OFFSET).toString('hex').toUpperCase(),
102
+ countryCode: stateDataBuf.slice(HOST_COUNTRY_CODE_OFFSET, HOST_RESERVED_OFFSET).toString(),
103
+ description: stateDataBuf.slice(HOST_DESCRIPTION_OFFSET, HOST_REG_LEDGER_OFFSET).toString().replace(/\0/g, ''),
104
+ registrationLedger: Number(stateDataBuf.readBigUInt64BE(HOST_REG_LEDGER_OFFSET)),
105
+ registrationFee: Number(stateDataBuf.readBigUInt64BE(HOST_REG_FEE_OFFSET)),
106
+ maxInstances: stateDataBuf.readUInt32BE(HOST_TOT_INS_COUNT_OFFSET),
107
+ activeInstances: stateDataBuf.readUInt32BE(HOST_ACT_INS_COUNT_OFFSET),
108
+ lastHeartbeatIndex: Number(stateDataBuf.readBigUInt64BE(HOST_HEARTBEAT_LEDGER_IDX_OFFSET)),
109
+ version: `${stateDataBuf.readUInt8(HOST_VERSION_OFFSET)}.${stateDataBuf.readUInt8(HOST_VERSION_OFFSET + 1)}.${stateDataBuf.readUInt8(HOST_VERSION_OFFSET + 2)}`,
110
+ isATransferer: (stateDataBuf.length > HOST_TRANSFER_FLAG_OFFSET && (stateDataBuf.readUInt8(HOST_TRANSFER_FLAG_OFFSET) === PENDING_TRANSFER)) ? TRANSFER_STATES.HAS_A_TRANSFER : TRANSFER_STATES.NO_TRANSFER
111
+ }
112
+ if (stateDataBuf.length > HOST_REG_TIMESTAMP_OFFSET)
113
+ data.registrationTimestamp = Number(stateDataBuf.readBigUInt64BE(HOST_REG_TIMESTAMP_OFFSET));
114
+ return data;
115
+ }
116
+
117
+ static decodeTokenIdState(stateDataBuf) {
118
+ return {
119
+ address: codec.encodeAccountID(stateDataBuf.slice(HOST_ADDRESS_OFFSET, HOST_CPU_MODEL_NAME_OFFSET)),
120
+ cpuModelName: stateDataBuf.slice(HOST_CPU_MODEL_NAME_OFFSET, HOST_CPU_COUNT_OFFSET).toString().replace(/\x00+$/, ''), // Remove trailing \x00 characters.
121
+ cpuCount: stateDataBuf.readUInt16BE(HOST_CPU_COUNT_OFFSET),
122
+ cpuMHz: stateDataBuf.readUInt16BE(HOST_CPU_SPEED_OFFSET),
123
+ cpuMicrosec: stateDataBuf.readUInt32BE(HOST_CPU_MICROSEC_OFFSET),
124
+ ramMb: stateDataBuf.readUInt32BE(HOST_RAM_MB_OFFSET),
125
+ diskMb: stateDataBuf.readUInt32BE(HOST_DISK_MB_OFFSET),
126
+ email: (stateDataBuf.length > HOST_EMAIL_ADDRESS_OFFSET ?
127
+ stateDataBuf.slice(HOST_EMAIL_ADDRESS_OFFSET, HOST_EMAIL_ADDRESS_OFFSET + HOST_EMAIL_ADDRESS_LEN).toString().toString().replace(/\0/g, '') :
128
+ "")
129
+ }
130
+ }
131
+
132
+
133
+ static decodeTransfereeAddrState(stateKeyBuf, stateDataBuf) {
134
+ const prevHostClassicAddress = codec.encodeAccountID(stateDataBuf.slice(PREV_HOST_ADDRESS_OFFSET, TRANSFER_LEDGER_IDX_OFFSET));
135
+ return {
136
+ futureOwnerAddress: codec.encodeAccountID(stateKeyBuf.slice(12)),
137
+ prevHostAddressKey: this.generateHostAddrStateKey(prevHostClassicAddress),
138
+ prevHostAddress: prevHostClassicAddress,
139
+ transferLedgerIdx: Number(stateDataBuf.readBigUInt64BE(TRANSFER_LEDGER_IDX_OFFSET)),
140
+ transferredNfTokenId: stateDataBuf.slice(TRANSFERRED_NFT_ID_OFFSET, 60).toString('hex').toUpperCase()
141
+ }
142
+ }
143
+
144
+ static decodeStateData(stateKey, stateData) {
145
+ const hexKey = stateKey.toString('hex').toUpperCase();
146
+ if (Buffer.from(HookStateKeys.PREFIX_HOST_ADDR, 'hex').compare(stateKey, 0, 4) === 0) {
147
+ return {
148
+ type: this.StateTypes.HOST_ADDR,
149
+ key: hexKey,
150
+ ...this.decodeHostAddressState(stateKey, stateData)
151
+ }
152
+ }
153
+ else if (Buffer.from(HookStateKeys.PREFIX_HOST_TOKENID, 'hex').compare(stateKey, 0, 4) === 0) {
154
+ // Generate the address state key.
155
+ const addressKeyBuf = Buffer.alloc(32, 0);
156
+ Buffer.from(HookStateKeys.PREFIX_HOST_ADDR, 'hex').copy(addressKeyBuf);
157
+ stateData.copy(addressKeyBuf, 12, HOST_ADDRESS_OFFSET, HOST_CPU_MODEL_NAME_OFFSET)
158
+ return {
159
+ type: this.StateTypes.TOKEN_ID,
160
+ key: hexKey,
161
+ addressKey: addressKeyBuf.toString('hex').toUpperCase(),
162
+ ...this.decodeTokenIdState(stateData)
163
+ }
164
+ }
165
+ else if (Buffer.from(HookStateKeys.PREFIX_TRANSFEREE_ADDR, 'hex').compare(stateKey, 0, 4) === 0) {
166
+ return {
167
+ type: this.StateTypes.TRANSFEREE_ADDR,
168
+ key: hexKey,
169
+ ...this.decodeTransfereeAddrState(stateKey, stateData)
170
+ }
171
+ }
172
+ else if (Buffer.from(HookStateKeys.HOST_COUNT, 'hex').compare(stateKey) === 0) {
173
+ return {
174
+ type: this.StateTypes.SIGLETON,
175
+ key: hexKey,
176
+ value: stateData.readUInt32BE()
177
+ }
178
+ }
179
+ else if (Buffer.from(HookStateKeys.MOMENT_BASE_INFO, 'hex').compare(stateKey) === 0) {
180
+ return {
181
+ type: this.StateTypes.SIGLETON,
182
+ key: hexKey,
183
+ value: {
184
+ baseIdx: Number(stateData.readBigUInt64BE(MOMENT_BASE_POINT_OFFSET)),
185
+ baseTransitionMoment: stateData.length > MOMENT_AT_TRANSITION_OFFSET ? stateData.readUInt32BE(MOMENT_AT_TRANSITION_OFFSET) : 0,
186
+ momentType: (stateData.length <= MOMENT_TYPE_OFFSET || stateData.readUInt8(MOMENT_TYPE_OFFSET) === MOMENT_TYPES.LEDGER) ? 'ledger' : 'timestamp'
187
+ }
188
+ }
189
+ }
190
+ else if (Buffer.from(HookStateKeys.HOST_REG_FEE, 'hex').compare(stateKey) === 0 || Buffer.from(HookStateKeys.MAX_REG, 'hex').compare(stateKey) === 0) {
191
+ return {
192
+ type: this.StateTypes.SIGLETON,
193
+ key: hexKey,
194
+ value: Number(stateData.readBigUInt64BE())
195
+ }
196
+ }
197
+ else if (Buffer.from(HookStateKeys.EVR_ISSUER_ADDR, 'hex').compare(stateKey) === 0 || Buffer.from(HookStateKeys.FOUNDATION_ADDR, 'hex').compare(stateKey) === 0) {
198
+ return {
199
+ type: this.StateTypes.CONFIGURATION,
200
+ key: hexKey,
201
+ value: codec.encodeAccountID(stateData)
202
+ }
203
+ }
204
+ else if (Buffer.from(HookStateKeys.MOMENT_SIZE, 'hex').compare(stateKey) === 0 ||
205
+ Buffer.from(HookStateKeys.HOST_HEARTBEAT_FREQ, 'hex').compare(stateKey) === 0 ||
206
+ Buffer.from(HookStateKeys.LEASE_ACQUIRE_WINDOW, 'hex').compare(stateKey) === 0) {
207
+ return {
208
+ type: this.StateTypes.CONFIGURATION,
209
+ key: hexKey,
210
+ value: stateData.readUInt16BE()
211
+ }
212
+ }
213
+ else if (Buffer.from(HookStateKeys.MINT_LIMIT, 'hex').compare(stateKey) === 0 || Buffer.from(HookStateKeys.FIXED_REG_FEE, 'hex').compare(stateKey) === 0) {
214
+ return {
215
+ type: this.StateTypes.CONFIGURATION,
216
+ key: hexKey,
217
+ value: Number(stateData.readBigUInt64BE())
218
+ }
219
+ }
220
+ else if (Buffer.from(HookStateKeys.PURCHASER_TARGET_PRICE, 'hex').compare(stateKey) === 0) {
221
+ const xfl = stateData.readBigInt64BE(0);
222
+ const val = XflHelpers.toString(xfl);
223
+ return {
224
+ type: this.StateTypes.CONFIGURATION,
225
+ key: hexKey,
226
+ value: val
227
+ }
228
+ }
229
+ else if (Buffer.from(HookStateKeys.REWARD_CONFIGURATION, 'hex').compare(stateKey) === 0) {
230
+ return {
231
+ type: this.StateTypes.CONFIGURATION,
232
+ key: hexKey,
233
+ value: {
234
+ epochCount: stateData.readUInt8(EPOCH_COUNT_OFFSET),
235
+ firstEpochRewardQuota: stateData.readUInt32BE(FIRST_EPOCH_REWARD_QUOTA_OFFSET),
236
+ epochRewardAmount: stateData.readUInt32BE(EPOCH_REWARD_AMOUNT_OFFSET),
237
+ rewardStartMoment: stateData.readUInt32BE(REWARD_START_MOMENT_OFFSET)
238
+ }
239
+ }
240
+ }
241
+ else if (Buffer.from(HookStateKeys.REWARD_INFO, 'hex').compare(stateKey) === 0) {
242
+ return {
243
+ type: this.StateTypes.SIGLETON,
244
+ key: hexKey,
245
+ value: {
246
+ epoch: stateData.readUInt8(EPOCH_OFFSET),
247
+ savedMoment: stateData.readUInt32BE(SAVED_MOMENT_OFFSET),
248
+ prevMomentActiveHostCount: stateData.readUInt32BE(PREV_MOMENT_ACTIVE_HOST_COUNT_OFFSET),
249
+ curMomentActiveHostCount: stateData.readUInt32BE(CUR_MOMENT_ACTIVE_HOST_COUNT_OFFSET),
250
+ epochPool: XflHelpers.toString(stateData.readBigInt64BE(EPOCH_POOL_OFFSET))
251
+ }
252
+ }
253
+ }
254
+ else if (Buffer.from(HookStateKeys.MAX_TOLERABLE_DOWNTIME, 'hex').compare(stateKey) === 0) {
255
+ return {
256
+ type: this.StateTypes.CONFIGURATION,
257
+ key: hexKey,
258
+ value: stateData.readUInt16BE()
259
+ }
260
+ }
261
+ else if (Buffer.from(HookStateKeys.MOMENT_TRANSIT_INFO, 'hex').compare(stateKey) === 0) {
262
+ Buffer.alloc(1).readUInt8()
263
+ return {
264
+ type: this.StateTypes.CONFIGURATION,
265
+ key: hexKey,
266
+ value: {
267
+ transitionIndex: Number(stateData.readBigInt64BE(TRANSIT_IDX_OFFSET)),
268
+ momentSize: stateData.readUInt16BE(TRANSIT_MOMENT_SIZE_OFFSET),
269
+ momentType: stateData.readUInt8(TRANSIT_MOMENT_TYPE_OFFSET) === MOMENT_TYPES.LEDGER ? 'ledger' : 'timestamp'
270
+ }
271
+ }
272
+ }
273
+ else if (Buffer.from(HookStateKeys.MAX_TRX_EMISSION_FEE, 'hex').compare(stateKey) === 0) {
274
+ return {
275
+ type: this.StateTypes.CONFIGURATION,
276
+ key: hexKey,
277
+ value: Number(stateData.readBigUInt64BE())
278
+ }
279
+ }
280
+ else
281
+ throw { type: 'Validation Error', message: 'Invalid state key.' };
282
+ }
283
+
284
+ static decodeStateKey(stateKey) {
285
+ const hexKey = stateKey.toString('hex').toUpperCase();
286
+ if (Buffer.from(HookStateKeys.PREFIX_HOST_ADDR, 'hex').compare(stateKey, 0, 4) === 0) {
287
+ return {
288
+ key: hexKey,
289
+ type: this.StateTypes.HOST_ADDR
290
+ };
291
+ }
292
+ else if (Buffer.from(HookStateKeys.PREFIX_HOST_TOKENID, 'hex').compare(stateKey, 0, 4) === 0) {
293
+ return {
294
+ key: hexKey,
295
+ type: this.StateTypes.TOKEN_ID
296
+ };
297
+ }
298
+ else if (Buffer.from(HookStateKeys.PREFIX_TRANSFEREE_ADDR, 'hex').compare(stateKey, 0, 4) === 0) {
299
+ return {
300
+ key: hexKey,
301
+ type: this.StateTypes.TRANSFEREE_ADDR
302
+ };
303
+ }
304
+ else if (Buffer.from(HookStateKeys.HOST_COUNT, 'hex').compare(stateKey) === 0 ||
305
+ Buffer.from(HookStateKeys.MOMENT_BASE_INFO, 'hex').compare(stateKey) === 0 ||
306
+ Buffer.from(HookStateKeys.HOST_REG_FEE, 'hex').compare(stateKey) === 0 ||
307
+ Buffer.from(HookStateKeys.MAX_REG, 'hex').compare(stateKey) === 0 ||
308
+ Buffer.from(HookStateKeys.REWARD_INFO, 'hex').compare(stateKey) === 0) {
309
+ return {
310
+ key: hexKey,
311
+ type: this.STATE_TYPES.SIGLETON
312
+ };
313
+ }
314
+ else if (Buffer.from(HookStateKeys.EVR_ISSUER_ADDR, 'hex').compare(stateKey) === 0 ||
315
+ Buffer.from(HookStateKeys.FOUNDATION_ADDR, 'hex').compare(stateKey) === 0 ||
316
+ Buffer.from(HookStateKeys.MOMENT_SIZE, 'hex').compare(stateKey) === 0 ||
317
+ Buffer.from(HookStateKeys.PURCHASER_TARGET_PRICE, 'hex').compare(stateKey) === 0 ||
318
+ Buffer.from(HookStateKeys.HOST_HEARTBEAT_FREQ, 'hex').compare(stateKey) ||
319
+ Buffer.from(HookStateKeys.MINT_LIMIT, 'hex').compare(stateKey) === 0 ||
320
+ Buffer.from(HookStateKeys.FIXED_REG_FEE, 'hex').compare(stateKey) === 0 ||
321
+ Buffer.from(HookStateKeys.LEASE_ACQUIRE_WINDOW, 'hex').compare(stateKey) === 0 ||
322
+ Buffer.from(HookStateKeys.REWARD_CONFIGURATION, 'hex').compare(stateKey) === 0 ||
323
+ Buffer.from(HookStateKeys.MAX_TOLERABLE_DOWNTIME, 'hex').compare(stateKey) === 0 ||
324
+ Buffer.from(HookStateKeys.MOMENT_TRANSIT_INFO, 'hex').compare(stateKey) === 0 ||
325
+ Buffer.from(HookStateKeys.MAX_TRX_EMISSION_FEE, 'hex').compare(stateKey) === 0) {
326
+ return {
327
+ key: hexKey,
328
+ type: this.STATE_TYPES.CONFIGURATION
329
+ };
330
+ }
331
+ else
332
+ throw { type: 'Validation Error', message: 'Invalid state key.' };
333
+ }
334
+
335
+ static generateTokenIdStateKey(nfTokenId) {
336
+ // 1 byte - Key Type.
337
+ let buf = Buffer.allocUnsafe(1);
338
+ buf.writeUInt8(STATE_KEY_TYPES.TOKEN_ID);
339
+
340
+ const nfTokenIdBuf = Buffer.from(nfTokenId, "hex");
341
+ const stateKeyBuf = (Buffer.concat([Buffer.from(EVERNODE_PREFIX, "utf-8"), buf, nfTokenIdBuf.slice(4, 32)]));
342
+ return stateKeyBuf.toString('hex').toUpperCase();
343
+ }
344
+
345
+ static generateHostAddrStateKey(address) {
346
+ // 1 byte - Key Type.
347
+ // 8 bytes - Zeros.
348
+ let buf = Buffer.allocUnsafe(9);
349
+ buf.writeUInt8(STATE_KEY_TYPES.HOST_ADDR);
350
+ for (let i = 0; i < HOST_ADDR_KEY_ZERO_COUNT; i++) {
351
+ buf.writeUInt8(0, i + 1);
352
+ }
353
+
354
+ const addrBuf = Buffer.from(codec.decodeAccountID(address), "hex");
355
+ const stateKeyBuf = Buffer.concat([Buffer.from(EVERNODE_PREFIX, "utf-8"), buf, addrBuf]);
356
+ return stateKeyBuf.toString('hex').toUpperCase();
357
+ }
358
+
359
+ static generateTransfereeAddrStateKey(address) {
360
+ // 1 byte - Key Type.
361
+ // 8 bytes - Zeros.
362
+ let buf = Buffer.allocUnsafe(9);
363
+ buf.writeUInt8(STATE_KEY_TYPES.TRANSFEREE_ADDR);
364
+ for (let i = 0; i < TRANSFEREE_ADDR_KEY_ZERO_COUNT; i++) {
365
+ buf.writeUInt8(0, i + 1);
366
+ }
367
+
368
+ const addrBuf = Buffer.from(codec.decodeAccountID(address), "hex");
369
+ const stateKeyBuf = Buffer.concat([Buffer.from(EVERNODE_PREFIX, "utf-8"), buf, addrBuf]);
370
+ return stateKeyBuf.toString('hex').toUpperCase();
371
+ }
372
+
373
+ static getHookStateIndex(hookAccount, stateKey, hookNamespace = EvernodeConstants.HOOK_NAMESPACE) {
374
+ const typeBuf = Buffer.allocUnsafe(2);
375
+ typeBuf.writeInt16BE(HOOK_STATE_LEDGER_TYPE_PREFIX);
376
+
377
+ const accIdBuf = codec.decodeAccountID(hookAccount);
378
+ const stateKeyBuf = Buffer.from(stateKey, 'hex');
379
+ const namespaceBuf = Buffer.from(hookNamespace, 'hex');
380
+
381
+ let hash = crypto.createHash('sha512');
382
+
383
+ let data = hash.update(typeBuf);
384
+ data = hash.update(accIdBuf);
385
+ data = hash.update(stateKeyBuf);
386
+ data = hash.update(namespaceBuf);
387
+
388
+ const digest = data.digest('hex');
389
+ // Get the first 32 bytes of hash.
390
+ return digest.substring(0, 64).toUpperCase();
391
+ }
392
+ }
393
+
394
+ module.exports = {
395
+ StateHelpers
396
+ }
@@ -0,0 +1,62 @@
1
+ const { MemoFormats } = require('./evernode-common');
2
+
3
+ class TransactionHelper {
4
+
5
+ // Convert memos from our object type to xrpl lib object type.
6
+ static formatMemos(memos) {
7
+ return memos ? memos.filter(m => m.type).map(m => {
8
+ const data = (m.format === MemoFormats.HEX) ? m.data :
9
+ TransactionHelper.asciiToHex((typeof m.data === "object") ? JSON.stringify(m.data) : m.data)
10
+ return {
11
+ Memo: {
12
+ MemoType: TransactionHelper.asciiToHex(m.type),
13
+ MemoFormat: TransactionHelper.asciiToHex(m.format),
14
+ MemoData: data
15
+ }
16
+ }
17
+ }) : [];
18
+ }
19
+
20
+ // Convert memos from xrpl lib object type to our object type.
21
+ static deserializeMemos(memos) {
22
+ if (!memos)
23
+ return [];
24
+
25
+ return memos.filter(m => m.Memo).map(m => {
26
+ const format = m.Memo.MemoFormat ? TransactionHelper.hexToASCII(m.Memo.MemoFormat) : null;
27
+ const data = m.Memo.MemoData ?
28
+ ((format === MemoFormats.HEX) ? m.Memo.MemoData : TransactionHelper.hexToASCII(m.Memo.MemoData)) : null;
29
+ return {
30
+ type: m.Memo.MemoType ? TransactionHelper.hexToASCII(m.Memo.MemoType) : null,
31
+ format: format,
32
+ data: data
33
+ }
34
+ })
35
+ }
36
+
37
+ static hexToASCII(hex) {
38
+ if (!hex)
39
+ return "";
40
+
41
+ let str = "";
42
+ for (let n = 0; n < hex.length; n += 2) {
43
+ str += String.fromCharCode(parseInt(hex.substr(n, 2), 16));
44
+ }
45
+ return str;
46
+ }
47
+
48
+ static asciiToHex(str) {
49
+ if (!str)
50
+ return "";
51
+
52
+ let hex = "";
53
+ for (let n = 0; n < str.length; n++) {
54
+ hex += str.charCodeAt(n).toString(16)
55
+ }
56
+ return hex;
57
+ }
58
+ }
59
+
60
+ module.exports = {
61
+ TransactionHelper
62
+ }
@@ -0,0 +1,50 @@
1
+ const { Buffer } = require('buffer');
2
+ const { XflHelpers } = require('./xfl-helpers');
3
+ const { EvernodeConstants, ErrorReasons } = require('./evernode-common');
4
+
5
+ // Utility helper functions.
6
+ class UtilHelpers {
7
+
8
+ static readUInt(buf, base = 32, isBE = true) {
9
+ buf = Buffer.from(buf);
10
+ switch (base) {
11
+ case (8):
12
+ return buf.readUInt8();
13
+ case (16):
14
+ return isBE ? buf.readUInt16BE() : buf.readUInt16LE();
15
+ case (32):
16
+ return isBE ? buf.readUInt32BE() : buf.readUInt32LE();
17
+ case (64):
18
+ return isBE ? Number(buf.readBigUInt64BE()) : Number(buf.readBigUInt64LE());
19
+ default:
20
+ throw 'Invalid base value';
21
+ }
22
+ }
23
+
24
+ static decodeLeaseNftUri(hexUri) {
25
+ // Get the lease index from the nft URI.
26
+ // <prefix><lease index (uint16)><half of tos hash (16 bytes)><lease amount (uint32)>
27
+ const prefixLen = EvernodeConstants.LEASE_NFT_PREFIX_HEX.length / 2;
28
+ const halfToSLen = 16;
29
+ const uriBuf = Buffer.from(hexUri, 'hex');
30
+ return {
31
+ leaseIndex: uriBuf.readUint16BE(prefixLen),
32
+ halfTos: uriBuf.slice(prefixLen + 2, halfToSLen),
33
+ leaseAmount: parseFloat(XflHelpers.toString(uriBuf.readBigInt64BE(prefixLen + 2 + halfToSLen)))
34
+ }
35
+ }
36
+
37
+ static getCurrentUnixTime(format = "sec") {
38
+ const time = Date.now();
39
+ switch (format) {
40
+ case "sec":
41
+ return Math.floor(time / 1000);
42
+ default:
43
+ return time;
44
+ }
45
+ }
46
+ }
47
+
48
+ module.exports = {
49
+ UtilHelpers
50
+ }
@@ -0,0 +1,130 @@
1
+ const minMantissa = 1000000000000000n
2
+ const maxMantissa = 9999999999999999n
3
+ const minExponent = -96
4
+ const maxExponent = 80
5
+
6
+ // Helper class to handle XFL float numbers.
7
+ class XflHelpers {
8
+
9
+ static getExponent(xfl) {
10
+ if (xfl < 0n)
11
+ throw "Invalid XFL";
12
+ if (xfl == 0n)
13
+ return 0n;
14
+ return ((xfl >> 54n) & 0xFFn) - 97n;
15
+ }
16
+
17
+ static getMantissa(xfl) {
18
+ if (xfl < 0n)
19
+ throw "Invalid XFL";
20
+ if (xfl == 0n)
21
+ return 0n;
22
+ return xfl - ((xfl >> 54n) << 54n);
23
+ }
24
+
25
+ static isNegative(xfl) {
26
+ if (xfl < 0n)
27
+ throw "Invalid XFL";
28
+ if (xfl == 0n)
29
+ return false;
30
+ return ((xfl >> 62n) & 1n) == 0n;
31
+ }
32
+
33
+ static toString(xfl) {
34
+ if (xfl < 0n)
35
+ throw "Invalid XFL";
36
+ if (xfl == 0n)
37
+ return '0';
38
+
39
+ const mantissa = this.getMantissa(xfl);
40
+ const exponent = this.getExponent(xfl);
41
+ const mantissaStr = mantissa.toString();
42
+ let finalResult = '';
43
+ if (exponent > 0n) {
44
+ finalResult = mantissaStr.padEnd(mantissaStr.length + Number(exponent), '0');
45
+ } else {
46
+ const newExponent = Number(exponent) + mantissaStr.length;
47
+ const cleanedMantissa = mantissaStr.replace(/0+$/, '');
48
+ if (newExponent == 0) {
49
+ finalResult = '0.' + cleanedMantissa;
50
+ } else if (newExponent < 0) {
51
+ finalResult = '0.' + cleanedMantissa.padStart(newExponent * (-1) + cleanedMantissa.length, '0');
52
+ } else {
53
+ finalResult = mantissaStr.substr(0, newExponent) + '.' + mantissaStr.substr(newExponent).replace(/0+$/, '');
54
+ }
55
+ }
56
+ return (this.isNegative(xfl) ? '-' : '') + finalResult.replace(/\.+$/, '');
57
+ }
58
+
59
+ static getXfl(floatStr) {
60
+ let exponent;
61
+ let mantissa;
62
+ floatStr = parseFloat(floatStr).toString();
63
+
64
+ if (floatStr === '0') {
65
+ exponent = BigInt(0);
66
+ mantissa = BigInt(0);
67
+ }
68
+ else if (floatStr.includes('.')) {
69
+ const parts = floatStr.split('.');
70
+ exponent = BigInt(-parts[1].length);
71
+ mantissa = BigInt(parseInt(parts.join('')));
72
+ }
73
+ else if (floatStr.endsWith('0')) {
74
+ const mantissaStr = floatStr.replace(/0+$/g, "");
75
+ exponent = BigInt(floatStr.length - mantissaStr.length);
76
+ mantissa = BigInt(parseInt(mantissaStr));
77
+ }
78
+ else {
79
+ exponent = BigInt(0);
80
+ mantissa = BigInt(parseInt(floatStr));
81
+ }
82
+
83
+ // Convert types as needed.
84
+ if (typeof (exponent) != 'bigint')
85
+ exponent = BigInt(exponent);
86
+
87
+ if (typeof (mantissa) != 'bigint')
88
+ mantissa = BigInt(mantissa);
89
+
90
+ // Canonical zero.
91
+ if (mantissa == 0n)
92
+ return 0n;
93
+
94
+ // Normalize.
95
+ let is_negative = mantissa < 0;
96
+ if (is_negative)
97
+ mantissa *= -1n;
98
+
99
+ while (mantissa > maxMantissa) {
100
+ mantissa /= 10n;
101
+ exponent++;
102
+ }
103
+ while (mantissa < minMantissa) {
104
+ mantissa *= 10n;
105
+ exponent--;
106
+ }
107
+
108
+ // Canonical zero on mantissa underflow.
109
+ if (mantissa == 0)
110
+ return 0n;
111
+
112
+ // Under and overflows.
113
+ if (exponent > maxExponent || exponent < minExponent)
114
+ return -1; // Note this is an "invalid" XFL used to propagate errors.
115
+
116
+ exponent += 97n;
117
+
118
+ let xfl = (is_negative ? 0n : 1n);
119
+ xfl <<= 8n;
120
+ xfl |= BigInt(exponent);
121
+ xfl <<= 54n;
122
+ xfl |= BigInt(mantissa);
123
+
124
+ return xfl;
125
+ }
126
+ }
127
+
128
+ module.exports = {
129
+ XflHelpers
130
+ }