evernode-js-client 0.5.8 → 0.5.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (2) hide show
  1. package/index.js +281 -49
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -11732,6 +11732,7 @@ const { EventEmitter } = __nccwpck_require__(6170);
11732
11732
  const { UtilHelpers } = __nccwpck_require__(6687);
11733
11733
  const { FirestoreHandler } = __nccwpck_require__(9718);
11734
11734
  const { StateHelpers } = __nccwpck_require__(3860);
11735
+ const { EvernodeHelpers } = __nccwpck_require__(2523);
11735
11736
 
11736
11737
  class BaseEvernodeClient {
11737
11738
 
@@ -11905,7 +11906,8 @@ class BaseEvernodeClient {
11905
11906
  rewardInfo: HookStateKeys.REWARD_INFO,
11906
11907
  rewardConfiguration: HookStateKeys.REWARD_CONFIGURATION,
11907
11908
  hostCount: HookStateKeys.HOST_COUNT,
11908
- momentTransitInfo: HookStateKeys.MOMENT_TRANSIT_INFO
11909
+ momentTransitInfo: HookStateKeys.MOMENT_TRANSIT_INFO,
11910
+ registryMaxTrxEmitFee: HookStateKeys.MAX_TRX_EMISSION_FEE
11909
11911
  }
11910
11912
  let config = {};
11911
11913
  for (const [key, value] of Object.entries(configStateKeys)) {
@@ -12036,17 +12038,13 @@ class BaseEvernodeClient {
12036
12038
  }
12037
12039
  }
12038
12040
  else if (tx.Memos.length >= 1 &&
12039
- tx.Memos[0].type === MemoTypes.HOST_REG && tx.Memos[0].format === MemoFormats.TEXT && tx.Memos[0].data) {
12041
+ tx.Memos[0].type === MemoTypes.HOST_REG && tx.Memos[0].format === MemoFormats.HEX && tx.Memos[0].data) {
12040
12042
 
12041
- const parts = tx.Memos[0].data.split(';');
12042
12043
  return {
12043
12044
  name: EvernodeEvents.HostRegistered,
12044
12045
  data: {
12045
12046
  transaction: tx,
12046
- host: tx.Account,
12047
- token: parts[0],
12048
- instanceSize: parts[1],
12049
- location: parts[2]
12047
+ host: tx.Account
12050
12048
  }
12051
12049
  }
12052
12050
  }
@@ -12134,17 +12132,13 @@ class BaseEvernodeClient {
12134
12132
  }
12135
12133
  }
12136
12134
  else if (tx.Memos.length >= 1 &&
12137
- tx.Memos[0].type === MemoTypes.HOST_UPDATE_INFO && tx.Memos[0].format === MemoFormats.TEXT && tx.Memos[0].data) {
12138
-
12139
- const specs = tx.Memos[0].data.split(';');
12135
+ tx.Memos[0].type === MemoTypes.HOST_UPDATE_INFO && tx.Memos[0].format === MemoFormats.HEX && tx.Memos[0].data) {
12140
12136
 
12141
12137
  return {
12142
12138
  name: EvernodeEvents.HostRegUpdated,
12143
12139
  data: {
12144
12140
  transaction: tx,
12145
- host: tx.Account,
12146
- version: specs[specs.length - 1],
12147
- specs: specs,
12141
+ host: tx.Account
12148
12142
  }
12149
12143
  }
12150
12144
  }
@@ -12237,13 +12231,16 @@ class BaseEvernodeClient {
12237
12231
  async getHosts(filters = null, pageSize = null, nextPageToken = null) {
12238
12232
  const hosts = await this.#firestoreHandler.getHosts(filters, pageSize, nextPageToken);
12239
12233
  const curMomentStartIdx = await this.getMomentStartIndex();
12240
- // Populate the host active status.
12241
- (hosts.nextPageToken ? hosts.data : hosts).forEach(h => {
12242
- h.active = (h.lastHeartbeatIndex > (this.config.hostHeartbeatFreq * this.config.momentSize) ?
12243
- (h.lastHeartbeatIndex >= (curMomentStartIdx - (this.config.hostHeartbeatFreq * this.config.momentSize))) :
12244
- (h.lastHeartbeatIndex > 0))
12245
- });
12246
- return hosts;
12234
+
12235
+ return await Promise.all((hosts.nextPageToken ? hosts.data : hosts).map(async host => {
12236
+ const hostAcc = new XrplAccount(host.address);
12237
+ host.domain = await hostAcc.getDomain();
12238
+
12239
+ host.active = (host.lastHeartbeatIndex > (this.config.hostHeartbeatFreq * this.config.momentSize) ?
12240
+ (host.lastHeartbeatIndex >= (curMomentStartIdx - (this.config.hostHeartbeatFreq * this.config.momentSize))) :
12241
+ (host.lastHeartbeatIndex > 0));
12242
+ return host;
12243
+ }));
12247
12244
  }
12248
12245
 
12249
12246
  /**
@@ -12303,11 +12300,28 @@ class BaseEvernodeClient {
12303
12300
  let memoData = Buffer.allocUnsafe(20);
12304
12301
  codec.decodeAccountID(hostAddress).copy(memoData);
12305
12302
 
12306
- await this.xrplAcc.makePayment(this.registryAddress,
12307
- XrplConstants.MIN_XRP_AMOUNT,
12308
- XrplConstants.XRP,
12309
- null,
12310
- [{ type: MemoTypes.DEAD_HOST_PRUNE, format: MemoFormats.HEX, data: memoData.toString('hex') }]);
12303
+ // To obtain registration NFT Page Keylet and index.
12304
+ const hostAcc = new XrplAccount(hostAddress, null, { xrplApi: this.xrplApi });
12305
+ const regNFT = (await hostAcc.getNfts()).find(n => n.URI.startsWith(EvernodeConstants.NFT_PREFIX_HEX) && n.Issuer === this.registryAddress);
12306
+ if (regNFT) {
12307
+ // Check whether the token was actually issued from Evernode registry contract.
12308
+ const issuerHex = regNFT.NFTokenID.substr(8, 40);
12309
+ const issuerAddr = codec.encodeAccountID(Buffer.from(issuerHex, 'hex'));
12310
+ if (issuerAddr == this.registryAddress) {
12311
+ const nftPageDataBuf = await EvernodeHelpers.getNFTPageAndLocation(regNFT.NFTokenID, hostAcc, this.xrplApi);
12312
+
12313
+ await this.xrplAcc.makePayment(this.registryAddress,
12314
+ XrplConstants.MIN_XRP_AMOUNT,
12315
+ XrplConstants.XRP,
12316
+ null,
12317
+ [
12318
+ { type: MemoTypes.DEAD_HOST_PRUNE, format: MemoFormats.HEX, data: memoData.toString('hex') },
12319
+ { type: MemoTypes.HOST_REGISTRY_REF, format: MemoFormats.HEX, data: nftPageDataBuf.toString('hex') }
12320
+ ]);
12321
+ } else
12322
+ throw "Invalid Registration NFT."
12323
+ } else
12324
+ throw "No Registration NFT was found for the Host account."
12311
12325
 
12312
12326
  }
12313
12327
  }
@@ -12340,6 +12354,29 @@ const HostEvents = {
12340
12354
  ExtendLease: EvernodeEvents.ExtendLease
12341
12355
  }
12342
12356
 
12357
+ const HOST_COUNTRY_CODE_MEMO_OFFSET = 0;
12358
+ const HOST_CPU_MICROSEC_MEMO_OFFSET = 2;
12359
+ const HOST_RAM_MB_MEMO_OFFSET = 6;
12360
+ const HOST_DISK_MB_MEMO_OFFSET = 10;
12361
+ const HOST_TOT_INS_COUNT_MEMO_OFFSET = 14;
12362
+ const HOST_CPU_MODEL_NAME_MEMO_OFFSET = 18;
12363
+ const HOST_CPU_COUNT_MEMO_OFFSET = 58;
12364
+ const HOST_CPU_SPEED_MEMO_OFFSET = 60;
12365
+ const HOST_DESCRIPTION_MEMO_OFFSET = 62;
12366
+ const HOST_EMAIL_ADDRESS_MEMO_OFFSET = 88;
12367
+ const HOST_REG_MEMO_SIZE = 128;
12368
+
12369
+ const HOST_UPDATE_TOKEN_ID_MEMO_OFFSET = 0;
12370
+ const HOST_UPDATE_COUNTRY_CODE_MEMO_OFFSET = 32;
12371
+ const HOST_UPDATE_CPU_MICROSEC_MEMO_OFFSET = 34;
12372
+ const HOST_UPDATE_RAM_MB_MEMO_OFFSET = 38;
12373
+ const HOST_UPDATE_DISK_MB_MEMO_OFFSET = 42;
12374
+ const HOST_UPDATE_TOT_INS_COUNT_MEMO_OFFSET = 46;
12375
+ const HOST_UPDATE_ACT_INS_COUNT_MEMO_OFFSET = 50;
12376
+ const HOST_UPDATE_DESCRIPTION_MEMO_OFFSET = 54;
12377
+ const HOST_UPDATE_VERSION_MEMO_OFFSET = 80;
12378
+ const HOST_UPDATE_MEMO_SIZE = 83;
12379
+
12343
12380
  class HostClient extends BaseEvernodeClient {
12344
12381
 
12345
12382
  constructor(xrpAddress, xrpSecret, options = {}) {
@@ -12459,8 +12496,8 @@ class HostClient extends BaseEvernodeClient {
12459
12496
  throw "description should consist of 0-26 ascii characters except ';'";
12460
12497
 
12461
12498
  else if (!emailAddress || !(/[a-z0-9]+@[a-z]+.[a-z]{2,3}/.test(emailAddress)) || (emailAddress.length > 40))
12462
- throw "Email address should be valid and can not have more than 40 characters.";
12463
-
12499
+ throw "Email address should be valid and can not have more than 40 characters.";
12500
+
12464
12501
  if (await this.isRegistered())
12465
12502
  throw "Host already registered.";
12466
12503
 
@@ -12500,12 +12537,24 @@ class HostClient extends BaseEvernodeClient {
12500
12537
  console.log("No initiated transfers were found.");
12501
12538
  }
12502
12539
 
12503
- const memoData = `${countryCode};${cpuMicroSec};${ramMb};${diskMb};${totalInstanceCount};${cpuModel};${cpuCount};${cpuSpeed};${description};${emailAddress}`
12540
+ // <country_code(2)><cpu_microsec(4)><ram_mb(4)><disk_mb(4)><no_of_total_instances(4)><cpu_model(40)><cpu_count(2)><cpu_speed(2)><description(26)><email_address(40)>
12541
+ const memoBuf = Buffer.alloc(HOST_REG_MEMO_SIZE, 0);
12542
+ Buffer.from(countryCode.substr(0, 2), "utf-8").copy(memoBuf, HOST_COUNTRY_CODE_MEMO_OFFSET);
12543
+ memoBuf.writeUInt32BE(cpuMicroSec, HOST_CPU_MICROSEC_MEMO_OFFSET);
12544
+ memoBuf.writeUInt32BE(ramMb, HOST_RAM_MB_MEMO_OFFSET);
12545
+ memoBuf.writeUInt32BE(diskMb, HOST_DISK_MB_MEMO_OFFSET);
12546
+ memoBuf.writeUInt32BE(totalInstanceCount, HOST_TOT_INS_COUNT_MEMO_OFFSET);
12547
+ Buffer.from(cpuModel.substr(0, 40), "utf-8").copy(memoBuf, HOST_CPU_MODEL_NAME_MEMO_OFFSET);
12548
+ memoBuf.writeUInt16BE(cpuCount, HOST_CPU_COUNT_MEMO_OFFSET);
12549
+ memoBuf.writeUInt16BE(cpuSpeed, HOST_CPU_SPEED_MEMO_OFFSET);
12550
+ Buffer.from(description.substr(0, 26), "utf-8").copy(memoBuf, HOST_DESCRIPTION_MEMO_OFFSET);
12551
+ Buffer.from(emailAddress.substr(0, 40), "utf-8").copy(memoBuf, HOST_EMAIL_ADDRESS_MEMO_OFFSET);
12552
+
12504
12553
  const tx = await this.xrplAcc.makePayment(this.registryAddress,
12505
12554
  (transferredNFTokenId) ? EvernodeConstants.NOW_IN_EVRS : this.config.hostRegFee.toString(),
12506
12555
  EvernodeConstants.EVR,
12507
12556
  this.config.evrIssuerAddress,
12508
- [{ type: MemoTypes.HOST_REG, format: MemoFormats.TEXT, data: memoData }],
12557
+ [{ type: MemoTypes.HOST_REG, format: MemoFormats.HEX, data: memoBuf.toString('hex') }],
12509
12558
  options.transactionOptions);
12510
12559
 
12511
12560
  console.log('Waiting for the sell offer')
@@ -12550,11 +12599,18 @@ class HostClient extends BaseEvernodeClient {
12550
12599
  throw "Host not registered."
12551
12600
 
12552
12601
  const regNFT = await this.getRegistrationNft();
12602
+
12603
+ // To obtain registration NFT Page Keylet and index.
12604
+ const nftPageDataBuf = await EvernodeHelpers.getNFTPageAndLocation(regNFT.NFTokenID, this.xrplAcc, this.xrplApi);
12605
+
12553
12606
  await this.xrplAcc.makePayment(this.registryAddress,
12554
12607
  XrplConstants.MIN_XRP_AMOUNT,
12555
12608
  XrplConstants.XRP,
12556
12609
  null,
12557
- [{ type: MemoTypes.HOST_DEREG, format: MemoFormats.HEX, data: regNFT.NFTokenID }],
12610
+ [
12611
+ { type: MemoTypes.HOST_DEREG, format: MemoFormats.HEX, data: regNFT.NFTokenID },
12612
+ { type: MemoTypes.HOST_REGISTRY_REF, format: MemoFormats.HEX, data: nftPageDataBuf.toString('hex') }
12613
+ ],
12558
12614
  options.transactionOptions);
12559
12615
 
12560
12616
  console.log('Waiting for the buy offer')
@@ -12593,21 +12649,62 @@ class HostClient extends BaseEvernodeClient {
12593
12649
  }
12594
12650
 
12595
12651
  async updateRegInfo(activeInstanceCount = null, version = null, totalInstanceCount = null, tokenID = null, countryCode = null, cpuMicroSec = null, ramMb = null, diskMb = null, description = null, options = {}) {
12596
- const dataStr = `${tokenID ? tokenID : ''};${countryCode ? countryCode : ''};${cpuMicroSec ? cpuMicroSec : ''};${ramMb ? ramMb : ''};${diskMb ? diskMb : ''};${totalInstanceCount ? totalInstanceCount : ''};${activeInstanceCount !== undefined ? activeInstanceCount : ''};${description ? description : ''};${version ? version : ''}`;
12652
+ // <token_id(32)><country_code(2)><cpu_microsec(4)><ram_mb(4)><disk_mb(4)><total_instance_count(4)><active_instances(4)><description(26)><version(3)>
12653
+ const memoBuf = Buffer.alloc(HOST_UPDATE_MEMO_SIZE, 0);
12654
+ if (tokenID)
12655
+ Buffer.from(tokenID.substr(0, 32), "hex").copy(memoBuf, HOST_UPDATE_TOKEN_ID_MEMO_OFFSET);
12656
+ if (countryCode)
12657
+ Buffer.from(countryCode.substr(0, 2), "utf-8").copy(memoBuf, HOST_UPDATE_COUNTRY_CODE_MEMO_OFFSET);
12658
+ if (cpuMicroSec)
12659
+ memoBuf.writeUInt32BE(cpuMicroSec, HOST_UPDATE_CPU_MICROSEC_MEMO_OFFSET);
12660
+ if (ramMb)
12661
+ memoBuf.writeUInt32BE(ramMb, HOST_UPDATE_RAM_MB_MEMO_OFFSET);
12662
+ if (diskMb)
12663
+ memoBuf.writeUInt32BE(diskMb, HOST_UPDATE_DISK_MB_MEMO_OFFSET);
12664
+ if (totalInstanceCount)
12665
+ memoBuf.writeUInt32BE(totalInstanceCount, HOST_UPDATE_TOT_INS_COUNT_MEMO_OFFSET);
12666
+ if (activeInstanceCount)
12667
+ memoBuf.writeUInt32BE(activeInstanceCount, HOST_UPDATE_ACT_INS_COUNT_MEMO_OFFSET);
12668
+ if (description)
12669
+ Buffer.from(description.substr(0, 26), "utf-8").copy(memoBuf, HOST_UPDATE_DESCRIPTION_MEMO_OFFSET);
12670
+ if (version) {
12671
+ const components = version.split('.').map(v => parseInt(v));
12672
+ if (components.length != 3)
12673
+ throw 'Invalid version format.';
12674
+ memoBuf.writeUInt8(components[0], HOST_UPDATE_VERSION_MEMO_OFFSET);
12675
+ memoBuf.writeUInt8(components[1], HOST_UPDATE_VERSION_MEMO_OFFSET + 1);
12676
+ memoBuf.writeUInt8(components[2], HOST_UPDATE_VERSION_MEMO_OFFSET + 2);
12677
+ }
12678
+
12679
+ // To obtain registration NFT Page Keylet and index.
12680
+ if (!tokenID)
12681
+ tokenID = (await this.getRegistrationNft()).NFTokenID;
12682
+ const nftPageDataBuf = await EvernodeHelpers.getNFTPageAndLocation(tokenID, this.xrplAcc, this.xrplApi);
12683
+
12597
12684
  return await this.xrplAcc.makePayment(this.registryAddress,
12598
12685
  XrplConstants.MIN_XRP_AMOUNT,
12599
12686
  XrplConstants.XRP,
12600
12687
  null,
12601
- [{ type: MemoTypes.HOST_UPDATE_INFO, format: MemoFormats.TEXT, data: dataStr }],
12688
+ [
12689
+ { type: MemoTypes.HOST_UPDATE_INFO, format: MemoFormats.HEX, data: memoBuf.toString('hex') },
12690
+ { type: MemoTypes.HOST_REGISTRY_REF, format: MemoFormats.HEX, data: nftPageDataBuf.toString('hex') }
12691
+ ],
12602
12692
  options.transactionOptions);
12603
12693
  }
12604
12694
 
12605
12695
  async heartbeat(options = {}) {
12696
+ // To obtain registration NFT Page Keylet and index.
12697
+ const regNFT = await this.getRegistrationNft();
12698
+ const nftPageDataBuf = await EvernodeHelpers.getNFTPageAndLocation(regNFT.NFTokenID, this.xrplAcc, this.xrplApi);
12699
+
12606
12700
  return this.xrplAcc.makePayment(this.registryAddress,
12607
12701
  XrplConstants.MIN_XRP_AMOUNT,
12608
12702
  XrplConstants.XRP,
12609
12703
  null,
12610
- [{ type: MemoTypes.HEARTBEAT, format: "", data: "" }],
12704
+ [
12705
+ { type: MemoTypes.HEARTBEAT, format: "", data: "" },
12706
+ { type: MemoTypes.HOST_REGISTRY_REF, format: MemoFormats.HEX, data: nftPageDataBuf.toString('hex') }
12707
+ ],
12611
12708
  options.transactionOptions);
12612
12709
  }
12613
12710
 
@@ -12691,11 +12788,19 @@ class HostClient extends BaseEvernodeClient {
12691
12788
  }
12692
12789
 
12693
12790
  async requestRebate(options = {}) {
12791
+
12792
+ // To obtain registration NFT Page Keylet and index.
12793
+ const regNFT = await this.getRegistrationNft();
12794
+ const nftPageDataBuf = await EvernodeHelpers.getNFTPageAndLocation(regNFT.NFTokenID, this.xrplAcc, this.xrplApi);
12795
+
12694
12796
  return this.xrplAcc.makePayment(this.registryAddress,
12695
12797
  XrplConstants.MIN_XRP_AMOUNT,
12696
12798
  XrplConstants.XRP,
12697
12799
  null,
12698
- [{ type: MemoTypes.HOST_REBATE, format: "", data: "" }],
12800
+ [
12801
+ { type: MemoTypes.HOST_REBATE, format: "", data: "" },
12802
+ { type: MemoTypes.HOST_REGISTRY_REF, format: MemoFormats.HEX, data: nftPageDataBuf.toString('hex') }
12803
+ ],
12699
12804
  options.transactionOptions);
12700
12805
  }
12701
12806
 
@@ -12726,16 +12831,21 @@ class HostClient extends BaseEvernodeClient {
12726
12831
  }
12727
12832
  }
12728
12833
 
12729
- const regNFT = (await this.xrplAcc.getNfts()).find(n => n.URI.startsWith(EvernodeConstants.NFT_PREFIX_HEX) && n.Issuer === this.registryAddress);
12730
-
12731
12834
  let memoData = Buffer.allocUnsafe(20);
12732
12835
  codec.decodeAccountID(transfereeAddress).copy(memoData);
12733
12836
 
12837
+ // To obtain registration NFT Page Keylet and index.
12838
+ const regNFT = await this.getRegistrationNft();
12839
+ const nftPageDataBuf = await EvernodeHelpers.getNFTPageAndLocation(regNFT.NFTokenID, this.xrplAcc, this.xrplApi);
12840
+
12734
12841
  await this.xrplAcc.makePayment(this.registryAddress,
12735
12842
  XrplConstants.MIN_XRP_AMOUNT,
12736
12843
  XrplConstants.XRP,
12737
12844
  null,
12738
- [{ type: MemoTypes.HOST_TRANSFER, format: MemoFormats.HEX, data: memoData.toString('hex') }],
12845
+ [
12846
+ { type: MemoTypes.HOST_TRANSFER, format: MemoFormats.HEX, data: memoData.toString('hex') },
12847
+ { type: MemoTypes.HOST_REGISTRY_REF, format: MemoFormats.HEX, data: nftPageDataBuf.toString('hex') }
12848
+ ],
12739
12849
  options.transactionOptions);
12740
12850
 
12741
12851
  let offer = null;
@@ -12767,6 +12877,19 @@ class HostClient extends BaseEvernodeClient {
12767
12877
 
12768
12878
  await this.xrplAcc.sellNft(offer.index);
12769
12879
  }
12880
+
12881
+ async hasPendingTransfer() {
12882
+
12883
+ // Check the availability of TRANSFEREE state for this host address.
12884
+ const stateTransfereeAddrKey = StateHelpers.generateTransfereeAddrStateKey(this.xrplAcc.address);
12885
+ const stateTransfereeAddrIndex = StateHelpers.getHookStateIndex(this.registryAddress, stateTransfereeAddrKey);
12886
+ const res = await this.xrplApi.getLedgerEntry(stateTransfereeAddrIndex);
12887
+
12888
+ if (res && res?.HookStateData)
12889
+ return true;
12890
+
12891
+ return false;
12892
+ }
12770
12893
  }
12771
12894
 
12772
12895
  module.exports = {
@@ -13593,7 +13716,8 @@ const MemoTypes = {
13593
13716
  REFUND: 'evnRefund',
13594
13717
  REFUND_REF: 'evnRefundRef',
13595
13718
  DEAD_HOST_PRUNE: 'evnDeadHostPrune',
13596
- HOST_REBATE: 'evnHostRebate'
13719
+ HOST_REBATE: 'evnHostRebate',
13720
+ HOST_REGISTRY_REF: 'evnHostRegistryRef'
13597
13721
  }
13598
13722
 
13599
13723
  const MemoFormats = {
@@ -13634,6 +13758,8 @@ const HookStateKeys = {
13634
13758
  REWARD_CONFIGURATION: "4556520100000000000000000000000000000000000000000000000000000009",
13635
13759
  MAX_TOLERABLE_DOWNTIME: "455652010000000000000000000000000000000000000000000000000000000A",
13636
13760
  MOMENT_TRANSIT_INFO: "455652010000000000000000000000000000000000000000000000000000000B",
13761
+ MAX_TRX_EMISSION_FEE: "455652010000000000000000000000000000000000000000000000000000000C",
13762
+
13637
13763
 
13638
13764
  // Singleton
13639
13765
  HOST_COUNT: "4556523200000000000000000000000000000000000000000000000000000000",
@@ -13683,6 +13809,7 @@ module.exports = {
13683
13809
  /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
13684
13810
 
13685
13811
  const { EvernodeConstants } = __nccwpck_require__(9849);
13812
+ const NFT_PAGE_LEDGER_ENTRY_TYPE_HEX = '0050';
13686
13813
 
13687
13814
  class EvernodeHelpers {
13688
13815
  static async getLeaseOffers(xrplAcc) {
@@ -13691,6 +13818,36 @@ class EvernodeHelpers {
13691
13818
  const nftOffers = (await xrplAcc.getNftOffers())?.filter(offer => (offer.Flags == 1 && hostTokenIDs.includes(offer.NFTokenID))); // Filter only sell offers
13692
13819
  return nftOffers;
13693
13820
  }
13821
+
13822
+ static async getNFTPageAndLocation(nfTokenId, xrplAcc, xrplApi, buffer = true) {
13823
+
13824
+ const nftPageApprxKeylet = xrplAcc.generateKeylet('nftPage', { nfTokenId: nfTokenId });
13825
+ const nftPageMaxKeylet = xrplAcc.generateKeylet('nftPageMax');
13826
+ // Index is the last 32 bytes of the Keylet (Last 64 HEX characters).
13827
+ let page = await xrplApi.getLedgerEntry(nftPageMaxKeylet.substring(4, 68));
13828
+ while (page?.PreviousPageMin) {
13829
+ // Compare the low 96 bits. (Last 24 HEX characters).
13830
+ if (Number('0x' + page.index.substring(40, 64)) >= Number('0x' + nftPageApprxKeylet.substring(40, 64))) {
13831
+ // Check the existence of the NFToken
13832
+ let token = page.NFTokens.find(n => n.NFToken.NFTokenID == nfTokenId);
13833
+ if (!token) {
13834
+ page = await xrplApi.getLedgerEntry(page.PreviousPageMin);
13835
+ }
13836
+ else
13837
+ break;
13838
+ }
13839
+ }
13840
+
13841
+ const nftPageInfo = page.NFTokens.map((n, loc) => { return { NFTPage: NFT_PAGE_LEDGER_ENTRY_TYPE_HEX + page.index, NFTokenID: n.NFToken.NFTokenID, location: loc } }).find(n => n.NFTokenID == nfTokenId);
13842
+ if (buffer) {
13843
+ let locBuf = Buffer.allocUnsafe(2);
13844
+ locBuf.writeUInt16BE(nftPageInfo.location);
13845
+ // <NFT_PAGE_KEYLET(34 bytes)><LOCATION(2 bytes)>
13846
+ return Buffer.concat([Buffer.from(nftPageInfo.NFTPage, "hex"), locBuf]);
13847
+ }
13848
+
13849
+ return nftPageInfo;
13850
+ }
13694
13851
  }
13695
13852
 
13696
13853
  module.exports = {
@@ -14107,7 +14264,7 @@ const HOST_CPU_SPEED_OFFSET = 62;
14107
14264
  const HOST_CPU_MICROSEC_OFFSET = 64;
14108
14265
  const HOST_RAM_MB_OFFSET = 68;
14109
14266
  const HOST_DISK_MB_OFFSET = 72;
14110
- const EMAIL_ADDRESS_OFFSET = 112;
14267
+ const HOST_EMAIL_ADDRESS_OFFSET = 76;
14111
14268
 
14112
14269
  const PREV_HOST_ADDRESS_OFFSET = 0;
14113
14270
  const TRANSFER_LEDGER_IDX_OFFSET = 20;
@@ -14134,6 +14291,7 @@ const HOST_ADDR_KEY_ZERO_COUNT = 8;
14134
14291
  const TRANSFEREE_ADDR_KEY_ZERO_COUNT = 8;
14135
14292
  const HOOK_STATE_LEDGER_TYPE_PREFIX = 118; // Decimal value of ASCII 'v'
14136
14293
  const PENDING_TRANSFER = 1;
14294
+ const HOST_EMAIL_ADDRESS_LEN = 40;
14137
14295
 
14138
14296
  class StateHelpers {
14139
14297
  static StateTypes = {
@@ -14184,7 +14342,9 @@ class StateHelpers {
14184
14342
  cpuMicrosec: stateDataBuf.readUInt32BE(HOST_CPU_MICROSEC_OFFSET),
14185
14343
  ramMb: stateDataBuf.readUInt32BE(HOST_RAM_MB_OFFSET),
14186
14344
  diskMb: stateDataBuf.readUInt32BE(HOST_DISK_MB_OFFSET),
14187
- email: (stateDataBuf.length > HOST_DISK_MB_OFFSET ? stateDataBuf.slice(HOST_DISK_MB_OFFSET, EMAIL_ADDRESS_OFFSET).toString() : "")
14345
+ email: (stateDataBuf.length > HOST_EMAIL_ADDRESS_OFFSET ?
14346
+ stateDataBuf.slice(HOST_EMAIL_ADDRESS_OFFSET, HOST_EMAIL_ADDRESS_OFFSET + HOST_EMAIL_ADDRESS_LEN).toString().toString().replace(/\0/g, '') :
14347
+ "")
14188
14348
  }
14189
14349
  }
14190
14350
 
@@ -14329,6 +14489,13 @@ class StateHelpers {
14329
14489
  }
14330
14490
  }
14331
14491
  }
14492
+ else if (Buffer.from(HookStateKeys.MAX_TRX_EMISSION_FEE, 'hex').compare(stateKey) === 0) {
14493
+ return {
14494
+ type: this.StateTypes.CONFIGURATION,
14495
+ key: hexKey,
14496
+ value: Number(stateData.readBigUInt64BE())
14497
+ }
14498
+ }
14332
14499
  else
14333
14500
  throw { type: 'Validation Error', message: 'Invalid state key.' };
14334
14501
  }
@@ -14347,6 +14514,12 @@ class StateHelpers {
14347
14514
  type: this.StateTypes.TOKEN_ID
14348
14515
  };
14349
14516
  }
14517
+ else if (Buffer.from(HookStateKeys.PREFIX_TRANSFEREE_ADDR, 'hex').compare(stateKey, 0, 4) === 0) {
14518
+ return {
14519
+ key: hexKey,
14520
+ type: this.StateTypes.TRANSFEREE_ADDR
14521
+ };
14522
+ }
14350
14523
  else if (Buffer.from(HookStateKeys.HOST_COUNT, 'hex').compare(stateKey) === 0 ||
14351
14524
  Buffer.from(HookStateKeys.MOMENT_BASE_INFO, 'hex').compare(stateKey) === 0 ||
14352
14525
  Buffer.from(HookStateKeys.HOST_REG_FEE, 'hex').compare(stateKey) === 0 ||
@@ -14367,7 +14540,8 @@ class StateHelpers {
14367
14540
  Buffer.from(HookStateKeys.LEASE_ACQUIRE_WINDOW, 'hex').compare(stateKey) === 0 ||
14368
14541
  Buffer.from(HookStateKeys.REWARD_CONFIGURATION, 'hex').compare(stateKey) === 0 ||
14369
14542
  Buffer.from(HookStateKeys.MAX_TOLERABLE_DOWNTIME, 'hex').compare(stateKey) === 0 ||
14370
- Buffer.from(HookStateKeys.MOMENT_TRANSIT_INFO, 'hex').compare(stateKey) === 0) {
14543
+ Buffer.from(HookStateKeys.MOMENT_TRANSIT_INFO, 'hex').compare(stateKey) === 0 ||
14544
+ Buffer.from(HookStateKeys.MAX_TRX_EMISSION_FEE, 'hex').compare(stateKey) === 0) {
14371
14545
  return {
14372
14546
  key: hexKey,
14373
14547
  type: this.STATE_TYPES.CONFIGURATION
@@ -14721,18 +14895,27 @@ class XrplAccount {
14721
14895
  #subscribed = false;
14722
14896
  #txStreamHandler;
14723
14897
 
14724
- constructor(address, secret = null, options = {}) {
14898
+ constructor(address = null, secret = null, options = {}) {
14899
+ if (!address && !secret)
14900
+ throw "Both address and secret cannot be empty";
14901
+
14902
+ this.address = address;
14903
+ this.secret = secret;
14725
14904
  this.xrplApi = options.xrplApi || DefaultValues.xrplApi;
14726
14905
 
14727
14906
  if (!this.xrplApi)
14728
14907
  throw "XrplAccount: xrplApi not specified.";
14729
14908
 
14730
- this.address = address;
14731
-
14732
- this.secret = secret;
14733
- if (this.secret) {
14909
+ if (!this.address && this.secret) {
14734
14910
  this.wallet = xrpl.Wallet.fromSeed(this.secret);
14735
- this.address= this.wallet.classicAddress;
14911
+ this.address = this.wallet.classicAddress;
14912
+ } else if (this.secret) {
14913
+ const keypair = kp.deriveKeypair(this.secret);
14914
+ const derivedPubKeyAddress = kp.deriveAddress(keypair.publicKey);
14915
+ if (this.address == derivedPubKeyAddress)
14916
+ this.wallet = xrpl.Wallet.fromSeed(this.secret);
14917
+ else
14918
+ this.wallet = xrpl.Wallet.fromSeed(this.secret, { masterAddress: this.address });
14736
14919
  }
14737
14920
 
14738
14921
  this.#txStreamHandler = (eventName, tx, error) => {
@@ -14759,6 +14942,10 @@ class XrplAccount {
14759
14942
  return kp.deriveKeypair(this.secret);
14760
14943
  }
14761
14944
 
14945
+ async exists() {
14946
+ return await this.xrplApi.isAccountExists(this.address);
14947
+ }
14948
+
14762
14949
  async getInfo() {
14763
14950
  return await this.xrplApi.getAccountInfo(this.address);
14764
14951
  }
@@ -14832,6 +15019,10 @@ class XrplAccount {
14832
15019
  return await this.xrplApi.getAccountTrx(this.address, { ledger_index_min: minLedgerIndex, ledger_index_max: maxLedgerIndex, forward: isForward });
14833
15020
  }
14834
15021
 
15022
+ async hasValidKeyPair() {
15023
+ return await this.xrplApi.isValidKeyForAddress(this.wallet.publicKey, this.address);
15024
+ }
15025
+
14835
15026
  setAccountFields(fields, options = {}) {
14836
15027
  /**
14837
15028
  * Example for fields
@@ -15055,6 +15246,29 @@ class XrplAccount {
15055
15246
  return this.#submitAndVerifyTransaction(owner ? { ...tx, Owner: owner } : tx, options);
15056
15247
  }
15057
15248
 
15249
+ generateKeylet(type, data = {}) {
15250
+ switch (type) {
15251
+ case 'nftPage': {
15252
+ const accIdHex = (codec.decodeAccountID(this.address)).toString('hex').toUpperCase();
15253
+ const tokenPortion = data?.nfTokenId.substr(40, 64);
15254
+ return '0050' + accIdHex + tokenPortion;
15255
+ }
15256
+
15257
+ case 'nftPageMax': {
15258
+ const accIdHex = (codec.decodeAccountID(this.address)).toString('hex').toUpperCase();
15259
+ return '0050' + accIdHex + 'F'.repeat(24);
15260
+ }
15261
+
15262
+ case 'nftPageMin': {
15263
+ const accIdHex = (codec.decodeAccountID(this.address)).toString('hex').toUpperCase();
15264
+ return '0050' + accIdHex + '0'.repeat(24);
15265
+ }
15266
+
15267
+ default:
15268
+ return null;
15269
+ }
15270
+ }
15271
+
15058
15272
  async subscribe() {
15059
15273
  // Subscribe only once. Otherwise event handlers will be duplicated.
15060
15274
  if (this.#subscribed)
@@ -15390,6 +15604,17 @@ class XrplApi {
15390
15604
  return derivedPubKeyAddress === address || (regularKey && derivedPubKeyAddress === regularKey);
15391
15605
  }
15392
15606
 
15607
+ async isAccountExists(address) {
15608
+ try {
15609
+ await this.#client.request({ command: 'account_info', account: address });
15610
+ return true;
15611
+ }
15612
+ catch (e) {
15613
+ if (e.data.error === 'actNotFound') return false;
15614
+ else throw e;
15615
+ }
15616
+ }
15617
+
15393
15618
  async getAccountInfo(address) {
15394
15619
  const resp = (await this.#client.request({ command: 'account_info', account: address }));
15395
15620
  return resp?.result?.account_data;
@@ -15434,8 +15659,15 @@ class XrplApi {
15434
15659
  }
15435
15660
 
15436
15661
  async getLedgerEntry(index, options) {
15437
- const resp = (await this.#client.request({ command: 'ledger_entry', index: index, ledger_index: "validated", ...options }));
15438
- return resp?.result?.node;
15662
+ try {
15663
+ const resp = (await this.#client.request({ command: 'ledger_entry', index: index, ledger_index: "validated", ...options }));
15664
+ return resp?.result?.node;
15665
+
15666
+ } catch (e) {
15667
+ if (e?.data?.error === 'entryNotFound')
15668
+ return null;
15669
+ throw e;
15670
+ }
15439
15671
  }
15440
15672
 
15441
15673
  async submitAndVerify(tx, options) {
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  ],
7
7
  "homepage": "https://github.com/HotPocketDev/evernode-js-client",
8
8
  "license": "MIT",
9
- "version": "0.5.8",
9
+ "version": "0.5.10",
10
10
  "dependencies": {
11
11
  "elliptic": "6.5.4",
12
12
  "libsodium-wrappers": "0.7.10",