@movebridge/core 0.0.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 ADDED
@@ -0,0 +1,973 @@
1
+ // src/client.ts
2
+ import { Aptos, AptosConfig, Network } from "@aptos-labs/ts-sdk";
3
+
4
+ // src/config.ts
5
+ var NETWORK_CONFIG = {
6
+ mainnet: {
7
+ chainId: 126,
8
+ rpcUrl: "https://full.mainnet.movementinfra.xyz/v1",
9
+ indexerUrl: "https://indexer.mainnet.movementnetwork.xyz/v1/graphql",
10
+ explorerUrl: "https://explorer.movementnetwork.xyz/?network=mainnet"
11
+ },
12
+ testnet: {
13
+ chainId: 250,
14
+ rpcUrl: "https://testnet.movementnetwork.xyz/v1",
15
+ indexerUrl: null,
16
+ explorerUrl: "https://explorer.movementnetwork.xyz/?network=bardock+testnet",
17
+ faucetUrl: "https://faucet.testnet.movementnetwork.xyz/"
18
+ }
19
+ };
20
+ var DEFAULT_COIN_TYPE = "0x1::aptos_coin::AptosCoin";
21
+ function resolveConfig(config) {
22
+ const networkConfig = NETWORK_CONFIG[config.network];
23
+ return {
24
+ network: config.network,
25
+ chainId: networkConfig.chainId,
26
+ rpcUrl: config.rpcUrl ?? networkConfig.rpcUrl,
27
+ indexerUrl: config.indexerUrl ?? networkConfig.indexerUrl,
28
+ explorerUrl: networkConfig.explorerUrl,
29
+ autoConnect: config.autoConnect ?? false
30
+ };
31
+ }
32
+ function isValidAddress(address) {
33
+ const addressRegex = /^0x[a-fA-F0-9]{1,64}$/;
34
+ return addressRegex.test(address);
35
+ }
36
+ function isValidEventHandle(eventHandle) {
37
+ const eventHandleRegex = /^0x[a-fA-F0-9]{1,64}::[a-zA-Z_][a-zA-Z0-9_]*::[a-zA-Z_][a-zA-Z0-9_]*$/;
38
+ return eventHandleRegex.test(eventHandle);
39
+ }
40
+ function getExplorerTxUrl(network, txHash) {
41
+ const config = NETWORK_CONFIG[network];
42
+ return `${config.explorerUrl}&txn=${txHash}`;
43
+ }
44
+ function getExplorerAccountUrl(network, address) {
45
+ const config = NETWORK_CONFIG[network];
46
+ return `${config.explorerUrl}&account=${address}`;
47
+ }
48
+
49
+ // src/errors.ts
50
+ var MovementError = class _MovementError extends Error {
51
+ constructor(message, code, details) {
52
+ super(message);
53
+ this.code = code;
54
+ this.details = details;
55
+ if (Error.captureStackTrace) {
56
+ Error.captureStackTrace(this, _MovementError);
57
+ }
58
+ Object.setPrototypeOf(this, _MovementError.prototype);
59
+ }
60
+ name = "MovementError";
61
+ /**
62
+ * Serializes the error to a JSON-compatible object
63
+ */
64
+ toJSON() {
65
+ return {
66
+ name: this.name,
67
+ message: this.message,
68
+ code: this.code,
69
+ details: this.details
70
+ };
71
+ }
72
+ /**
73
+ * Creates a string representation of the error
74
+ */
75
+ toString() {
76
+ return `${this.name} [${this.code}]: ${this.message}`;
77
+ }
78
+ };
79
+ var Errors = {
80
+ /**
81
+ * Creates an invalid address error
82
+ */
83
+ invalidAddress(address, reason) {
84
+ return new MovementError(
85
+ `Invalid address: ${address}${reason ? ` - ${reason}` : ""}`,
86
+ "INVALID_ADDRESS",
87
+ { address, reason }
88
+ );
89
+ },
90
+ /**
91
+ * Creates a wallet not found error
92
+ */
93
+ walletNotFound(wallet, available) {
94
+ return new MovementError(
95
+ `Wallet "${wallet}" not found. Available wallets: ${available.join(", ") || "none"}`,
96
+ "WALLET_NOT_FOUND",
97
+ { wallet, available }
98
+ );
99
+ },
100
+ /**
101
+ * Creates a wallet connection failed error
102
+ */
103
+ walletConnectionFailed(wallet, originalError) {
104
+ const errorMessage = originalError instanceof Error ? originalError.message : String(originalError);
105
+ return new MovementError(
106
+ `Failed to connect to wallet "${wallet}": ${errorMessage}`,
107
+ "WALLET_CONNECTION_FAILED",
108
+ { wallet, originalError }
109
+ );
110
+ },
111
+ /**
112
+ * Creates a wallet not connected error
113
+ */
114
+ walletNotConnected() {
115
+ return new MovementError(
116
+ "No wallet connected. Please connect a wallet first.",
117
+ "WALLET_NOT_CONNECTED"
118
+ );
119
+ },
120
+ /**
121
+ * Creates a transaction failed error
122
+ */
123
+ transactionFailed(hash, vmStatus, gasUsed) {
124
+ return new MovementError(
125
+ `Transaction ${hash} failed with status: ${vmStatus}`,
126
+ "TRANSACTION_FAILED",
127
+ { hash, vmStatus, gasUsed }
128
+ );
129
+ },
130
+ /**
131
+ * Creates a transaction timeout error
132
+ */
133
+ transactionTimeout(hash) {
134
+ return new MovementError(
135
+ `Transaction ${hash} timed out waiting for confirmation`,
136
+ "TRANSACTION_TIMEOUT",
137
+ { hash }
138
+ );
139
+ },
140
+ /**
141
+ * Creates a view function failed error
142
+ */
143
+ viewFunctionFailed(functionName, args, originalError) {
144
+ const errorMessage = originalError instanceof Error ? originalError.message : String(originalError);
145
+ return new MovementError(
146
+ `View function "${functionName}" failed: ${errorMessage}`,
147
+ "VIEW_FUNCTION_FAILED",
148
+ { function: functionName, args, originalError }
149
+ );
150
+ },
151
+ /**
152
+ * Creates an invalid event handle error
153
+ */
154
+ invalidEventHandle(eventHandle) {
155
+ return new MovementError(
156
+ `Invalid event handle format: ${eventHandle}`,
157
+ "INVALID_EVENT_HANDLE",
158
+ { eventHandle, expectedFormat: "0xADDRESS::module::EventType" }
159
+ );
160
+ },
161
+ /**
162
+ * Creates a network error
163
+ */
164
+ networkError(url, httpStatus, responseBody) {
165
+ return new MovementError(
166
+ `Network request to ${url} failed${httpStatus ? ` with status ${httpStatus}` : ""}`,
167
+ "NETWORK_ERROR",
168
+ { url, httpStatus, responseBody }
169
+ );
170
+ },
171
+ /**
172
+ * Creates an ABI fetch failed error
173
+ */
174
+ abiFetchFailed(address, network, originalError) {
175
+ const errorMessage = originalError instanceof Error ? originalError.message : String(originalError);
176
+ return new MovementError(
177
+ `Failed to fetch ABI for ${address} on ${network}: ${errorMessage}`,
178
+ "ABI_FETCH_FAILED",
179
+ { address, network, originalError }
180
+ );
181
+ },
182
+ /**
183
+ * Creates a codegen failed error
184
+ */
185
+ codegenFailed(reason, abi) {
186
+ return new MovementError(
187
+ `Code generation failed: ${reason}`,
188
+ "CODEGEN_FAILED",
189
+ { reason, abi }
190
+ );
191
+ },
192
+ /**
193
+ * Creates an invalid argument error
194
+ */
195
+ invalidArgument(argument, reason) {
196
+ return new MovementError(
197
+ `Invalid argument "${argument}": ${reason}`,
198
+ "INVALID_ARGUMENT",
199
+ { argument, reason }
200
+ );
201
+ }
202
+ };
203
+ function isMovementError(error) {
204
+ return error instanceof MovementError;
205
+ }
206
+ function wrapError(error, code, context) {
207
+ if (isMovementError(error)) {
208
+ return error;
209
+ }
210
+ const message = error instanceof Error ? error.message : String(error);
211
+ return new MovementError(
212
+ context ? `${context}: ${message}` : message,
213
+ code,
214
+ { originalError: error }
215
+ );
216
+ }
217
+
218
+ // src/wallet.ts
219
+ import EventEmitter from "eventemitter3";
220
+ import { getAptosWallets } from "@aptos-labs/wallet-standard";
221
+ var SUPPORTED_WALLETS = {
222
+ "petra": "petra",
223
+ "petra wallet": "petra",
224
+ "pontem": "pontem",
225
+ "pontem wallet": "pontem",
226
+ "nightly": "nightly",
227
+ "nightly wallet": "nightly"
228
+ };
229
+ var STORAGE_KEY = "movebridge:lastWallet";
230
+ function toHexString(data) {
231
+ if (typeof data === "string") return data;
232
+ if (data instanceof Uint8Array) {
233
+ return "0x" + Array.from(data).map((b) => b.toString(16).padStart(2, "0")).join("");
234
+ }
235
+ if (data && typeof data.toString === "function") {
236
+ return data.toString();
237
+ }
238
+ return String(data);
239
+ }
240
+ 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"];
247
+ return {
248
+ name: wallet.name,
249
+ icon: wallet.icon || "",
250
+ 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");
256
+ }
257
+ return {
258
+ address: toHexString(result.address),
259
+ publicKey: toHexString(result.publicKey)
260
+ };
261
+ },
262
+ async disconnect() {
263
+ if (disconnectFeature) await disconnectFeature.disconnect();
264
+ },
265
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
266
+ async signAndSubmitTransaction(payload) {
267
+ if (!signTxFeature) throw new Error("Wallet does not support signAndSubmitTransaction");
268
+ const response = await signTxFeature.signAndSubmitTransaction(payload);
269
+ if (response?.status === "rejected") {
270
+ throw new Error("User rejected the transaction");
271
+ }
272
+ const result = response?.args ?? response;
273
+ return { hash: result.hash || toHexString(result) };
274
+ },
275
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
276
+ async signTransaction(payload) {
277
+ if (!signOnlyFeature) throw new Error("Wallet does not support signTransaction");
278
+ const response = await signOnlyFeature.signTransaction(payload);
279
+ if (response?.status === "rejected") {
280
+ throw new Error("User rejected the transaction");
281
+ }
282
+ const result = response?.args ?? response;
283
+ return result.authenticator || result.signature || new Uint8Array();
284
+ },
285
+ onAccountChange(cb) {
286
+ if (accountChangeFeature) {
287
+ accountChangeFeature.onAccountChange((account) => {
288
+ if (account) {
289
+ cb({ address: toHexString(account.address), publicKey: toHexString(account.publicKey) });
290
+ } else {
291
+ cb(null);
292
+ }
293
+ });
294
+ }
295
+ },
296
+ onNetworkChange(cb) {
297
+ if (networkChangeFeature) {
298
+ networkChangeFeature.onNetworkChange((network) => {
299
+ cb(network?.name || String(network));
300
+ });
301
+ }
302
+ }
303
+ };
304
+ }
305
+ var WalletManager = class extends EventEmitter {
306
+ state = { connected: false, address: null, publicKey: null };
307
+ currentWallet = null;
308
+ adapter = null;
309
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
310
+ standardWallets = /* @__PURE__ */ new Map();
311
+ detectedWallets = [];
312
+ unsubscribe = null;
313
+ detectWallets() {
314
+ if (typeof window === "undefined") return [];
315
+ const available = /* @__PURE__ */ new Set();
316
+ this.standardWallets.clear();
317
+ try {
318
+ const { aptosWallets, on } = getAptosWallets();
319
+ if (this.unsubscribe) this.unsubscribe();
320
+ this.unsubscribe = on("register", () => this.detectWallets());
321
+ for (const wallet of aptosWallets) {
322
+ const normalizedName = wallet.name.toLowerCase();
323
+ const walletType = SUPPORTED_WALLETS[normalizedName];
324
+ if (walletType) {
325
+ available.add(walletType);
326
+ this.standardWallets.set(walletType, wallet);
327
+ }
328
+ }
329
+ } catch (error) {
330
+ console.warn("Failed to detect wallets via AIP-62 standard:", error);
331
+ }
332
+ this.detectedWallets = Array.from(available);
333
+ return this.detectedWallets;
334
+ }
335
+ getWalletInfo() {
336
+ return this.detectedWallets.map((type) => {
337
+ const wallet = this.standardWallets.get(type);
338
+ return { type, name: wallet?.name || type, icon: wallet?.icon || "" };
339
+ });
340
+ }
341
+ async connect(wallet) {
342
+ const available = this.detectWallets();
343
+ if (!available.includes(wallet)) throw Errors.walletNotFound(wallet, available);
344
+ try {
345
+ const standardWallet = this.standardWallets.get(wallet);
346
+ if (!standardWallet) throw new Error(`Wallet ${wallet} not found`);
347
+ this.adapter = createStandardAdapter(standardWallet);
348
+ const result = await this.adapter.connect();
349
+ this.state = { connected: true, address: result.address, publicKey: result.publicKey };
350
+ this.currentWallet = wallet;
351
+ this.saveLastWallet(wallet);
352
+ this.setupEventListeners();
353
+ this.emit("connect", result.address);
354
+ } catch (error) {
355
+ this.adapter = null;
356
+ throw Errors.walletConnectionFailed(wallet, error);
357
+ }
358
+ }
359
+ async disconnect() {
360
+ if (this.adapter) {
361
+ try {
362
+ await this.adapter.disconnect();
363
+ } catch {
364
+ }
365
+ }
366
+ this.state = { connected: false, address: null, publicKey: null };
367
+ this.currentWallet = null;
368
+ this.adapter = null;
369
+ this.clearLastWallet();
370
+ this.emit("disconnect");
371
+ }
372
+ getState() {
373
+ return { ...this.state };
374
+ }
375
+ getWallet() {
376
+ return this.currentWallet;
377
+ }
378
+ getAdapter() {
379
+ return this.adapter;
380
+ }
381
+ async autoConnect() {
382
+ const lastWallet = this.getLastWallet();
383
+ if (!lastWallet) return;
384
+ const available = this.detectWallets();
385
+ if (available.includes(lastWallet)) {
386
+ try {
387
+ await this.connect(lastWallet);
388
+ } catch {
389
+ this.clearLastWallet();
390
+ }
391
+ }
392
+ }
393
+ destroy() {
394
+ if (this.unsubscribe) {
395
+ this.unsubscribe();
396
+ this.unsubscribe = null;
397
+ }
398
+ }
399
+ setupEventListeners() {
400
+ if (!this.adapter) return;
401
+ this.adapter.onAccountChange((account) => {
402
+ if (account) {
403
+ this.state = { connected: true, address: account.address, publicKey: account.publicKey };
404
+ this.emit("accountChanged", account.address);
405
+ } else {
406
+ this.state = { connected: false, address: null, publicKey: null };
407
+ this.emit("disconnect");
408
+ }
409
+ });
410
+ this.adapter.onNetworkChange((network) => this.emit("networkChanged", network));
411
+ }
412
+ saveLastWallet(wallet) {
413
+ try {
414
+ if (typeof localStorage !== "undefined") localStorage.setItem(STORAGE_KEY, wallet);
415
+ } catch {
416
+ }
417
+ }
418
+ getLastWallet() {
419
+ try {
420
+ if (typeof localStorage !== "undefined") {
421
+ const wallet = localStorage.getItem(STORAGE_KEY);
422
+ if (wallet && Object.values(SUPPORTED_WALLETS).includes(wallet)) {
423
+ return wallet;
424
+ }
425
+ }
426
+ } catch {
427
+ }
428
+ return null;
429
+ }
430
+ clearLastWallet() {
431
+ try {
432
+ if (typeof localStorage !== "undefined") localStorage.removeItem(STORAGE_KEY);
433
+ } catch {
434
+ }
435
+ }
436
+ };
437
+
438
+ // src/transaction.ts
439
+ var TransactionBuilder = class {
440
+ constructor(aptosClient, walletManager) {
441
+ this.aptosClient = aptosClient;
442
+ this.walletManager = walletManager;
443
+ }
444
+ /**
445
+ * Builds a transfer transaction payload
446
+ * @param options - Transfer options
447
+ * @returns Transaction payload
448
+ */
449
+ async transfer(options) {
450
+ const coinType = options.coinType ?? DEFAULT_COIN_TYPE;
451
+ return {
452
+ type: "entry_function_payload",
453
+ function: "0x1::coin::transfer",
454
+ typeArguments: [coinType],
455
+ arguments: [options.to, options.amount]
456
+ };
457
+ }
458
+ /**
459
+ * Builds a generic transaction payload
460
+ * @param options - Build options
461
+ * @returns Transaction payload
462
+ */
463
+ async build(options) {
464
+ return {
465
+ type: "entry_function_payload",
466
+ function: options.function,
467
+ typeArguments: options.typeArguments,
468
+ arguments: options.arguments
469
+ };
470
+ }
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
+ /**
523
+ * Signs and submits a transaction in one step
524
+ * @param payload - Transaction payload
525
+ * @returns Transaction hash
526
+ */
527
+ async signAndSubmit(payload) {
528
+ const adapter = this.walletManager.getAdapter();
529
+ if (!adapter) {
530
+ throw Errors.walletNotConnected();
531
+ }
532
+ try {
533
+ const result = await adapter.signAndSubmitTransaction({
534
+ type: payload.type,
535
+ function: payload.function,
536
+ type_arguments: payload.typeArguments,
537
+ arguments: payload.arguments
538
+ });
539
+ return result.hash;
540
+ } catch (error) {
541
+ throw wrapError(error, "TRANSACTION_FAILED", "Failed to sign and submit transaction");
542
+ }
543
+ }
544
+ /**
545
+ * Simulates a transaction without submitting
546
+ * @param payload - Transaction payload
547
+ * @returns Simulation result with gas estimate
548
+ */
549
+ async simulate(payload) {
550
+ const state = this.walletManager.getState();
551
+ if (!state.connected || !state.address) {
552
+ throw Errors.walletNotConnected();
553
+ }
554
+ try {
555
+ const client = this.aptosClient;
556
+ const result = await client.transaction.simulate.simple({
557
+ sender: state.address,
558
+ data: {
559
+ function: payload.function,
560
+ typeArguments: payload.typeArguments,
561
+ functionArguments: payload.arguments
562
+ }
563
+ });
564
+ const simResult = result[0];
565
+ return {
566
+ success: simResult?.success ?? false,
567
+ gasUsed: String(simResult?.gas_used ?? "0"),
568
+ vmStatus: String(simResult?.vm_status ?? "unknown")
569
+ };
570
+ } catch (error) {
571
+ throw wrapError(error, "TRANSACTION_FAILED", "Failed to simulate transaction");
572
+ }
573
+ }
574
+ };
575
+
576
+ // src/contract.ts
577
+ var ContractInterface = class {
578
+ constructor(aptosClient, walletManager, options) {
579
+ this.aptosClient = aptosClient;
580
+ this.walletManager = walletManager;
581
+ this.address = options.address;
582
+ this.module = options.module;
583
+ }
584
+ /** Contract address */
585
+ address;
586
+ /** Module name */
587
+ module;
588
+ /**
589
+ * Calls a view function (read-only)
590
+ * @param functionName - Name of the view function
591
+ * @param args - Function arguments
592
+ * @param typeArgs - Type arguments (optional)
593
+ * @returns Function result
594
+ * @throws MovementError with code VIEW_FUNCTION_FAILED if call fails
595
+ */
596
+ async view(functionName, args, typeArgs = []) {
597
+ const fullFunctionName = `${this.address}::${this.module}::${functionName}`;
598
+ try {
599
+ const client = this.aptosClient;
600
+ const result = await client.view({
601
+ payload: {
602
+ function: fullFunctionName,
603
+ typeArguments: typeArgs,
604
+ functionArguments: args
605
+ }
606
+ });
607
+ return result.length === 1 ? result[0] : result;
608
+ } catch (error) {
609
+ throw Errors.viewFunctionFailed(fullFunctionName, args, error);
610
+ }
611
+ }
612
+ /**
613
+ * Calls an entry function (write operation)
614
+ * @param functionName - Name of the entry function
615
+ * @param args - Function arguments
616
+ * @param typeArgs - Type arguments (optional)
617
+ * @returns Transaction hash
618
+ * @throws MovementError with code WALLET_NOT_CONNECTED if no wallet is connected
619
+ * @throws MovementError with code TRANSACTION_FAILED if transaction fails
620
+ */
621
+ async call(functionName, args, typeArgs = []) {
622
+ const adapter = this.walletManager.getAdapter();
623
+ const state = this.walletManager.getState();
624
+ if (!adapter || !state.connected) {
625
+ throw Errors.walletNotConnected();
626
+ }
627
+ const fullFunctionName = `${this.address}::${this.module}::${functionName}`;
628
+ try {
629
+ const result = await adapter.signAndSubmitTransaction({
630
+ type: "entry_function_payload",
631
+ function: fullFunctionName,
632
+ type_arguments: typeArgs,
633
+ arguments: args
634
+ });
635
+ return result.hash;
636
+ } catch (error) {
637
+ throw wrapError(error, "TRANSACTION_FAILED", `Failed to call ${fullFunctionName}`);
638
+ }
639
+ }
640
+ /**
641
+ * Checks if a resource exists at the contract address
642
+ * @param resourceType - Full resource type (e.g., '0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>')
643
+ * @returns true if resource exists
644
+ */
645
+ async hasResource(resourceType) {
646
+ try {
647
+ const client = this.aptosClient;
648
+ await client.getAccountResource({
649
+ accountAddress: this.address,
650
+ resourceType
651
+ });
652
+ return true;
653
+ } catch {
654
+ return false;
655
+ }
656
+ }
657
+ /**
658
+ * Gets a resource from the contract address
659
+ * @param resourceType - Full resource type
660
+ * @returns Resource data or null if not found
661
+ */
662
+ async getResource(resourceType) {
663
+ try {
664
+ const client = this.aptosClient;
665
+ const resource = await client.getAccountResource({
666
+ accountAddress: this.address,
667
+ resourceType
668
+ });
669
+ return resource;
670
+ } catch {
671
+ return null;
672
+ }
673
+ }
674
+ /**
675
+ * Gets the full function name for a function in this module
676
+ * @param functionName - Function name
677
+ * @returns Full function name (address::module::function)
678
+ */
679
+ getFullFunctionName(functionName) {
680
+ return `${this.address}::${this.module}::${functionName}`;
681
+ }
682
+ };
683
+
684
+ // src/events.ts
685
+ var EventListener = class {
686
+ constructor(aptosClient, options) {
687
+ this.aptosClient = aptosClient;
688
+ this.pollIntervalMs = options?.pollIntervalMs ?? 3e3;
689
+ }
690
+ subscriptions = /* @__PURE__ */ new Map();
691
+ subscriptionCounter = 0;
692
+ pollIntervalMs;
693
+ /**
694
+ * Subscribes to contract events
695
+ * @param subscription - Subscription configuration
696
+ * @returns Subscription ID
697
+ * @throws MovementError with code INVALID_EVENT_HANDLE if event handle is invalid
698
+ */
699
+ subscribe(subscription) {
700
+ if (!isValidEventHandle(subscription.eventHandle)) {
701
+ throw Errors.invalidEventHandle(subscription.eventHandle);
702
+ }
703
+ const subscriptionId = `sub_${++this.subscriptionCounter}`;
704
+ const internalSub = {
705
+ eventHandle: subscription.eventHandle,
706
+ callback: subscription.callback,
707
+ lastSequenceNumber: "0",
708
+ intervalId: null
709
+ };
710
+ internalSub.intervalId = setInterval(() => {
711
+ this.pollEvents(subscriptionId).catch(() => {
712
+ });
713
+ }, this.pollIntervalMs);
714
+ this.pollEvents(subscriptionId).catch(() => {
715
+ });
716
+ this.subscriptions.set(subscriptionId, internalSub);
717
+ return subscriptionId;
718
+ }
719
+ /**
720
+ * Unsubscribes from events
721
+ * @param subscriptionId - Subscription ID to remove
722
+ */
723
+ unsubscribe(subscriptionId) {
724
+ const subscription = this.subscriptions.get(subscriptionId);
725
+ if (subscription) {
726
+ if (subscription.intervalId) {
727
+ clearInterval(subscription.intervalId);
728
+ }
729
+ this.subscriptions.delete(subscriptionId);
730
+ }
731
+ }
732
+ /**
733
+ * Unsubscribes from all events
734
+ */
735
+ unsubscribeAll() {
736
+ for (const [id] of this.subscriptions) {
737
+ this.unsubscribe(id);
738
+ }
739
+ }
740
+ /**
741
+ * Gets active subscription count
742
+ * @returns Number of active subscriptions
743
+ */
744
+ getSubscriptionCount() {
745
+ return this.subscriptions.size;
746
+ }
747
+ /**
748
+ * Checks if a subscription exists
749
+ * @param subscriptionId - Subscription ID
750
+ * @returns true if subscription exists
751
+ */
752
+ hasSubscription(subscriptionId) {
753
+ return this.subscriptions.has(subscriptionId);
754
+ }
755
+ /**
756
+ * Polls for new events
757
+ * @param subscriptionId - Subscription ID
758
+ */
759
+ async pollEvents(subscriptionId) {
760
+ const subscription = this.subscriptions.get(subscriptionId);
761
+ if (!subscription) {
762
+ return;
763
+ }
764
+ 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,
775
+ options: {
776
+ limit: 25
777
+ }
778
+ });
779
+ for (const event of events) {
780
+ const sequenceNumber = event.sequence_number?.toString() ?? "0";
781
+ if (BigInt(sequenceNumber) > BigInt(subscription.lastSequenceNumber)) {
782
+ const contractEvent = {
783
+ type: event.type,
784
+ sequenceNumber,
785
+ data: event.data
786
+ };
787
+ subscription.callback(contractEvent);
788
+ subscription.lastSequenceNumber = sequenceNumber;
789
+ }
790
+ }
791
+ } catch {
792
+ }
793
+ }
794
+ };
795
+
796
+ // src/client.ts
797
+ var Movement = class {
798
+ /** Resolved configuration */
799
+ config;
800
+ /** Aptos SDK client instance */
801
+ aptosClient;
802
+ /** Wallet manager instance */
803
+ wallet;
804
+ /** Transaction builder instance */
805
+ transaction;
806
+ /** Event listener instance */
807
+ events;
808
+ /**
809
+ * Creates a new Movement client
810
+ * @param config - Configuration options
811
+ */
812
+ constructor(config) {
813
+ this.config = resolveConfig(config);
814
+ const aptosConfigOptions = {
815
+ network: Network.CUSTOM,
816
+ fullnode: this.config.rpcUrl
817
+ };
818
+ if (this.config.indexerUrl) {
819
+ aptosConfigOptions.indexer = this.config.indexerUrl;
820
+ }
821
+ const aptosConfig = new AptosConfig(aptosConfigOptions);
822
+ this.aptosClient = new Aptos(aptosConfig);
823
+ this.wallet = new WalletManager();
824
+ this.transaction = new TransactionBuilder(this.aptosClient, this.wallet);
825
+ this.events = new EventListener(this.aptosClient);
826
+ if (this.config.autoConnect) {
827
+ this.wallet.autoConnect().catch(() => {
828
+ });
829
+ }
830
+ }
831
+ /**
832
+ * Gets the underlying Aptos client
833
+ * @returns Aptos SDK client instance
834
+ */
835
+ getAptosClient() {
836
+ return this.aptosClient;
837
+ }
838
+ /**
839
+ * Gets the account balance for an address
840
+ * @param address - Account address
841
+ * @returns Balance as string in smallest unit (octas)
842
+ * @throws MovementError with code INVALID_ADDRESS if address is invalid
843
+ */
844
+ async getAccountBalance(address) {
845
+ if (!isValidAddress(address)) {
846
+ throw Errors.invalidAddress(address, "Invalid address format");
847
+ }
848
+ try {
849
+ const resources = await this.aptosClient.getAccountResources({
850
+ accountAddress: address
851
+ });
852
+ const coinResource = resources.find(
853
+ (r) => r.type === "0x1::coin::CoinStore<0x1::aptos_coin::AptosCoin>"
854
+ );
855
+ if (!coinResource) {
856
+ return "0";
857
+ }
858
+ const data = coinResource.data;
859
+ return data.coin.value;
860
+ } catch (error) {
861
+ throw wrapError(error, "NETWORK_ERROR", `Failed to get balance for ${address}`);
862
+ }
863
+ }
864
+ /**
865
+ * Gets all resources for an account
866
+ * @param address - Account address
867
+ * @returns Array of resources
868
+ * @throws MovementError with code INVALID_ADDRESS if address is invalid
869
+ */
870
+ async getAccountResources(address) {
871
+ if (!isValidAddress(address)) {
872
+ throw Errors.invalidAddress(address, "Invalid address format");
873
+ }
874
+ try {
875
+ const resources = await this.aptosClient.getAccountResources({
876
+ accountAddress: address
877
+ });
878
+ return resources.map((r) => ({
879
+ type: r.type,
880
+ data: r.data
881
+ }));
882
+ } catch (error) {
883
+ if (error instanceof Error && error.message.includes("Account not found")) {
884
+ return [];
885
+ }
886
+ throw wrapError(error, "NETWORK_ERROR", `Failed to get resources for ${address}`);
887
+ }
888
+ }
889
+ /**
890
+ * Gets a transaction by hash
891
+ * @param hash - Transaction hash
892
+ * @returns Transaction data
893
+ */
894
+ async getTransaction(hash) {
895
+ try {
896
+ const tx = await this.aptosClient.getTransactionByHash({ transactionHash: hash });
897
+ const userTx = tx;
898
+ return {
899
+ hash: userTx.hash,
900
+ sender: userTx.sender,
901
+ sequenceNumber: userTx.sequence_number,
902
+ payload: {
903
+ type: "entry_function_payload",
904
+ function: userTx.payload?.function ?? "",
905
+ typeArguments: userTx.payload?.type_arguments ?? [],
906
+ arguments: userTx.payload?.arguments ?? []
907
+ },
908
+ timestamp: userTx.timestamp
909
+ };
910
+ } catch (error) {
911
+ throw wrapError(error, "NETWORK_ERROR", `Failed to get transaction ${hash}`);
912
+ }
913
+ }
914
+ /**
915
+ * Waits for a transaction to be confirmed
916
+ * @param hash - Transaction hash
917
+ * @param options - Wait options
918
+ * @returns Transaction response
919
+ */
920
+ async waitForTransaction(hash, options) {
921
+ const timeoutMs = options?.timeoutMs ?? 3e4;
922
+ const checkIntervalMs = options?.checkIntervalMs ?? 1e3;
923
+ const startTime = Date.now();
924
+ while (Date.now() - startTime < timeoutMs) {
925
+ try {
926
+ const tx = await this.aptosClient.getTransactionByHash({ transactionHash: hash });
927
+ if ("success" in tx) {
928
+ const committedTx = tx;
929
+ return {
930
+ hash: committedTx.hash,
931
+ success: committedTx.success,
932
+ vmStatus: committedTx.vm_status,
933
+ gasUsed: committedTx.gas_used,
934
+ events: committedTx.events.map((e) => ({
935
+ type: e.type,
936
+ sequenceNumber: e.sequence_number,
937
+ data: e.data
938
+ }))
939
+ };
940
+ }
941
+ } catch {
942
+ }
943
+ await new Promise((resolve) => setTimeout(resolve, checkIntervalMs));
944
+ }
945
+ throw Errors.transactionTimeout(hash);
946
+ }
947
+ /**
948
+ * Creates a contract interface for interacting with a Move module
949
+ * @param options - Contract options
950
+ * @returns Contract interface
951
+ */
952
+ contract(options) {
953
+ return new ContractInterface(this.aptosClient, this.wallet, options);
954
+ }
955
+ };
956
+ export {
957
+ ContractInterface,
958
+ DEFAULT_COIN_TYPE,
959
+ Errors,
960
+ EventListener,
961
+ Movement,
962
+ MovementError,
963
+ NETWORK_CONFIG,
964
+ TransactionBuilder,
965
+ WalletManager,
966
+ getExplorerAccountUrl,
967
+ getExplorerTxUrl,
968
+ isMovementError,
969
+ isValidAddress,
970
+ isValidEventHandle,
971
+ resolveConfig,
972
+ wrapError
973
+ };