@movebridge/core 0.1.0 → 0.2.1

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.js CHANGED
@@ -63,7 +63,7 @@ var NETWORK_CONFIG = {
63
63
  testnet: {
64
64
  chainId: 250,
65
65
  rpcUrl: "https://testnet.movementnetwork.xyz/v1",
66
- indexerUrl: null,
66
+ indexerUrl: "https://hasura.testnet.movementnetwork.xyz/v1/graphql",
67
67
  explorerUrl: "https://explorer.movementnetwork.xyz/?network=bardock+testnet",
68
68
  faucetUrl: "https://faucet.testnet.movementnetwork.xyz/"
69
69
  }
@@ -280,6 +280,56 @@ var SUPPORTED_WALLETS = {
280
280
  "okxwallet": "okx"
281
281
  };
282
282
  var STORAGE_KEY = "movebridge:lastWallet";
283
+ function normalizeHash(data) {
284
+ if (data === null || data === void 0) {
285
+ throw new Error("Invalid hash: received null or undefined");
286
+ }
287
+ if (typeof data === "string") {
288
+ const trimmed = data.trim();
289
+ if (!trimmed) {
290
+ throw new Error("Invalid hash: received empty string");
291
+ }
292
+ return trimmed.startsWith("0x") ? trimmed : `0x${trimmed}`;
293
+ }
294
+ if (data instanceof Uint8Array) {
295
+ if (data.length === 0) {
296
+ throw new Error("Invalid hash: received empty Uint8Array");
297
+ }
298
+ return "0x" + Array.from(data).map((b) => b.toString(16).padStart(2, "0")).join("");
299
+ }
300
+ if (data instanceof ArrayBuffer) {
301
+ const arr = new Uint8Array(data);
302
+ if (arr.length === 0) {
303
+ throw new Error("Invalid hash: received empty ArrayBuffer");
304
+ }
305
+ return "0x" + Array.from(arr).map((b) => b.toString(16).padStart(2, "0")).join("");
306
+ }
307
+ if (data && typeof data === "object") {
308
+ if ("hash" in data) {
309
+ return normalizeHash(data.hash);
310
+ }
311
+ if ("txnHash" in data) {
312
+ return normalizeHash(data.txnHash);
313
+ }
314
+ if ("transactionHash" in data) {
315
+ return normalizeHash(data.transactionHash);
316
+ }
317
+ if ("output" in data) {
318
+ return normalizeHash(data.output);
319
+ }
320
+ if (typeof data.toString === "function") {
321
+ const str = data.toString();
322
+ if (str !== "[object Object]") {
323
+ return str.startsWith("0x") ? str : `0x${str}`;
324
+ }
325
+ }
326
+ }
327
+ const strValue = String(data);
328
+ if (strValue === "[object Object]" || !strValue) {
329
+ throw new Error("Invalid hash: could not extract hash from response");
330
+ }
331
+ return strValue.startsWith("0x") ? strValue : `0x${strValue}`;
332
+ }
283
333
  function toHexString(data) {
284
334
  if (typeof data === "string") return data;
285
335
  if (data instanceof Uint8Array) {
@@ -290,56 +340,159 @@ function toHexString(data) {
290
340
  }
291
341
  return String(data);
292
342
  }
343
+ function extractUserResponse(response) {
344
+ if (!response) {
345
+ throw new Error("Empty response from wallet");
346
+ }
347
+ if (typeof response === "string" || typeof response === "number") {
348
+ return response;
349
+ }
350
+ if (response instanceof Uint8Array) {
351
+ return response;
352
+ }
353
+ const resp = response;
354
+ if (resp.status === "rejected" || resp.status === "Rejected") {
355
+ throw new Error("User rejected the request");
356
+ }
357
+ if (resp.status === "error" || resp.error) {
358
+ const errorMsg = resp.error || resp.message || "Transaction failed";
359
+ throw new Error(String(errorMsg));
360
+ }
361
+ if (resp.args !== void 0) {
362
+ return resp.args;
363
+ }
364
+ if (resp.status === "approved" && resp.output !== void 0) {
365
+ return resp.output;
366
+ }
367
+ if (resp.result !== void 0) {
368
+ return resp.result;
369
+ }
370
+ if (resp.data !== void 0) {
371
+ return resp.data;
372
+ }
373
+ return response;
374
+ }
293
375
  function createStandardAdapter(wallet) {
294
- const connectFeature = wallet.features?.["aptos:connect"];
295
- const disconnectFeature = wallet.features?.["aptos:disconnect"];
296
- const signTxFeature = wallet.features?.["aptos:signAndSubmitTransaction"];
297
- const signOnlyFeature = wallet.features?.["aptos:signTransaction"];
298
- const accountChangeFeature = wallet.features?.["aptos:onAccountChange"];
299
- const networkChangeFeature = wallet.features?.["aptos:onNetworkChange"];
376
+ const features = wallet.features || {};
377
+ const connectFeature = features["aptos:connect"];
378
+ const disconnectFeature = features["aptos:disconnect"];
379
+ const signTxFeature = features["aptos:signAndSubmitTransaction"];
380
+ const signOnlyFeature = features["aptos:signTransaction"];
381
+ const accountChangeFeature = features["aptos:onAccountChange"];
382
+ const networkChangeFeature = features["aptos:onNetworkChange"];
300
383
  return {
301
384
  name: wallet.name,
302
385
  icon: wallet.icon || "",
303
386
  async connect() {
304
- if (!connectFeature) throw new Error("Wallet does not support connect");
305
- const response = await connectFeature.connect();
306
- const result = response?.args ?? response;
307
- if (response?.status === "rejected") {
308
- throw new Error("User rejected the connection");
387
+ if (!connectFeature) {
388
+ throw new Error("Wallet does not support connect");
309
389
  }
390
+ const response = await connectFeature.connect();
391
+ const result = extractUserResponse(response);
310
392
  return {
311
393
  address: toHexString(result.address),
312
394
  publicKey: toHexString(result.publicKey)
313
395
  };
314
396
  },
315
397
  async disconnect() {
316
- if (disconnectFeature) await disconnectFeature.disconnect();
398
+ if (disconnectFeature) {
399
+ await disconnectFeature.disconnect();
400
+ }
317
401
  },
318
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
319
402
  async signAndSubmitTransaction(payload) {
320
- if (!signTxFeature) throw new Error("Wallet does not support signAndSubmitTransaction");
321
- const response = await signTxFeature.signAndSubmitTransaction(payload);
322
- if (response?.status === "rejected") {
323
- throw new Error("User rejected the transaction");
403
+ if (!signTxFeature) {
404
+ throw new Error("Wallet does not support signAndSubmitTransaction");
405
+ }
406
+ const walletName = wallet.name.toLowerCase();
407
+ const isOKX = walletName.includes("okx");
408
+ let txPayload;
409
+ if (isOKX) {
410
+ txPayload = {
411
+ type: "entry_function_payload",
412
+ function: payload.payload.function,
413
+ type_arguments: payload.payload.typeArguments,
414
+ arguments: payload.payload.functionArguments
415
+ };
416
+ } else {
417
+ txPayload = payload;
418
+ }
419
+ let response;
420
+ try {
421
+ response = await signTxFeature.signAndSubmitTransaction(txPayload);
422
+ } catch (firstError) {
423
+ if (isOKX) {
424
+ try {
425
+ response = await signTxFeature.signAndSubmitTransaction(payload);
426
+ } catch {
427
+ throw firstError;
428
+ }
429
+ } else {
430
+ try {
431
+ const legacyPayload = {
432
+ type: "entry_function_payload",
433
+ function: payload.payload.function,
434
+ type_arguments: payload.payload.typeArguments,
435
+ arguments: payload.payload.functionArguments
436
+ };
437
+ response = await signTxFeature.signAndSubmitTransaction(legacyPayload);
438
+ } catch {
439
+ throw firstError;
440
+ }
441
+ }
442
+ }
443
+ const result = extractUserResponse(response);
444
+ try {
445
+ if (typeof result === "string") {
446
+ return { hash: normalizeHash(result) };
447
+ }
448
+ if (result && typeof result === "object") {
449
+ const hashObj = result;
450
+ if (hashObj.hash !== void 0) {
451
+ return { hash: normalizeHash(hashObj.hash) };
452
+ }
453
+ if (hashObj.txnHash !== void 0) {
454
+ return { hash: normalizeHash(hashObj.txnHash) };
455
+ }
456
+ if (hashObj.transactionHash !== void 0) {
457
+ return { hash: normalizeHash(hashObj.transactionHash) };
458
+ }
459
+ return { hash: normalizeHash(result) };
460
+ }
461
+ return { hash: normalizeHash(result) };
462
+ } catch (error) {
463
+ throw new Error(
464
+ `Failed to extract transaction hash from wallet response: ${error instanceof Error ? error.message : "Unknown error"}`
465
+ );
324
466
  }
325
- const result = response?.args ?? response;
326
- return { hash: result.hash || toHexString(result) };
327
467
  },
328
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
329
468
  async signTransaction(payload) {
330
- if (!signOnlyFeature) throw new Error("Wallet does not support signTransaction");
469
+ if (!signOnlyFeature) {
470
+ throw new Error("Wallet does not support signTransaction");
471
+ }
331
472
  const response = await signOnlyFeature.signTransaction(payload);
332
- if (response?.status === "rejected") {
333
- throw new Error("User rejected the transaction");
473
+ const result = extractUserResponse(response);
474
+ if (result instanceof Uint8Array) {
475
+ return result;
334
476
  }
335
- const result = response?.args ?? response;
336
- return result.authenticator || result.signature || new Uint8Array();
477
+ if (result && typeof result === "object") {
478
+ if ("authenticator" in result && result.authenticator) {
479
+ return result.authenticator;
480
+ }
481
+ if ("signature" in result && result.signature) {
482
+ return result.signature;
483
+ }
484
+ }
485
+ return new Uint8Array();
337
486
  },
338
487
  onAccountChange(cb) {
339
488
  if (accountChangeFeature) {
340
489
  accountChangeFeature.onAccountChange((account) => {
341
- if (account) {
342
- cb({ address: toHexString(account.address), publicKey: toHexString(account.publicKey) });
490
+ if (account && typeof account === "object") {
491
+ const acc = account;
492
+ cb({
493
+ address: toHexString(acc.address),
494
+ publicKey: toHexString(acc.publicKey)
495
+ });
343
496
  } else {
344
497
  cb(null);
345
498
  }
@@ -349,7 +502,11 @@ function createStandardAdapter(wallet) {
349
502
  onNetworkChange(cb) {
350
503
  if (networkChangeFeature) {
351
504
  networkChangeFeature.onNetworkChange((network) => {
352
- cb(network?.name || String(network));
505
+ if (network && typeof network === "object" && "name" in network) {
506
+ cb(network.name);
507
+ } else {
508
+ cb(String(network));
509
+ }
353
510
  });
354
511
  }
355
512
  }
@@ -359,17 +516,22 @@ var WalletManager = class extends import_eventemitter3.default {
359
516
  state = { connected: false, address: null, publicKey: null };
360
517
  currentWallet = null;
361
518
  adapter = null;
362
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
363
519
  standardWallets = /* @__PURE__ */ new Map();
364
520
  detectedWallets = [];
365
521
  unsubscribe = null;
522
+ /**
523
+ * Detects available wallets using AIP-62 standard
524
+ * @returns Array of detected wallet types
525
+ */
366
526
  detectWallets() {
367
527
  if (typeof window === "undefined") return [];
368
528
  const available = /* @__PURE__ */ new Set();
369
529
  this.standardWallets.clear();
370
530
  try {
371
531
  const { aptosWallets, on } = (0, import_wallet_standard.getAptosWallets)();
372
- if (this.unsubscribe) this.unsubscribe();
532
+ if (this.unsubscribe) {
533
+ this.unsubscribe();
534
+ }
373
535
  this.unsubscribe = on("register", () => this.detectWallets());
374
536
  for (const wallet of aptosWallets) {
375
537
  const normalizedName = wallet.name.toLowerCase();
@@ -385,21 +547,42 @@ var WalletManager = class extends import_eventemitter3.default {
385
547
  this.detectedWallets = Array.from(available);
386
548
  return this.detectedWallets;
387
549
  }
550
+ /**
551
+ * Gets detailed wallet information for UI display
552
+ * @returns Array of wallet info objects
553
+ */
388
554
  getWalletInfo() {
389
555
  return this.detectedWallets.map((type) => {
390
556
  const wallet = this.standardWallets.get(type);
391
- return { type, name: wallet?.name || type, icon: wallet?.icon || "" };
557
+ return {
558
+ type,
559
+ name: wallet?.name || type,
560
+ icon: wallet?.icon || ""
561
+ };
392
562
  });
393
563
  }
564
+ /**
565
+ * Connects to a wallet
566
+ * @param wallet - Wallet type to connect to
567
+ * @throws MovementError if wallet not found or connection fails
568
+ */
394
569
  async connect(wallet) {
395
570
  const available = this.detectWallets();
396
- if (!available.includes(wallet)) throw Errors.walletNotFound(wallet, available);
571
+ if (!available.includes(wallet)) {
572
+ throw Errors.walletNotFound(wallet, available);
573
+ }
397
574
  try {
398
575
  const standardWallet = this.standardWallets.get(wallet);
399
- if (!standardWallet) throw new Error(`Wallet ${wallet} not found`);
576
+ if (!standardWallet) {
577
+ throw new Error(`Wallet ${wallet} not found`);
578
+ }
400
579
  this.adapter = createStandardAdapter(standardWallet);
401
580
  const result = await this.adapter.connect();
402
- this.state = { connected: true, address: result.address, publicKey: result.publicKey };
581
+ this.state = {
582
+ connected: true,
583
+ address: result.address,
584
+ publicKey: result.publicKey
585
+ };
403
586
  this.currentWallet = wallet;
404
587
  this.saveLastWallet(wallet);
405
588
  this.setupEventListeners();
@@ -409,6 +592,9 @@ var WalletManager = class extends import_eventemitter3.default {
409
592
  throw Errors.walletConnectionFailed(wallet, error);
410
593
  }
411
594
  }
595
+ /**
596
+ * Disconnects from the current wallet
597
+ */
412
598
  async disconnect() {
413
599
  if (this.adapter) {
414
600
  try {
@@ -422,15 +608,28 @@ var WalletManager = class extends import_eventemitter3.default {
422
608
  this.clearLastWallet();
423
609
  this.emit("disconnect");
424
610
  }
611
+ /**
612
+ * Gets the current wallet state
613
+ */
425
614
  getState() {
426
615
  return { ...this.state };
427
616
  }
617
+ /**
618
+ * Gets the currently connected wallet type
619
+ */
428
620
  getWallet() {
429
621
  return this.currentWallet;
430
622
  }
623
+ /**
624
+ * Gets the wallet adapter for direct access
625
+ * @internal
626
+ */
431
627
  getAdapter() {
432
628
  return this.adapter;
433
629
  }
630
+ /**
631
+ * Attempts to auto-connect to the last used wallet
632
+ */
434
633
  async autoConnect() {
435
634
  const lastWallet = this.getLastWallet();
436
635
  if (!lastWallet) return;
@@ -443,6 +642,9 @@ var WalletManager = class extends import_eventemitter3.default {
443
642
  }
444
643
  }
445
644
  }
645
+ /**
646
+ * Cleans up resources
647
+ */
446
648
  destroy() {
447
649
  if (this.unsubscribe) {
448
650
  this.unsubscribe();
@@ -453,18 +655,26 @@ var WalletManager = class extends import_eventemitter3.default {
453
655
  if (!this.adapter) return;
454
656
  this.adapter.onAccountChange((account) => {
455
657
  if (account) {
456
- this.state = { connected: true, address: account.address, publicKey: account.publicKey };
658
+ this.state = {
659
+ connected: true,
660
+ address: account.address,
661
+ publicKey: account.publicKey
662
+ };
457
663
  this.emit("accountChanged", account.address);
458
664
  } else {
459
665
  this.state = { connected: false, address: null, publicKey: null };
460
666
  this.emit("disconnect");
461
667
  }
462
668
  });
463
- this.adapter.onNetworkChange((network) => this.emit("networkChanged", network));
669
+ this.adapter.onNetworkChange((network) => {
670
+ this.emit("networkChanged", network);
671
+ });
464
672
  }
465
673
  saveLastWallet(wallet) {
466
674
  try {
467
- if (typeof localStorage !== "undefined") localStorage.setItem(STORAGE_KEY, wallet);
675
+ if (typeof localStorage !== "undefined") {
676
+ localStorage.setItem(STORAGE_KEY, wallet);
677
+ }
468
678
  } catch {
469
679
  }
470
680
  }
@@ -482,7 +692,9 @@ var WalletManager = class extends import_eventemitter3.default {
482
692
  }
483
693
  clearLastWallet() {
484
694
  try {
485
- if (typeof localStorage !== "undefined") localStorage.removeItem(STORAGE_KEY);
695
+ if (typeof localStorage !== "undefined") {
696
+ localStorage.removeItem(STORAGE_KEY);
697
+ }
486
698
  } catch {
487
699
  }
488
700
  }
@@ -496,108 +708,70 @@ var TransactionBuilder = class {
496
708
  }
497
709
  /**
498
710
  * Builds a transfer transaction payload
711
+ * Uses 0x1::aptos_account::transfer which handles account creation
499
712
  * @param options - Transfer options
500
- * @returns Transaction payload
713
+ * @returns Transaction payload ready for signing
501
714
  */
502
715
  async transfer(options) {
503
- const coinType = options.coinType ?? DEFAULT_COIN_TYPE;
504
716
  return {
505
- type: "entry_function_payload",
506
- function: "0x1::coin::transfer",
507
- typeArguments: [coinType],
508
- arguments: [options.to, options.amount]
717
+ function: "0x1::aptos_account::transfer",
718
+ typeArguments: [],
719
+ functionArguments: [options.to, options.amount]
509
720
  };
510
721
  }
511
722
  /**
512
723
  * Builds a generic transaction payload
513
- * @param options - Build options
514
- * @returns Transaction payload
724
+ * @param options - Build options with function, typeArguments, and arguments
725
+ * @returns Transaction payload ready for signing
515
726
  */
516
727
  async build(options) {
517
728
  return {
518
- type: "entry_function_payload",
519
729
  function: options.function,
520
730
  typeArguments: options.typeArguments,
521
- arguments: options.arguments
731
+ functionArguments: options.arguments
522
732
  };
523
733
  }
524
- /**
525
- * Signs a transaction payload
526
- * @param payload - Transaction payload to sign
527
- * @returns Signed transaction
528
- * @throws MovementError with code WALLET_NOT_CONNECTED if no wallet is connected
529
- */
530
- async sign(payload) {
531
- const adapter = this.walletManager.getAdapter();
532
- const state = this.walletManager.getState();
533
- if (!adapter || !state.connected || !state.address) {
534
- throw Errors.walletNotConnected();
535
- }
536
- try {
537
- const signatureBytes = await adapter.signTransaction({
538
- type: payload.type,
539
- function: payload.function,
540
- type_arguments: payload.typeArguments,
541
- arguments: payload.arguments
542
- });
543
- const signature = Array.from(signatureBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
544
- return {
545
- payload,
546
- signature: `0x${signature}`,
547
- sender: state.address
548
- };
549
- } catch (error) {
550
- throw wrapError(error, "TRANSACTION_FAILED", "Failed to sign transaction");
551
- }
552
- }
553
- /**
554
- * Submits a signed transaction to the network
555
- * @param signed - Signed transaction
556
- * @returns Transaction hash
557
- */
558
- async submit(signed) {
559
- const adapter = this.walletManager.getAdapter();
560
- if (!adapter) {
561
- throw Errors.walletNotConnected();
562
- }
563
- try {
564
- const result = await adapter.signAndSubmitTransaction({
565
- type: signed.payload.type,
566
- function: signed.payload.function,
567
- type_arguments: signed.payload.typeArguments,
568
- arguments: signed.payload.arguments
569
- });
570
- return result.hash;
571
- } catch (error) {
572
- throw wrapError(error, "TRANSACTION_FAILED", "Failed to submit transaction");
573
- }
574
- }
575
734
  /**
576
735
  * Signs and submits a transaction in one step
736
+ * This is the recommended method for most use cases
577
737
  * @param payload - Transaction payload
578
738
  * @returns Transaction hash
739
+ * @throws MovementError with code WALLET_NOT_CONNECTED if no wallet connected
740
+ * @throws MovementError with code TRANSACTION_FAILED if submission fails
741
+ *
742
+ * Note: Transaction failures do NOT affect wallet connection state.
743
+ * The wallet remains connected even if a transaction fails.
579
744
  */
580
745
  async signAndSubmit(payload) {
581
746
  const adapter = this.walletManager.getAdapter();
582
- if (!adapter) {
747
+ const stateBefore = this.walletManager.getState();
748
+ if (!adapter || !stateBefore.connected) {
583
749
  throw Errors.walletNotConnected();
584
750
  }
585
751
  try {
586
752
  const result = await adapter.signAndSubmitTransaction({
587
- type: payload.type,
588
- function: payload.function,
589
- type_arguments: payload.typeArguments,
590
- arguments: payload.arguments
753
+ payload: {
754
+ function: payload.function,
755
+ typeArguments: payload.typeArguments,
756
+ functionArguments: payload.functionArguments
757
+ }
591
758
  });
592
759
  return result.hash;
593
760
  } catch (error) {
594
- throw wrapError(error, "TRANSACTION_FAILED", "Failed to sign and submit transaction");
761
+ const errorMessage = error instanceof Error ? error.message : String(error);
762
+ const isUserRejection = errorMessage.toLowerCase().includes("rejected") || errorMessage.toLowerCase().includes("cancelled") || errorMessage.toLowerCase().includes("canceled") || errorMessage.toLowerCase().includes("denied");
763
+ throw wrapError(
764
+ error,
765
+ "TRANSACTION_FAILED",
766
+ isUserRejection ? "Transaction was rejected by user" : "Failed to sign and submit transaction"
767
+ );
595
768
  }
596
769
  }
597
770
  /**
598
771
  * Simulates a transaction without submitting
772
+ * Useful for gas estimation and checking if transaction will succeed
599
773
  * @param payload - Transaction payload
600
- * @returns Simulation result with gas estimate
774
+ * @returns Simulation result with success status and gas estimate
601
775
  */
602
776
  async simulate(payload) {
603
777
  const state = this.walletManager.getState();
@@ -605,16 +779,17 @@ var TransactionBuilder = class {
605
779
  throw Errors.walletNotConnected();
606
780
  }
607
781
  try {
608
- const client = this.aptosClient;
609
- const result = await client.transaction.simulate.simple({
782
+ const transaction = await this.aptosClient.transaction.build.simple({
610
783
  sender: state.address,
611
784
  data: {
612
785
  function: payload.function,
613
786
  typeArguments: payload.typeArguments,
614
- functionArguments: payload.arguments
787
+ functionArguments: payload.functionArguments
615
788
  }
616
789
  });
617
- const simResult = result[0];
790
+ const [simResult] = await this.aptosClient.transaction.simulate.simple({
791
+ transaction
792
+ });
618
793
  return {
619
794
  success: simResult?.success ?? false,
620
795
  gasUsed: String(simResult?.gas_used ?? "0"),
@@ -624,6 +799,15 @@ var TransactionBuilder = class {
624
799
  throw wrapError(error, "TRANSACTION_FAILED", "Failed to simulate transaction");
625
800
  }
626
801
  }
802
+ /**
803
+ * Convenience method: Transfer and wait for confirmation
804
+ * @param options - Transfer options
805
+ * @returns Transaction hash
806
+ */
807
+ async transferAndSubmit(options) {
808
+ const payload = await this.transfer(options);
809
+ return this.signAndSubmit(payload);
810
+ }
627
811
  };
628
812
 
629
813
  // src/contract.ts
@@ -640,17 +824,27 @@ var ContractInterface = class {
640
824
  module;
641
825
  /**
642
826
  * Calls a view function (read-only)
827
+ * View functions don't require a wallet connection
828
+ *
643
829
  * @param functionName - Name of the view function
644
830
  * @param args - Function arguments
645
- * @param typeArgs - Type arguments (optional)
831
+ * @param typeArgs - Type arguments for generic functions
646
832
  * @returns Function result
647
833
  * @throws MovementError with code VIEW_FUNCTION_FAILED if call fails
834
+ *
835
+ * @example
836
+ * ```typescript
837
+ * // Get coin balance
838
+ * const balance = await contract.view('balance', [address], ['0x1::aptos_coin::AptosCoin']);
839
+ *
840
+ * // Check if account exists
841
+ * const exists = await contract.view('exists_at', [address]);
842
+ * ```
648
843
  */
649
- async view(functionName, args, typeArgs = []) {
844
+ async view(functionName, args = [], typeArgs = []) {
650
845
  const fullFunctionName = `${this.address}::${this.module}::${functionName}`;
651
846
  try {
652
- const client = this.aptosClient;
653
- const result = await client.view({
847
+ const result = await this.aptosClient.view({
654
848
  payload: {
655
849
  function: fullFunctionName,
656
850
  typeArguments: typeArgs,
@@ -664,14 +858,25 @@ var ContractInterface = class {
664
858
  }
665
859
  /**
666
860
  * Calls an entry function (write operation)
861
+ * Requires a connected wallet
862
+ *
667
863
  * @param functionName - Name of the entry function
668
864
  * @param args - Function arguments
669
- * @param typeArgs - Type arguments (optional)
865
+ * @param typeArgs - Type arguments for generic functions
670
866
  * @returns Transaction hash
671
- * @throws MovementError with code WALLET_NOT_CONNECTED if no wallet is connected
867
+ * @throws MovementError with code WALLET_NOT_CONNECTED if no wallet connected
672
868
  * @throws MovementError with code TRANSACTION_FAILED if transaction fails
869
+ *
870
+ * @example
871
+ * ```typescript
872
+ * // Transfer coins
873
+ * const txHash = await contract.call('transfer', [recipient, amount], ['0x1::aptos_coin::AptosCoin']);
874
+ *
875
+ * // Call a custom function
876
+ * const txHash = await contract.call('increment', []);
877
+ * ```
673
878
  */
674
- async call(functionName, args, typeArgs = []) {
879
+ async call(functionName, args = [], typeArgs = []) {
675
880
  const adapter = this.walletManager.getAdapter();
676
881
  const state = this.walletManager.getState();
677
882
  if (!adapter || !state.connected) {
@@ -680,10 +885,11 @@ var ContractInterface = class {
680
885
  const fullFunctionName = `${this.address}::${this.module}::${functionName}`;
681
886
  try {
682
887
  const result = await adapter.signAndSubmitTransaction({
683
- type: "entry_function_payload",
684
- function: fullFunctionName,
685
- type_arguments: typeArgs,
686
- arguments: args
888
+ payload: {
889
+ function: fullFunctionName,
890
+ typeArguments: typeArgs,
891
+ functionArguments: args
892
+ }
687
893
  });
688
894
  return result.hash;
689
895
  } catch (error) {
@@ -692,13 +898,17 @@ var ContractInterface = class {
692
898
  }
693
899
  /**
694
900
  * Checks if a resource exists at the contract address
695
- * @param resourceType - Full resource type (e.g., '0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>')
901
+ * @param resourceType - Full resource type
696
902
  * @returns true if resource exists
903
+ *
904
+ * @example
905
+ * ```typescript
906
+ * const hasCoin = await contract.hasResource('0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>');
907
+ * ```
697
908
  */
698
909
  async hasResource(resourceType) {
699
910
  try {
700
- const client = this.aptosClient;
701
- await client.getAccountResource({
911
+ await this.aptosClient.getAccountResource({
702
912
  accountAddress: this.address,
703
913
  resourceType
704
914
  });
@@ -711,11 +921,17 @@ var ContractInterface = class {
711
921
  * Gets a resource from the contract address
712
922
  * @param resourceType - Full resource type
713
923
  * @returns Resource data or null if not found
924
+ *
925
+ * @example
926
+ * ```typescript
927
+ * const coinStore = await contract.getResource<{ coin: { value: string } }>(
928
+ * '0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>'
929
+ * );
930
+ * ```
714
931
  */
715
932
  async getResource(resourceType) {
716
933
  try {
717
- const client = this.aptosClient;
718
- const resource = await client.getAccountResource({
934
+ const resource = await this.aptosClient.getAccountResource({
719
935
  accountAddress: this.address,
720
936
  resourceType
721
937
  });
@@ -744,31 +960,99 @@ var EventListener = class {
744
960
  subscriptionCounter = 0;
745
961
  pollIntervalMs;
746
962
  /**
747
- * Subscribes to contract events
963
+ * Subscribes to blockchain events
964
+ * Supports both new format (accountAddress + eventType) and legacy format (eventHandle)
965
+ *
748
966
  * @param subscription - Subscription configuration
749
- * @returns Subscription ID
750
- * @throws MovementError with code INVALID_EVENT_HANDLE if event handle is invalid
967
+ * @returns Subscription ID for unsubscribing
968
+ *
969
+ * @example
970
+ * ```typescript
971
+ * // New format (recommended)
972
+ * const subId = events.subscribe({
973
+ * accountAddress: '0x1',
974
+ * eventType: '0x1::coin::DepositEvent',
975
+ * callback: (event) => console.log(event),
976
+ * });
977
+ *
978
+ * // Legacy format (backward compatible)
979
+ * const subId = events.subscribe({
980
+ * eventHandle: '0x1::coin::DepositEvent',
981
+ * callback: (event) => console.log(event),
982
+ * });
983
+ * ```
751
984
  */
752
985
  subscribe(subscription) {
753
- if (!isValidEventHandle(subscription.eventHandle)) {
754
- throw Errors.invalidEventHandle(subscription.eventHandle);
755
- }
756
986
  const subscriptionId = `sub_${++this.subscriptionCounter}`;
987
+ let accountAddress;
988
+ let eventType;
989
+ if ("accountAddress" in subscription) {
990
+ accountAddress = subscription.accountAddress;
991
+ eventType = subscription.eventType;
992
+ } else {
993
+ const parsed = this.parseEventHandle(subscription.eventHandle);
994
+ accountAddress = parsed.accountAddress;
995
+ eventType = parsed.eventType;
996
+ }
997
+ if (!this.isValidEventType(eventType)) {
998
+ if (process.env.NODE_ENV === "development") {
999
+ console.warn(
1000
+ `[EventListener] Event type "${eventType}" may not be in the expected format (address::module::EventType). Subscription will proceed but may not receive events.`
1001
+ );
1002
+ }
1003
+ }
757
1004
  const internalSub = {
758
- eventHandle: subscription.eventHandle,
1005
+ accountAddress,
1006
+ eventType,
759
1007
  callback: subscription.callback,
760
- lastSequenceNumber: "0",
1008
+ lastSequenceNumber: BigInt(-1),
1009
+ // Start at -1 to catch all events
761
1010
  intervalId: null
762
1011
  };
1012
+ this.subscriptions.set(subscriptionId, internalSub);
763
1013
  internalSub.intervalId = setInterval(() => {
764
1014
  this.pollEvents(subscriptionId).catch(() => {
765
1015
  });
766
1016
  }, this.pollIntervalMs);
767
1017
  this.pollEvents(subscriptionId).catch(() => {
768
1018
  });
769
- this.subscriptions.set(subscriptionId, internalSub);
770
1019
  return subscriptionId;
771
1020
  }
1021
+ /**
1022
+ * Parses a legacy event handle into account address and event type
1023
+ * @internal
1024
+ */
1025
+ parseEventHandle(eventHandle) {
1026
+ const parts = eventHandle.split("::");
1027
+ if (parts.length >= 3 && parts[0]) {
1028
+ return {
1029
+ accountAddress: parts[0],
1030
+ eventType: eventHandle
1031
+ };
1032
+ }
1033
+ return {
1034
+ accountAddress: eventHandle,
1035
+ eventType: eventHandle
1036
+ };
1037
+ }
1038
+ /**
1039
+ * Validates event type format
1040
+ * Expected format: address::module::EventType
1041
+ * @internal
1042
+ */
1043
+ isValidEventType(eventType) {
1044
+ const parts = eventType.split("::");
1045
+ if (parts.length < 3) {
1046
+ return false;
1047
+ }
1048
+ const address = parts[0];
1049
+ if (!address || !address.startsWith("0x")) {
1050
+ return false;
1051
+ }
1052
+ const module2 = parts[1];
1053
+ const eventName = parts.slice(2).join("::");
1054
+ return Boolean(module2 && eventName);
1055
+ }
772
1056
  /**
773
1057
  * Unsubscribes from events
774
1058
  * @param subscriptionId - Subscription ID to remove
@@ -778,6 +1062,7 @@ var EventListener = class {
778
1062
  if (subscription) {
779
1063
  if (subscription.intervalId) {
780
1064
  clearInterval(subscription.intervalId);
1065
+ subscription.intervalId = null;
781
1066
  }
782
1067
  this.subscriptions.delete(subscriptionId);
783
1068
  }
@@ -806,8 +1091,8 @@ var EventListener = class {
806
1091
  return this.subscriptions.has(subscriptionId);
807
1092
  }
808
1093
  /**
809
- * Polls for new events
810
- * @param subscriptionId - Subscription ID
1094
+ * Polls for new events for a subscription
1095
+ * @internal
811
1096
  */
812
1097
  async pollEvents(subscriptionId) {
813
1098
  const subscription = this.subscriptions.get(subscriptionId);
@@ -815,33 +1100,199 @@ var EventListener = class {
815
1100
  return;
816
1101
  }
817
1102
  try {
818
- const parts = subscription.eventHandle.split("::");
819
- if (parts.length !== 3) {
820
- return;
1103
+ const events = await this.fetchEvents(
1104
+ subscription.accountAddress,
1105
+ subscription.eventType
1106
+ );
1107
+ const sortedEvents = this.sortEventsBySequence(events);
1108
+ for (const event of sortedEvents) {
1109
+ const sequenceNumber = this.parseSequenceNumber(event.sequence_number);
1110
+ if (sequenceNumber > subscription.lastSequenceNumber) {
1111
+ const contractEvent = {
1112
+ type: event.type || subscription.eventType,
1113
+ sequenceNumber: String(event.sequence_number),
1114
+ data: this.normalizeEventData(event.data)
1115
+ };
1116
+ this.safeInvokeCallback(subscription.callback, contractEvent);
1117
+ subscription.lastSequenceNumber = sequenceNumber;
1118
+ }
1119
+ }
1120
+ } catch (error) {
1121
+ if (process.env.NODE_ENV === "development") {
1122
+ console.warn(`[EventListener] Polling error for ${subscriptionId}:`, error);
821
1123
  }
822
- const [address, module2, eventType] = parts;
823
- const eventHandleStruct = `${address}::${module2}::${eventType}`;
824
- const client = this.aptosClient;
825
- const events = await client.getAccountEventsByEventType({
826
- accountAddress: address,
827
- eventType: eventHandleStruct,
1124
+ }
1125
+ }
1126
+ /**
1127
+ * Fetches events from the blockchain
1128
+ * Handles different API response formats and fallback methods
1129
+ * @internal
1130
+ */
1131
+ async fetchEvents(accountAddress, eventType) {
1132
+ try {
1133
+ const events = await this.aptosClient.getAccountEventsByEventType({
1134
+ accountAddress,
1135
+ eventType,
828
1136
  options: {
829
- limit: 25
1137
+ limit: 25,
1138
+ orderBy: [{ sequence_number: "desc" }]
830
1139
  }
831
1140
  });
832
- for (const event of events) {
833
- const sequenceNumber = event.sequence_number?.toString() ?? "0";
834
- if (BigInt(sequenceNumber) > BigInt(subscription.lastSequenceNumber)) {
835
- const contractEvent = {
836
- type: event.type,
837
- sequenceNumber,
838
- data: event.data
839
- };
840
- subscription.callback(contractEvent);
841
- subscription.lastSequenceNumber = sequenceNumber;
1141
+ return events.map((e) => ({
1142
+ type: e.type,
1143
+ sequence_number: e.sequence_number,
1144
+ data: e.data
1145
+ }));
1146
+ } catch (indexerError) {
1147
+ try {
1148
+ const events = await this.fetchEventsViaEventHandle(accountAddress, eventType);
1149
+ return events;
1150
+ } catch (handleError) {
1151
+ try {
1152
+ const events = await this.fetchEventsViaResources(accountAddress, eventType);
1153
+ return events;
1154
+ } catch (resourceError) {
1155
+ if (process.env.NODE_ENV === "development") {
1156
+ console.warn(
1157
+ `[EventListener] Failed to fetch events for ${eventType}:`,
1158
+ { indexerError, handleError, resourceError }
1159
+ );
1160
+ }
1161
+ return [];
842
1162
  }
843
1163
  }
1164
+ }
1165
+ }
1166
+ /**
1167
+ * Fetches events using the event handle API
1168
+ * @internal
1169
+ */
1170
+ async fetchEventsViaEventHandle(accountAddress, eventType) {
1171
+ const parts = eventType.split("::");
1172
+ if (parts.length < 3) {
1173
+ throw new Error("Invalid event type format");
1174
+ }
1175
+ const structName = parts.slice(2).join("::");
1176
+ const eventHandleMappings = {
1177
+ "DepositEvent": { resource: "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>", field: "deposit_events" },
1178
+ "WithdrawEvent": { resource: "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>", field: "withdraw_events" }
1179
+ };
1180
+ const mapping = eventHandleMappings[structName];
1181
+ if (!mapping) {
1182
+ throw new Error(`Unknown event type: ${structName}`);
1183
+ }
1184
+ const events = await this.aptosClient.getEventsByEventHandle({
1185
+ accountAddress,
1186
+ eventHandleStruct: mapping.resource,
1187
+ fieldName: mapping.field,
1188
+ options: { limit: 25 }
1189
+ });
1190
+ return events.map((e) => ({
1191
+ type: e.type || eventType,
1192
+ sequence_number: e.sequence_number,
1193
+ data: e.data
1194
+ }));
1195
+ }
1196
+ /**
1197
+ * Fetches events by checking account resources
1198
+ * This is a fallback method when indexer is not available
1199
+ * @internal
1200
+ */
1201
+ async fetchEventsViaResources(accountAddress, eventType) {
1202
+ const resources = await this.aptosClient.getAccountResources({
1203
+ accountAddress
1204
+ });
1205
+ const coinStore = resources.find(
1206
+ (r) => r.type === "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>"
1207
+ );
1208
+ if (!coinStore) {
1209
+ return [];
1210
+ }
1211
+ const data = coinStore.data;
1212
+ const isDeposit = eventType.includes("Deposit");
1213
+ const eventHandle = isDeposit ? data.deposit_events : data.withdraw_events;
1214
+ if (!eventHandle) {
1215
+ return [];
1216
+ }
1217
+ try {
1218
+ const creationNum = eventHandle.guid.id.creation_num;
1219
+ const addr = eventHandle.guid.id.addr;
1220
+ const response = await fetch(
1221
+ `${this.getFullnodeUrl()}/accounts/${accountAddress}/events/${addr}/${creationNum}?limit=25`
1222
+ );
1223
+ if (!response.ok) {
1224
+ throw new Error(`HTTP ${response.status}`);
1225
+ }
1226
+ const events = await response.json();
1227
+ return events.map((e) => ({
1228
+ type: e.type || eventType,
1229
+ sequence_number: e.sequence_number,
1230
+ data: e.data
1231
+ }));
1232
+ } catch {
1233
+ return [];
1234
+ }
1235
+ }
1236
+ /**
1237
+ * Gets the fullnode URL from the Aptos client config
1238
+ * @internal
1239
+ */
1240
+ getFullnodeUrl() {
1241
+ const config = this.aptosClient.config;
1242
+ return config?.fullnode || "https://testnet.movementnetwork.xyz/v1";
1243
+ }
1244
+ /**
1245
+ * Sorts events by sequence number in ascending order
1246
+ * @internal
1247
+ */
1248
+ sortEventsBySequence(events) {
1249
+ return [...events].sort((a, b) => {
1250
+ const seqA = this.parseSequenceNumber(a.sequence_number);
1251
+ const seqB = this.parseSequenceNumber(b.sequence_number);
1252
+ return seqA < seqB ? -1 : seqA > seqB ? 1 : 0;
1253
+ });
1254
+ }
1255
+ /**
1256
+ * Parses sequence number from various formats
1257
+ * @internal
1258
+ */
1259
+ parseSequenceNumber(value) {
1260
+ if (typeof value === "bigint") {
1261
+ return value;
1262
+ }
1263
+ if (typeof value === "number") {
1264
+ return BigInt(value);
1265
+ }
1266
+ try {
1267
+ return BigInt(value);
844
1268
  } catch {
1269
+ return BigInt(0);
1270
+ }
1271
+ }
1272
+ /**
1273
+ * Normalizes event data to a consistent format
1274
+ * @internal
1275
+ */
1276
+ normalizeEventData(data) {
1277
+ if (data === null || data === void 0) {
1278
+ return {};
1279
+ }
1280
+ if (typeof data === "object") {
1281
+ return data;
1282
+ }
1283
+ return { value: data };
1284
+ }
1285
+ /**
1286
+ * Safely invokes a callback without letting errors propagate
1287
+ * @internal
1288
+ */
1289
+ safeInvokeCallback(callback, event) {
1290
+ try {
1291
+ callback(event);
1292
+ } catch (error) {
1293
+ if (process.env.NODE_ENV === "development") {
1294
+ console.warn("[EventListener] Callback error:", error);
1295
+ }
845
1296
  }
846
1297
  }
847
1298
  };
@@ -953,10 +1404,9 @@ var Movement = class {
953
1404
  sender: userTx.sender,
954
1405
  sequenceNumber: userTx.sequence_number,
955
1406
  payload: {
956
- type: "entry_function_payload",
957
1407
  function: userTx.payload?.function ?? "",
958
1408
  typeArguments: userTx.payload?.type_arguments ?? [],
959
- arguments: userTx.payload?.arguments ?? []
1409
+ functionArguments: userTx.payload?.arguments ?? []
960
1410
  },
961
1411
  timestamp: userTx.timestamp
962
1412
  };