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