@movebridge/core 0.0.1 → 0.2.0

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/dist/index.mjs CHANGED
@@ -219,14 +219,32 @@ function wrapError(error, code, context) {
219
219
  import EventEmitter from "eventemitter3";
220
220
  import { getAptosWallets } from "@aptos-labs/wallet-standard";
221
221
  var SUPPORTED_WALLETS = {
222
- "petra": "petra",
223
- "petra wallet": "petra",
224
- "pontem": "pontem",
225
- "pontem wallet": "pontem",
222
+ "razor": "razor",
223
+ "razor wallet": "razor",
224
+ "razorwallet": "razor",
226
225
  "nightly": "nightly",
227
- "nightly wallet": "nightly"
226
+ "nightly wallet": "nightly",
227
+ "okx": "okx",
228
+ "okx wallet": "okx",
229
+ "okxwallet": "okx"
228
230
  };
229
231
  var STORAGE_KEY = "movebridge:lastWallet";
232
+ function normalizeHash(data) {
233
+ if (typeof data === "string") {
234
+ return data.startsWith("0x") ? data : `0x${data}`;
235
+ }
236
+ if (data instanceof Uint8Array) {
237
+ return "0x" + Array.from(data).map((b) => b.toString(16).padStart(2, "0")).join("");
238
+ }
239
+ if (data && typeof data === "object" && "hash" in data) {
240
+ return normalizeHash(data.hash);
241
+ }
242
+ if (data && typeof data.toString === "function") {
243
+ const str = data.toString();
244
+ return str.startsWith("0x") ? str : `0x${str}`;
245
+ }
246
+ return String(data);
247
+ }
230
248
  function toHexString(data) {
231
249
  if (typeof data === "string") return data;
232
250
  if (data instanceof Uint8Array) {
@@ -237,56 +255,88 @@ function toHexString(data) {
237
255
  }
238
256
  return String(data);
239
257
  }
258
+ function extractUserResponse(response) {
259
+ if (!response) {
260
+ throw new Error("Empty response from wallet");
261
+ }
262
+ const resp = response;
263
+ if (resp.status === "rejected") {
264
+ throw new Error("User rejected the request");
265
+ }
266
+ if (resp.args !== void 0) {
267
+ return resp.args;
268
+ }
269
+ return response;
270
+ }
240
271
  function createStandardAdapter(wallet) {
241
- const connectFeature = wallet.features?.["aptos:connect"];
242
- const disconnectFeature = wallet.features?.["aptos:disconnect"];
243
- const signTxFeature = wallet.features?.["aptos:signAndSubmitTransaction"];
244
- const signOnlyFeature = wallet.features?.["aptos:signTransaction"];
245
- const accountChangeFeature = wallet.features?.["aptos:onAccountChange"];
246
- const networkChangeFeature = wallet.features?.["aptos:onNetworkChange"];
272
+ const features = wallet.features || {};
273
+ const connectFeature = features["aptos:connect"];
274
+ const disconnectFeature = features["aptos:disconnect"];
275
+ const signTxFeature = features["aptos:signAndSubmitTransaction"];
276
+ const signOnlyFeature = features["aptos:signTransaction"];
277
+ const accountChangeFeature = features["aptos:onAccountChange"];
278
+ const networkChangeFeature = features["aptos:onNetworkChange"];
247
279
  return {
248
280
  name: wallet.name,
249
281
  icon: wallet.icon || "",
250
282
  async connect() {
251
- if (!connectFeature) throw new Error("Wallet does not support connect");
252
- const response = await connectFeature.connect();
253
- const result = response?.args ?? response;
254
- if (response?.status === "rejected") {
255
- throw new Error("User rejected the connection");
283
+ if (!connectFeature) {
284
+ throw new Error("Wallet does not support connect");
256
285
  }
286
+ const response = await connectFeature.connect();
287
+ const result = extractUserResponse(response);
257
288
  return {
258
289
  address: toHexString(result.address),
259
290
  publicKey: toHexString(result.publicKey)
260
291
  };
261
292
  },
262
293
  async disconnect() {
263
- if (disconnectFeature) await disconnectFeature.disconnect();
294
+ if (disconnectFeature) {
295
+ await disconnectFeature.disconnect();
296
+ }
264
297
  },
265
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
266
298
  async signAndSubmitTransaction(payload) {
267
- if (!signTxFeature) throw new Error("Wallet does not support signAndSubmitTransaction");
299
+ if (!signTxFeature) {
300
+ throw new Error("Wallet does not support signAndSubmitTransaction");
301
+ }
268
302
  const response = await signTxFeature.signAndSubmitTransaction(payload);
269
- if (response?.status === "rejected") {
270
- throw new Error("User rejected the transaction");
303
+ const result = extractUserResponse(response);
304
+ let hash;
305
+ if (result && typeof result === "object" && "hash" in result) {
306
+ hash = normalizeHash(result.hash);
307
+ } else {
308
+ hash = normalizeHash(result);
271
309
  }
272
- const result = response?.args ?? response;
273
- return { hash: result.hash || toHexString(result) };
310
+ return { hash };
274
311
  },
275
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
276
312
  async signTransaction(payload) {
277
- if (!signOnlyFeature) throw new Error("Wallet does not support signTransaction");
313
+ if (!signOnlyFeature) {
314
+ throw new Error("Wallet does not support signTransaction");
315
+ }
278
316
  const response = await signOnlyFeature.signTransaction(payload);
279
- if (response?.status === "rejected") {
280
- throw new Error("User rejected the transaction");
317
+ const result = extractUserResponse(response);
318
+ if (result instanceof Uint8Array) {
319
+ return result;
320
+ }
321
+ if (result && typeof result === "object") {
322
+ if ("authenticator" in result && result.authenticator) {
323
+ return result.authenticator;
324
+ }
325
+ if ("signature" in result && result.signature) {
326
+ return result.signature;
327
+ }
281
328
  }
282
- const result = response?.args ?? response;
283
- return result.authenticator || result.signature || new Uint8Array();
329
+ return new Uint8Array();
284
330
  },
285
331
  onAccountChange(cb) {
286
332
  if (accountChangeFeature) {
287
333
  accountChangeFeature.onAccountChange((account) => {
288
- if (account) {
289
- cb({ address: toHexString(account.address), publicKey: toHexString(account.publicKey) });
334
+ if (account && typeof account === "object") {
335
+ const acc = account;
336
+ cb({
337
+ address: toHexString(acc.address),
338
+ publicKey: toHexString(acc.publicKey)
339
+ });
290
340
  } else {
291
341
  cb(null);
292
342
  }
@@ -296,7 +346,11 @@ function createStandardAdapter(wallet) {
296
346
  onNetworkChange(cb) {
297
347
  if (networkChangeFeature) {
298
348
  networkChangeFeature.onNetworkChange((network) => {
299
- cb(network?.name || String(network));
349
+ if (network && typeof network === "object" && "name" in network) {
350
+ cb(network.name);
351
+ } else {
352
+ cb(String(network));
353
+ }
300
354
  });
301
355
  }
302
356
  }
@@ -306,17 +360,22 @@ var WalletManager = class extends EventEmitter {
306
360
  state = { connected: false, address: null, publicKey: null };
307
361
  currentWallet = null;
308
362
  adapter = null;
309
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
310
363
  standardWallets = /* @__PURE__ */ new Map();
311
364
  detectedWallets = [];
312
365
  unsubscribe = null;
366
+ /**
367
+ * Detects available wallets using AIP-62 standard
368
+ * @returns Array of detected wallet types
369
+ */
313
370
  detectWallets() {
314
371
  if (typeof window === "undefined") return [];
315
372
  const available = /* @__PURE__ */ new Set();
316
373
  this.standardWallets.clear();
317
374
  try {
318
375
  const { aptosWallets, on } = getAptosWallets();
319
- if (this.unsubscribe) this.unsubscribe();
376
+ if (this.unsubscribe) {
377
+ this.unsubscribe();
378
+ }
320
379
  this.unsubscribe = on("register", () => this.detectWallets());
321
380
  for (const wallet of aptosWallets) {
322
381
  const normalizedName = wallet.name.toLowerCase();
@@ -332,21 +391,42 @@ var WalletManager = class extends EventEmitter {
332
391
  this.detectedWallets = Array.from(available);
333
392
  return this.detectedWallets;
334
393
  }
394
+ /**
395
+ * Gets detailed wallet information for UI display
396
+ * @returns Array of wallet info objects
397
+ */
335
398
  getWalletInfo() {
336
399
  return this.detectedWallets.map((type) => {
337
400
  const wallet = this.standardWallets.get(type);
338
- return { type, name: wallet?.name || type, icon: wallet?.icon || "" };
401
+ return {
402
+ type,
403
+ name: wallet?.name || type,
404
+ icon: wallet?.icon || ""
405
+ };
339
406
  });
340
407
  }
408
+ /**
409
+ * Connects to a wallet
410
+ * @param wallet - Wallet type to connect to
411
+ * @throws MovementError if wallet not found or connection fails
412
+ */
341
413
  async connect(wallet) {
342
414
  const available = this.detectWallets();
343
- if (!available.includes(wallet)) throw Errors.walletNotFound(wallet, available);
415
+ if (!available.includes(wallet)) {
416
+ throw Errors.walletNotFound(wallet, available);
417
+ }
344
418
  try {
345
419
  const standardWallet = this.standardWallets.get(wallet);
346
- if (!standardWallet) throw new Error(`Wallet ${wallet} not found`);
420
+ if (!standardWallet) {
421
+ throw new Error(`Wallet ${wallet} not found`);
422
+ }
347
423
  this.adapter = createStandardAdapter(standardWallet);
348
424
  const result = await this.adapter.connect();
349
- this.state = { connected: true, address: result.address, publicKey: result.publicKey };
425
+ this.state = {
426
+ connected: true,
427
+ address: result.address,
428
+ publicKey: result.publicKey
429
+ };
350
430
  this.currentWallet = wallet;
351
431
  this.saveLastWallet(wallet);
352
432
  this.setupEventListeners();
@@ -356,6 +436,9 @@ var WalletManager = class extends EventEmitter {
356
436
  throw Errors.walletConnectionFailed(wallet, error);
357
437
  }
358
438
  }
439
+ /**
440
+ * Disconnects from the current wallet
441
+ */
359
442
  async disconnect() {
360
443
  if (this.adapter) {
361
444
  try {
@@ -369,15 +452,28 @@ var WalletManager = class extends EventEmitter {
369
452
  this.clearLastWallet();
370
453
  this.emit("disconnect");
371
454
  }
455
+ /**
456
+ * Gets the current wallet state
457
+ */
372
458
  getState() {
373
459
  return { ...this.state };
374
460
  }
461
+ /**
462
+ * Gets the currently connected wallet type
463
+ */
375
464
  getWallet() {
376
465
  return this.currentWallet;
377
466
  }
467
+ /**
468
+ * Gets the wallet adapter for direct access
469
+ * @internal
470
+ */
378
471
  getAdapter() {
379
472
  return this.adapter;
380
473
  }
474
+ /**
475
+ * Attempts to auto-connect to the last used wallet
476
+ */
381
477
  async autoConnect() {
382
478
  const lastWallet = this.getLastWallet();
383
479
  if (!lastWallet) return;
@@ -390,6 +486,9 @@ var WalletManager = class extends EventEmitter {
390
486
  }
391
487
  }
392
488
  }
489
+ /**
490
+ * Cleans up resources
491
+ */
393
492
  destroy() {
394
493
  if (this.unsubscribe) {
395
494
  this.unsubscribe();
@@ -400,18 +499,26 @@ var WalletManager = class extends EventEmitter {
400
499
  if (!this.adapter) return;
401
500
  this.adapter.onAccountChange((account) => {
402
501
  if (account) {
403
- this.state = { connected: true, address: account.address, publicKey: account.publicKey };
502
+ this.state = {
503
+ connected: true,
504
+ address: account.address,
505
+ publicKey: account.publicKey
506
+ };
404
507
  this.emit("accountChanged", account.address);
405
508
  } else {
406
509
  this.state = { connected: false, address: null, publicKey: null };
407
510
  this.emit("disconnect");
408
511
  }
409
512
  });
410
- this.adapter.onNetworkChange((network) => this.emit("networkChanged", network));
513
+ this.adapter.onNetworkChange((network) => {
514
+ this.emit("networkChanged", network);
515
+ });
411
516
  }
412
517
  saveLastWallet(wallet) {
413
518
  try {
414
- if (typeof localStorage !== "undefined") localStorage.setItem(STORAGE_KEY, wallet);
519
+ if (typeof localStorage !== "undefined") {
520
+ localStorage.setItem(STORAGE_KEY, wallet);
521
+ }
415
522
  } catch {
416
523
  }
417
524
  }
@@ -429,7 +536,9 @@ var WalletManager = class extends EventEmitter {
429
536
  }
430
537
  clearLastWallet() {
431
538
  try {
432
- if (typeof localStorage !== "undefined") localStorage.removeItem(STORAGE_KEY);
539
+ if (typeof localStorage !== "undefined") {
540
+ localStorage.removeItem(STORAGE_KEY);
541
+ }
433
542
  } catch {
434
543
  }
435
544
  }
@@ -443,98 +552,50 @@ var TransactionBuilder = class {
443
552
  }
444
553
  /**
445
554
  * Builds a transfer transaction payload
555
+ * Uses 0x1::aptos_account::transfer which handles account creation
446
556
  * @param options - Transfer options
447
- * @returns Transaction payload
557
+ * @returns Transaction payload ready for signing
448
558
  */
449
559
  async transfer(options) {
450
- const coinType = options.coinType ?? DEFAULT_COIN_TYPE;
451
560
  return {
452
- type: "entry_function_payload",
453
- function: "0x1::coin::transfer",
454
- typeArguments: [coinType],
455
- arguments: [options.to, options.amount]
561
+ function: "0x1::aptos_account::transfer",
562
+ typeArguments: [],
563
+ functionArguments: [options.to, options.amount]
456
564
  };
457
565
  }
458
566
  /**
459
567
  * Builds a generic transaction payload
460
- * @param options - Build options
461
- * @returns Transaction payload
568
+ * @param options - Build options with function, typeArguments, and arguments
569
+ * @returns Transaction payload ready for signing
462
570
  */
463
571
  async build(options) {
464
572
  return {
465
- type: "entry_function_payload",
466
573
  function: options.function,
467
574
  typeArguments: options.typeArguments,
468
- arguments: options.arguments
575
+ functionArguments: options.arguments
469
576
  };
470
577
  }
471
- /**
472
- * Signs a transaction payload
473
- * @param payload - Transaction payload to sign
474
- * @returns Signed transaction
475
- * @throws MovementError with code WALLET_NOT_CONNECTED if no wallet is connected
476
- */
477
- async sign(payload) {
478
- const adapter = this.walletManager.getAdapter();
479
- const state = this.walletManager.getState();
480
- if (!adapter || !state.connected || !state.address) {
481
- throw Errors.walletNotConnected();
482
- }
483
- try {
484
- const signatureBytes = await adapter.signTransaction({
485
- type: payload.type,
486
- function: payload.function,
487
- type_arguments: payload.typeArguments,
488
- arguments: payload.arguments
489
- });
490
- const signature = Array.from(signatureBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
491
- return {
492
- payload,
493
- signature: `0x${signature}`,
494
- sender: state.address
495
- };
496
- } catch (error) {
497
- throw wrapError(error, "TRANSACTION_FAILED", "Failed to sign transaction");
498
- }
499
- }
500
- /**
501
- * Submits a signed transaction to the network
502
- * @param signed - Signed transaction
503
- * @returns Transaction hash
504
- */
505
- async submit(signed) {
506
- const adapter = this.walletManager.getAdapter();
507
- if (!adapter) {
508
- throw Errors.walletNotConnected();
509
- }
510
- try {
511
- const result = await adapter.signAndSubmitTransaction({
512
- type: signed.payload.type,
513
- function: signed.payload.function,
514
- type_arguments: signed.payload.typeArguments,
515
- arguments: signed.payload.arguments
516
- });
517
- return result.hash;
518
- } catch (error) {
519
- throw wrapError(error, "TRANSACTION_FAILED", "Failed to submit transaction");
520
- }
521
- }
522
578
  /**
523
579
  * Signs and submits a transaction in one step
580
+ * This is the recommended method for most use cases
524
581
  * @param payload - Transaction payload
525
582
  * @returns Transaction hash
583
+ * @throws MovementError with code WALLET_NOT_CONNECTED if no wallet connected
584
+ * @throws MovementError with code TRANSACTION_FAILED if submission fails
526
585
  */
527
586
  async signAndSubmit(payload) {
528
587
  const adapter = this.walletManager.getAdapter();
529
- if (!adapter) {
588
+ const state = this.walletManager.getState();
589
+ if (!adapter || !state.connected) {
530
590
  throw Errors.walletNotConnected();
531
591
  }
532
592
  try {
533
593
  const result = await adapter.signAndSubmitTransaction({
534
- type: payload.type,
535
- function: payload.function,
536
- type_arguments: payload.typeArguments,
537
- arguments: payload.arguments
594
+ payload: {
595
+ function: payload.function,
596
+ typeArguments: payload.typeArguments,
597
+ functionArguments: payload.functionArguments
598
+ }
538
599
  });
539
600
  return result.hash;
540
601
  } catch (error) {
@@ -543,8 +604,9 @@ var TransactionBuilder = class {
543
604
  }
544
605
  /**
545
606
  * Simulates a transaction without submitting
607
+ * Useful for gas estimation and checking if transaction will succeed
546
608
  * @param payload - Transaction payload
547
- * @returns Simulation result with gas estimate
609
+ * @returns Simulation result with success status and gas estimate
548
610
  */
549
611
  async simulate(payload) {
550
612
  const state = this.walletManager.getState();
@@ -552,16 +614,17 @@ var TransactionBuilder = class {
552
614
  throw Errors.walletNotConnected();
553
615
  }
554
616
  try {
555
- const client = this.aptosClient;
556
- const result = await client.transaction.simulate.simple({
617
+ const transaction = await this.aptosClient.transaction.build.simple({
557
618
  sender: state.address,
558
619
  data: {
559
620
  function: payload.function,
560
621
  typeArguments: payload.typeArguments,
561
- functionArguments: payload.arguments
622
+ functionArguments: payload.functionArguments
562
623
  }
563
624
  });
564
- const simResult = result[0];
625
+ const [simResult] = await this.aptosClient.transaction.simulate.simple({
626
+ transaction
627
+ });
565
628
  return {
566
629
  success: simResult?.success ?? false,
567
630
  gasUsed: String(simResult?.gas_used ?? "0"),
@@ -571,6 +634,15 @@ var TransactionBuilder = class {
571
634
  throw wrapError(error, "TRANSACTION_FAILED", "Failed to simulate transaction");
572
635
  }
573
636
  }
637
+ /**
638
+ * Convenience method: Transfer and wait for confirmation
639
+ * @param options - Transfer options
640
+ * @returns Transaction hash
641
+ */
642
+ async transferAndSubmit(options) {
643
+ const payload = await this.transfer(options);
644
+ return this.signAndSubmit(payload);
645
+ }
574
646
  };
575
647
 
576
648
  // src/contract.ts
@@ -587,17 +659,27 @@ var ContractInterface = class {
587
659
  module;
588
660
  /**
589
661
  * Calls a view function (read-only)
662
+ * View functions don't require a wallet connection
663
+ *
590
664
  * @param functionName - Name of the view function
591
665
  * @param args - Function arguments
592
- * @param typeArgs - Type arguments (optional)
666
+ * @param typeArgs - Type arguments for generic functions
593
667
  * @returns Function result
594
668
  * @throws MovementError with code VIEW_FUNCTION_FAILED if call fails
669
+ *
670
+ * @example
671
+ * ```typescript
672
+ * // Get coin balance
673
+ * const balance = await contract.view('balance', [address], ['0x1::aptos_coin::AptosCoin']);
674
+ *
675
+ * // Check if account exists
676
+ * const exists = await contract.view('exists_at', [address]);
677
+ * ```
595
678
  */
596
- async view(functionName, args, typeArgs = []) {
679
+ async view(functionName, args = [], typeArgs = []) {
597
680
  const fullFunctionName = `${this.address}::${this.module}::${functionName}`;
598
681
  try {
599
- const client = this.aptosClient;
600
- const result = await client.view({
682
+ const result = await this.aptosClient.view({
601
683
  payload: {
602
684
  function: fullFunctionName,
603
685
  typeArguments: typeArgs,
@@ -611,14 +693,25 @@ var ContractInterface = class {
611
693
  }
612
694
  /**
613
695
  * Calls an entry function (write operation)
696
+ * Requires a connected wallet
697
+ *
614
698
  * @param functionName - Name of the entry function
615
699
  * @param args - Function arguments
616
- * @param typeArgs - Type arguments (optional)
700
+ * @param typeArgs - Type arguments for generic functions
617
701
  * @returns Transaction hash
618
- * @throws MovementError with code WALLET_NOT_CONNECTED if no wallet is connected
702
+ * @throws MovementError with code WALLET_NOT_CONNECTED if no wallet connected
619
703
  * @throws MovementError with code TRANSACTION_FAILED if transaction fails
704
+ *
705
+ * @example
706
+ * ```typescript
707
+ * // Transfer coins
708
+ * const txHash = await contract.call('transfer', [recipient, amount], ['0x1::aptos_coin::AptosCoin']);
709
+ *
710
+ * // Call a custom function
711
+ * const txHash = await contract.call('increment', []);
712
+ * ```
620
713
  */
621
- async call(functionName, args, typeArgs = []) {
714
+ async call(functionName, args = [], typeArgs = []) {
622
715
  const adapter = this.walletManager.getAdapter();
623
716
  const state = this.walletManager.getState();
624
717
  if (!adapter || !state.connected) {
@@ -627,10 +720,11 @@ var ContractInterface = class {
627
720
  const fullFunctionName = `${this.address}::${this.module}::${functionName}`;
628
721
  try {
629
722
  const result = await adapter.signAndSubmitTransaction({
630
- type: "entry_function_payload",
631
- function: fullFunctionName,
632
- type_arguments: typeArgs,
633
- arguments: args
723
+ payload: {
724
+ function: fullFunctionName,
725
+ typeArguments: typeArgs,
726
+ functionArguments: args
727
+ }
634
728
  });
635
729
  return result.hash;
636
730
  } catch (error) {
@@ -639,13 +733,17 @@ var ContractInterface = class {
639
733
  }
640
734
  /**
641
735
  * Checks if a resource exists at the contract address
642
- * @param resourceType - Full resource type (e.g., '0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>')
736
+ * @param resourceType - Full resource type
643
737
  * @returns true if resource exists
738
+ *
739
+ * @example
740
+ * ```typescript
741
+ * const hasCoin = await contract.hasResource('0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>');
742
+ * ```
644
743
  */
645
744
  async hasResource(resourceType) {
646
745
  try {
647
- const client = this.aptosClient;
648
- await client.getAccountResource({
746
+ await this.aptosClient.getAccountResource({
649
747
  accountAddress: this.address,
650
748
  resourceType
651
749
  });
@@ -658,11 +756,17 @@ var ContractInterface = class {
658
756
  * Gets a resource from the contract address
659
757
  * @param resourceType - Full resource type
660
758
  * @returns Resource data or null if not found
759
+ *
760
+ * @example
761
+ * ```typescript
762
+ * const coinStore = await contract.getResource<{ coin: { value: string } }>(
763
+ * '0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>'
764
+ * );
765
+ * ```
661
766
  */
662
767
  async getResource(resourceType) {
663
768
  try {
664
- const client = this.aptosClient;
665
- const resource = await client.getAccountResource({
769
+ const resource = await this.aptosClient.getAccountResource({
666
770
  accountAddress: this.address,
667
771
  resourceType
668
772
  });
@@ -691,29 +795,60 @@ var EventListener = class {
691
795
  subscriptionCounter = 0;
692
796
  pollIntervalMs;
693
797
  /**
694
- * Subscribes to contract events
798
+ * Subscribes to blockchain events
799
+ * Supports both new format (accountAddress + eventType) and legacy format (eventHandle)
800
+ *
695
801
  * @param subscription - Subscription configuration
696
- * @returns Subscription ID
697
- * @throws MovementError with code INVALID_EVENT_HANDLE if event handle is invalid
802
+ * @returns Subscription ID for unsubscribing
803
+ *
804
+ * @example
805
+ * ```typescript
806
+ * // New format (recommended)
807
+ * const subId = events.subscribe({
808
+ * accountAddress: '0x1',
809
+ * eventType: '0x1::coin::DepositEvent',
810
+ * callback: (event) => console.log(event),
811
+ * });
812
+ *
813
+ * // Legacy format (backward compatible)
814
+ * const subId = events.subscribe({
815
+ * eventHandle: '0x1::coin::DepositEvent',
816
+ * callback: (event) => console.log(event),
817
+ * });
818
+ * ```
698
819
  */
699
820
  subscribe(subscription) {
700
- if (!isValidEventHandle(subscription.eventHandle)) {
701
- throw Errors.invalidEventHandle(subscription.eventHandle);
702
- }
703
821
  const subscriptionId = `sub_${++this.subscriptionCounter}`;
822
+ let accountAddress;
823
+ let eventType;
824
+ if ("accountAddress" in subscription) {
825
+ accountAddress = subscription.accountAddress;
826
+ eventType = subscription.eventType;
827
+ } else {
828
+ const parts = subscription.eventHandle.split("::");
829
+ if (parts.length >= 3 && parts[0]) {
830
+ accountAddress = parts[0];
831
+ eventType = subscription.eventHandle;
832
+ } else {
833
+ accountAddress = subscription.eventHandle;
834
+ eventType = subscription.eventHandle;
835
+ }
836
+ }
704
837
  const internalSub = {
705
- eventHandle: subscription.eventHandle,
838
+ accountAddress,
839
+ eventType,
706
840
  callback: subscription.callback,
707
- lastSequenceNumber: "0",
841
+ lastSequenceNumber: BigInt(-1),
842
+ // Start at -1 to catch all events
708
843
  intervalId: null
709
844
  };
845
+ this.subscriptions.set(subscriptionId, internalSub);
710
846
  internalSub.intervalId = setInterval(() => {
711
847
  this.pollEvents(subscriptionId).catch(() => {
712
848
  });
713
849
  }, this.pollIntervalMs);
714
850
  this.pollEvents(subscriptionId).catch(() => {
715
851
  });
716
- this.subscriptions.set(subscriptionId, internalSub);
717
852
  return subscriptionId;
718
853
  }
719
854
  /**
@@ -725,6 +860,7 @@ var EventListener = class {
725
860
  if (subscription) {
726
861
  if (subscription.intervalId) {
727
862
  clearInterval(subscription.intervalId);
863
+ subscription.intervalId = null;
728
864
  }
729
865
  this.subscriptions.delete(subscriptionId);
730
866
  }
@@ -753,8 +889,8 @@ var EventListener = class {
753
889
  return this.subscriptions.has(subscriptionId);
754
890
  }
755
891
  /**
756
- * Polls for new events
757
- * @param subscriptionId - Subscription ID
892
+ * Polls for new events for a subscription
893
+ * @internal
758
894
  */
759
895
  async pollEvents(subscriptionId) {
760
896
  const subscription = this.subscriptions.get(subscriptionId);
@@ -762,29 +898,31 @@ var EventListener = class {
762
898
  return;
763
899
  }
764
900
  try {
765
- const parts = subscription.eventHandle.split("::");
766
- if (parts.length !== 3) {
767
- return;
768
- }
769
- const [address, module, eventType] = parts;
770
- const eventHandleStruct = `${address}::${module}::${eventType}`;
771
- const client = this.aptosClient;
772
- const events = await client.getAccountEventsByEventType({
773
- accountAddress: address,
774
- eventType: eventHandleStruct,
901
+ const events = await this.aptosClient.getAccountEventsByEventType({
902
+ accountAddress: subscription.accountAddress,
903
+ eventType: subscription.eventType,
775
904
  options: {
776
- limit: 25
905
+ limit: 25,
906
+ orderBy: [{ sequence_number: "desc" }]
777
907
  }
778
908
  });
779
- for (const event of events) {
780
- const sequenceNumber = event.sequence_number?.toString() ?? "0";
781
- if (BigInt(sequenceNumber) > BigInt(subscription.lastSequenceNumber)) {
909
+ const sortedEvents = [...events].sort((a, b) => {
910
+ const seqA = BigInt(a.sequence_number);
911
+ const seqB = BigInt(b.sequence_number);
912
+ return seqA < seqB ? -1 : seqA > seqB ? 1 : 0;
913
+ });
914
+ for (const event of sortedEvents) {
915
+ const sequenceNumber = BigInt(event.sequence_number);
916
+ if (sequenceNumber > subscription.lastSequenceNumber) {
782
917
  const contractEvent = {
783
918
  type: event.type,
784
- sequenceNumber,
919
+ sequenceNumber: String(event.sequence_number),
785
920
  data: event.data
786
921
  };
787
- subscription.callback(contractEvent);
922
+ try {
923
+ subscription.callback(contractEvent);
924
+ } catch {
925
+ }
788
926
  subscription.lastSequenceNumber = sequenceNumber;
789
927
  }
790
928
  }
@@ -900,10 +1038,9 @@ var Movement = class {
900
1038
  sender: userTx.sender,
901
1039
  sequenceNumber: userTx.sequence_number,
902
1040
  payload: {
903
- type: "entry_function_payload",
904
1041
  function: userTx.payload?.function ?? "",
905
1042
  typeArguments: userTx.payload?.type_arguments ?? [],
906
- arguments: userTx.payload?.arguments ?? []
1043
+ functionArguments: userTx.payload?.arguments ?? []
907
1044
  },
908
1045
  timestamp: userTx.timestamp
909
1046
  };