@movebridge/core 0.2.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.d.mts +56 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.js +360 -45
- package/dist/index.mjs +360 -45
- package/package.json +52 -52
- package/LICENSE +0 -0
package/dist/index.d.mts
CHANGED
|
@@ -288,6 +288,9 @@ declare class TransactionBuilder {
|
|
|
288
288
|
* @returns Transaction hash
|
|
289
289
|
* @throws MovementError with code WALLET_NOT_CONNECTED if no wallet connected
|
|
290
290
|
* @throws MovementError with code TRANSACTION_FAILED if submission fails
|
|
291
|
+
*
|
|
292
|
+
* Note: Transaction failures do NOT affect wallet connection state.
|
|
293
|
+
* The wallet remains connected even if a transaction fails.
|
|
291
294
|
*/
|
|
292
295
|
signAndSubmit(payload: TransactionPayload): Promise<string>;
|
|
293
296
|
/**
|
|
@@ -489,6 +492,17 @@ declare class EventListener {
|
|
|
489
492
|
* ```
|
|
490
493
|
*/
|
|
491
494
|
subscribe(subscription: EventSubscriptionConfig | LegacyEventSubscription): string;
|
|
495
|
+
/**
|
|
496
|
+
* Parses a legacy event handle into account address and event type
|
|
497
|
+
* @internal
|
|
498
|
+
*/
|
|
499
|
+
private parseEventHandle;
|
|
500
|
+
/**
|
|
501
|
+
* Validates event type format
|
|
502
|
+
* Expected format: address::module::EventType
|
|
503
|
+
* @internal
|
|
504
|
+
*/
|
|
505
|
+
private isValidEventType;
|
|
492
506
|
/**
|
|
493
507
|
* Unsubscribes from events
|
|
494
508
|
* @param subscriptionId - Subscription ID to remove
|
|
@@ -514,6 +528,48 @@ declare class EventListener {
|
|
|
514
528
|
* @internal
|
|
515
529
|
*/
|
|
516
530
|
private pollEvents;
|
|
531
|
+
/**
|
|
532
|
+
* Fetches events from the blockchain
|
|
533
|
+
* Handles different API response formats and fallback methods
|
|
534
|
+
* @internal
|
|
535
|
+
*/
|
|
536
|
+
private fetchEvents;
|
|
537
|
+
/**
|
|
538
|
+
* Fetches events using the event handle API
|
|
539
|
+
* @internal
|
|
540
|
+
*/
|
|
541
|
+
private fetchEventsViaEventHandle;
|
|
542
|
+
/**
|
|
543
|
+
* Fetches events by checking account resources
|
|
544
|
+
* This is a fallback method when indexer is not available
|
|
545
|
+
* @internal
|
|
546
|
+
*/
|
|
547
|
+
private fetchEventsViaResources;
|
|
548
|
+
/**
|
|
549
|
+
* Gets the fullnode URL from the Aptos client config
|
|
550
|
+
* @internal
|
|
551
|
+
*/
|
|
552
|
+
private getFullnodeUrl;
|
|
553
|
+
/**
|
|
554
|
+
* Sorts events by sequence number in ascending order
|
|
555
|
+
* @internal
|
|
556
|
+
*/
|
|
557
|
+
private sortEventsBySequence;
|
|
558
|
+
/**
|
|
559
|
+
* Parses sequence number from various formats
|
|
560
|
+
* @internal
|
|
561
|
+
*/
|
|
562
|
+
private parseSequenceNumber;
|
|
563
|
+
/**
|
|
564
|
+
* Normalizes event data to a consistent format
|
|
565
|
+
* @internal
|
|
566
|
+
*/
|
|
567
|
+
private normalizeEventData;
|
|
568
|
+
/**
|
|
569
|
+
* Safely invokes a callback without letting errors propagate
|
|
570
|
+
* @internal
|
|
571
|
+
*/
|
|
572
|
+
private safeInvokeCallback;
|
|
517
573
|
}
|
|
518
574
|
|
|
519
575
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -288,6 +288,9 @@ declare class TransactionBuilder {
|
|
|
288
288
|
* @returns Transaction hash
|
|
289
289
|
* @throws MovementError with code WALLET_NOT_CONNECTED if no wallet connected
|
|
290
290
|
* @throws MovementError with code TRANSACTION_FAILED if submission fails
|
|
291
|
+
*
|
|
292
|
+
* Note: Transaction failures do NOT affect wallet connection state.
|
|
293
|
+
* The wallet remains connected even if a transaction fails.
|
|
291
294
|
*/
|
|
292
295
|
signAndSubmit(payload: TransactionPayload): Promise<string>;
|
|
293
296
|
/**
|
|
@@ -489,6 +492,17 @@ declare class EventListener {
|
|
|
489
492
|
* ```
|
|
490
493
|
*/
|
|
491
494
|
subscribe(subscription: EventSubscriptionConfig | LegacyEventSubscription): string;
|
|
495
|
+
/**
|
|
496
|
+
* Parses a legacy event handle into account address and event type
|
|
497
|
+
* @internal
|
|
498
|
+
*/
|
|
499
|
+
private parseEventHandle;
|
|
500
|
+
/**
|
|
501
|
+
* Validates event type format
|
|
502
|
+
* Expected format: address::module::EventType
|
|
503
|
+
* @internal
|
|
504
|
+
*/
|
|
505
|
+
private isValidEventType;
|
|
492
506
|
/**
|
|
493
507
|
* Unsubscribes from events
|
|
494
508
|
* @param subscriptionId - Subscription ID to remove
|
|
@@ -514,6 +528,48 @@ declare class EventListener {
|
|
|
514
528
|
* @internal
|
|
515
529
|
*/
|
|
516
530
|
private pollEvents;
|
|
531
|
+
/**
|
|
532
|
+
* Fetches events from the blockchain
|
|
533
|
+
* Handles different API response formats and fallback methods
|
|
534
|
+
* @internal
|
|
535
|
+
*/
|
|
536
|
+
private fetchEvents;
|
|
537
|
+
/**
|
|
538
|
+
* Fetches events using the event handle API
|
|
539
|
+
* @internal
|
|
540
|
+
*/
|
|
541
|
+
private fetchEventsViaEventHandle;
|
|
542
|
+
/**
|
|
543
|
+
* Fetches events by checking account resources
|
|
544
|
+
* This is a fallback method when indexer is not available
|
|
545
|
+
* @internal
|
|
546
|
+
*/
|
|
547
|
+
private fetchEventsViaResources;
|
|
548
|
+
/**
|
|
549
|
+
* Gets the fullnode URL from the Aptos client config
|
|
550
|
+
* @internal
|
|
551
|
+
*/
|
|
552
|
+
private getFullnodeUrl;
|
|
553
|
+
/**
|
|
554
|
+
* Sorts events by sequence number in ascending order
|
|
555
|
+
* @internal
|
|
556
|
+
*/
|
|
557
|
+
private sortEventsBySequence;
|
|
558
|
+
/**
|
|
559
|
+
* Parses sequence number from various formats
|
|
560
|
+
* @internal
|
|
561
|
+
*/
|
|
562
|
+
private parseSequenceNumber;
|
|
563
|
+
/**
|
|
564
|
+
* Normalizes event data to a consistent format
|
|
565
|
+
* @internal
|
|
566
|
+
*/
|
|
567
|
+
private normalizeEventData;
|
|
568
|
+
/**
|
|
569
|
+
* Safely invokes a callback without letting errors propagate
|
|
570
|
+
* @internal
|
|
571
|
+
*/
|
|
572
|
+
private safeInvokeCallback;
|
|
517
573
|
}
|
|
518
574
|
|
|
519
575
|
/**
|
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:
|
|
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
|
}
|
|
@@ -281,20 +281,54 @@ var SUPPORTED_WALLETS = {
|
|
|
281
281
|
};
|
|
282
282
|
var STORAGE_KEY = "movebridge:lastWallet";
|
|
283
283
|
function normalizeHash(data) {
|
|
284
|
+
if (data === null || data === void 0) {
|
|
285
|
+
throw new Error("Invalid hash: received null or undefined");
|
|
286
|
+
}
|
|
284
287
|
if (typeof data === "string") {
|
|
285
|
-
|
|
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}`;
|
|
286
293
|
}
|
|
287
294
|
if (data instanceof Uint8Array) {
|
|
295
|
+
if (data.length === 0) {
|
|
296
|
+
throw new Error("Invalid hash: received empty Uint8Array");
|
|
297
|
+
}
|
|
288
298
|
return "0x" + Array.from(data).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
289
299
|
}
|
|
290
|
-
if (data
|
|
291
|
-
|
|
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("");
|
|
292
306
|
}
|
|
293
|
-
if (data && typeof data
|
|
294
|
-
|
|
295
|
-
|
|
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
|
+
}
|
|
296
326
|
}
|
|
297
|
-
|
|
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}`;
|
|
298
332
|
}
|
|
299
333
|
function toHexString(data) {
|
|
300
334
|
if (typeof data === "string") return data;
|
|
@@ -310,13 +344,32 @@ function extractUserResponse(response) {
|
|
|
310
344
|
if (!response) {
|
|
311
345
|
throw new Error("Empty response from wallet");
|
|
312
346
|
}
|
|
347
|
+
if (typeof response === "string" || typeof response === "number") {
|
|
348
|
+
return response;
|
|
349
|
+
}
|
|
350
|
+
if (response instanceof Uint8Array) {
|
|
351
|
+
return response;
|
|
352
|
+
}
|
|
313
353
|
const resp = response;
|
|
314
|
-
if (resp.status === "rejected") {
|
|
354
|
+
if (resp.status === "rejected" || resp.status === "Rejected") {
|
|
315
355
|
throw new Error("User rejected the request");
|
|
316
356
|
}
|
|
357
|
+
if (resp.status === "error" || resp.error) {
|
|
358
|
+
const errorMsg = resp.error || resp.message || "Transaction failed";
|
|
359
|
+
throw new Error(String(errorMsg));
|
|
360
|
+
}
|
|
317
361
|
if (resp.args !== void 0) {
|
|
318
362
|
return resp.args;
|
|
319
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
|
+
}
|
|
320
373
|
return response;
|
|
321
374
|
}
|
|
322
375
|
function createStandardAdapter(wallet) {
|
|
@@ -350,15 +403,67 @@ function createStandardAdapter(wallet) {
|
|
|
350
403
|
if (!signTxFeature) {
|
|
351
404
|
throw new Error("Wallet does not support signAndSubmitTransaction");
|
|
352
405
|
}
|
|
353
|
-
const
|
|
354
|
-
const
|
|
355
|
-
let
|
|
356
|
-
if (
|
|
357
|
-
|
|
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
|
+
};
|
|
358
416
|
} else {
|
|
359
|
-
|
|
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
|
+
);
|
|
360
466
|
}
|
|
361
|
-
return { hash };
|
|
362
467
|
},
|
|
363
468
|
async signTransaction(payload) {
|
|
364
469
|
if (!signOnlyFeature) {
|
|
@@ -633,11 +738,14 @@ var TransactionBuilder = class {
|
|
|
633
738
|
* @returns Transaction hash
|
|
634
739
|
* @throws MovementError with code WALLET_NOT_CONNECTED if no wallet connected
|
|
635
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.
|
|
636
744
|
*/
|
|
637
745
|
async signAndSubmit(payload) {
|
|
638
746
|
const adapter = this.walletManager.getAdapter();
|
|
639
|
-
const
|
|
640
|
-
if (!adapter || !
|
|
747
|
+
const stateBefore = this.walletManager.getState();
|
|
748
|
+
if (!adapter || !stateBefore.connected) {
|
|
641
749
|
throw Errors.walletNotConnected();
|
|
642
750
|
}
|
|
643
751
|
try {
|
|
@@ -650,7 +758,13 @@ var TransactionBuilder = class {
|
|
|
650
758
|
});
|
|
651
759
|
return result.hash;
|
|
652
760
|
} catch (error) {
|
|
653
|
-
|
|
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
|
+
);
|
|
654
768
|
}
|
|
655
769
|
}
|
|
656
770
|
/**
|
|
@@ -876,13 +990,15 @@ var EventListener = class {
|
|
|
876
990
|
accountAddress = subscription.accountAddress;
|
|
877
991
|
eventType = subscription.eventType;
|
|
878
992
|
} else {
|
|
879
|
-
const
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
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
|
+
);
|
|
886
1002
|
}
|
|
887
1003
|
}
|
|
888
1004
|
const internalSub = {
|
|
@@ -902,6 +1018,41 @@ var EventListener = class {
|
|
|
902
1018
|
});
|
|
903
1019
|
return subscriptionId;
|
|
904
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
|
+
}
|
|
905
1056
|
/**
|
|
906
1057
|
* Unsubscribes from events
|
|
907
1058
|
* @param subscriptionId - Subscription ID to remove
|
|
@@ -948,36 +1099,200 @@ var EventListener = class {
|
|
|
948
1099
|
if (!subscription) {
|
|
949
1100
|
return;
|
|
950
1101
|
}
|
|
1102
|
+
try {
|
|
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);
|
|
1123
|
+
}
|
|
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) {
|
|
951
1132
|
try {
|
|
952
1133
|
const events = await this.aptosClient.getAccountEventsByEventType({
|
|
953
|
-
accountAddress
|
|
954
|
-
eventType
|
|
1134
|
+
accountAddress,
|
|
1135
|
+
eventType,
|
|
955
1136
|
options: {
|
|
956
1137
|
limit: 25,
|
|
957
1138
|
orderBy: [{ sequence_number: "desc" }]
|
|
958
1139
|
}
|
|
959
1140
|
});
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
});
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
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
|
+
);
|
|
976
1160
|
}
|
|
977
|
-
|
|
1161
|
+
return [];
|
|
978
1162
|
}
|
|
979
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
|
+
}));
|
|
980
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);
|
|
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
|
+
}
|
|
981
1296
|
}
|
|
982
1297
|
}
|
|
983
1298
|
};
|
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:
|
|
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
|
}
|
|
@@ -230,20 +230,54 @@ var SUPPORTED_WALLETS = {
|
|
|
230
230
|
};
|
|
231
231
|
var STORAGE_KEY = "movebridge:lastWallet";
|
|
232
232
|
function normalizeHash(data) {
|
|
233
|
+
if (data === null || data === void 0) {
|
|
234
|
+
throw new Error("Invalid hash: received null or undefined");
|
|
235
|
+
}
|
|
233
236
|
if (typeof data === "string") {
|
|
234
|
-
|
|
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}`;
|
|
235
242
|
}
|
|
236
243
|
if (data instanceof Uint8Array) {
|
|
244
|
+
if (data.length === 0) {
|
|
245
|
+
throw new Error("Invalid hash: received empty Uint8Array");
|
|
246
|
+
}
|
|
237
247
|
return "0x" + Array.from(data).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
238
248
|
}
|
|
239
|
-
if (data
|
|
240
|
-
|
|
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("");
|
|
241
255
|
}
|
|
242
|
-
if (data && typeof data
|
|
243
|
-
|
|
244
|
-
|
|
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
|
+
}
|
|
245
275
|
}
|
|
246
|
-
|
|
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}`;
|
|
247
281
|
}
|
|
248
282
|
function toHexString(data) {
|
|
249
283
|
if (typeof data === "string") return data;
|
|
@@ -259,13 +293,32 @@ function extractUserResponse(response) {
|
|
|
259
293
|
if (!response) {
|
|
260
294
|
throw new Error("Empty response from wallet");
|
|
261
295
|
}
|
|
296
|
+
if (typeof response === "string" || typeof response === "number") {
|
|
297
|
+
return response;
|
|
298
|
+
}
|
|
299
|
+
if (response instanceof Uint8Array) {
|
|
300
|
+
return response;
|
|
301
|
+
}
|
|
262
302
|
const resp = response;
|
|
263
|
-
if (resp.status === "rejected") {
|
|
303
|
+
if (resp.status === "rejected" || resp.status === "Rejected") {
|
|
264
304
|
throw new Error("User rejected the request");
|
|
265
305
|
}
|
|
306
|
+
if (resp.status === "error" || resp.error) {
|
|
307
|
+
const errorMsg = resp.error || resp.message || "Transaction failed";
|
|
308
|
+
throw new Error(String(errorMsg));
|
|
309
|
+
}
|
|
266
310
|
if (resp.args !== void 0) {
|
|
267
311
|
return resp.args;
|
|
268
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
|
+
}
|
|
269
322
|
return response;
|
|
270
323
|
}
|
|
271
324
|
function createStandardAdapter(wallet) {
|
|
@@ -299,15 +352,67 @@ function createStandardAdapter(wallet) {
|
|
|
299
352
|
if (!signTxFeature) {
|
|
300
353
|
throw new Error("Wallet does not support signAndSubmitTransaction");
|
|
301
354
|
}
|
|
302
|
-
const
|
|
303
|
-
const
|
|
304
|
-
let
|
|
305
|
-
if (
|
|
306
|
-
|
|
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
|
+
};
|
|
307
365
|
} else {
|
|
308
|
-
|
|
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
|
+
);
|
|
309
415
|
}
|
|
310
|
-
return { hash };
|
|
311
416
|
},
|
|
312
417
|
async signTransaction(payload) {
|
|
313
418
|
if (!signOnlyFeature) {
|
|
@@ -582,11 +687,14 @@ var TransactionBuilder = class {
|
|
|
582
687
|
* @returns Transaction hash
|
|
583
688
|
* @throws MovementError with code WALLET_NOT_CONNECTED if no wallet connected
|
|
584
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.
|
|
585
693
|
*/
|
|
586
694
|
async signAndSubmit(payload) {
|
|
587
695
|
const adapter = this.walletManager.getAdapter();
|
|
588
|
-
const
|
|
589
|
-
if (!adapter || !
|
|
696
|
+
const stateBefore = this.walletManager.getState();
|
|
697
|
+
if (!adapter || !stateBefore.connected) {
|
|
590
698
|
throw Errors.walletNotConnected();
|
|
591
699
|
}
|
|
592
700
|
try {
|
|
@@ -599,7 +707,13 @@ var TransactionBuilder = class {
|
|
|
599
707
|
});
|
|
600
708
|
return result.hash;
|
|
601
709
|
} catch (error) {
|
|
602
|
-
|
|
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
|
+
);
|
|
603
717
|
}
|
|
604
718
|
}
|
|
605
719
|
/**
|
|
@@ -825,13 +939,15 @@ var EventListener = class {
|
|
|
825
939
|
accountAddress = subscription.accountAddress;
|
|
826
940
|
eventType = subscription.eventType;
|
|
827
941
|
} else {
|
|
828
|
-
const
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
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
|
+
);
|
|
835
951
|
}
|
|
836
952
|
}
|
|
837
953
|
const internalSub = {
|
|
@@ -851,6 +967,41 @@ var EventListener = class {
|
|
|
851
967
|
});
|
|
852
968
|
return subscriptionId;
|
|
853
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
|
+
}
|
|
854
1005
|
/**
|
|
855
1006
|
* Unsubscribes from events
|
|
856
1007
|
* @param subscriptionId - Subscription ID to remove
|
|
@@ -897,36 +1048,200 @@ var EventListener = class {
|
|
|
897
1048
|
if (!subscription) {
|
|
898
1049
|
return;
|
|
899
1050
|
}
|
|
1051
|
+
try {
|
|
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);
|
|
1072
|
+
}
|
|
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) {
|
|
900
1081
|
try {
|
|
901
1082
|
const events = await this.aptosClient.getAccountEventsByEventType({
|
|
902
|
-
accountAddress
|
|
903
|
-
eventType
|
|
1083
|
+
accountAddress,
|
|
1084
|
+
eventType,
|
|
904
1085
|
options: {
|
|
905
1086
|
limit: 25,
|
|
906
1087
|
orderBy: [{ sequence_number: "desc" }]
|
|
907
1088
|
}
|
|
908
1089
|
});
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
});
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
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
|
+
);
|
|
925
1109
|
}
|
|
926
|
-
|
|
1110
|
+
return [];
|
|
927
1111
|
}
|
|
928
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
|
+
}));
|
|
929
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);
|
|
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
|
+
}
|
|
930
1245
|
}
|
|
931
1246
|
}
|
|
932
1247
|
};
|
package/package.json
CHANGED
|
@@ -1,55 +1,55 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
2
|
+
"name": "@movebridge/core",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Core SDK for Movement Network - wallet management, transactions, and contract interactions",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
20
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
21
|
+
"clean": "rm -rf dist",
|
|
22
|
+
"typecheck": "tsc --noEmit",
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"test:watch": "vitest"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@aptos-labs/ts-sdk": "^1.26.0",
|
|
28
|
+
"@aptos-labs/wallet-standard": "^0.2.0",
|
|
29
|
+
"eventemitter3": "^5.0.1"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"tsup": "^8.0.1",
|
|
33
|
+
"typescript": "^5.3.2"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {},
|
|
36
|
+
"keywords": [
|
|
37
|
+
"movement",
|
|
38
|
+
"aptos",
|
|
39
|
+
"blockchain",
|
|
40
|
+
"sdk",
|
|
41
|
+
"wallet",
|
|
42
|
+
"web3"
|
|
43
|
+
],
|
|
44
|
+
"author": "Aqila Rifti",
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "https://github.com/AqilaRifti/MoveBridge",
|
|
49
|
+
"directory": "packages/core"
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://github.com/AqilaRifti/MoveBridge#readme",
|
|
52
|
+
"bugs": {
|
|
53
|
+
"url": "https://github.com/AqilaRifti/MoveBridge/issues"
|
|
13
54
|
}
|
|
14
|
-
},
|
|
15
|
-
"files": [
|
|
16
|
-
"dist"
|
|
17
|
-
],
|
|
18
|
-
"dependencies": {
|
|
19
|
-
"@aptos-labs/ts-sdk": "^1.26.0",
|
|
20
|
-
"@aptos-labs/wallet-standard": "^0.2.0",
|
|
21
|
-
"eventemitter3": "^5.0.1"
|
|
22
|
-
},
|
|
23
|
-
"devDependencies": {
|
|
24
|
-
"tsup": "^8.0.1",
|
|
25
|
-
"typescript": "^5.3.2"
|
|
26
|
-
},
|
|
27
|
-
"peerDependencies": {},
|
|
28
|
-
"keywords": [
|
|
29
|
-
"movement",
|
|
30
|
-
"aptos",
|
|
31
|
-
"blockchain",
|
|
32
|
-
"sdk",
|
|
33
|
-
"wallet",
|
|
34
|
-
"web3"
|
|
35
|
-
],
|
|
36
|
-
"author": "Aqila Rifti",
|
|
37
|
-
"license": "MIT",
|
|
38
|
-
"repository": {
|
|
39
|
-
"type": "git",
|
|
40
|
-
"url": "https://github.com/AqilaRifti/MoveBridge",
|
|
41
|
-
"directory": "packages/core"
|
|
42
|
-
},
|
|
43
|
-
"homepage": "https://github.com/AqilaRifti/MoveBridge#readme",
|
|
44
|
-
"bugs": {
|
|
45
|
-
"url": "https://github.com/AqilaRifti/MoveBridge/issues"
|
|
46
|
-
},
|
|
47
|
-
"scripts": {
|
|
48
|
-
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
49
|
-
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
50
|
-
"clean": "rm -rf dist",
|
|
51
|
-
"typecheck": "tsc --noEmit",
|
|
52
|
-
"test": "vitest run",
|
|
53
|
-
"test:watch": "vitest"
|
|
54
|
-
}
|
|
55
55
|
}
|
package/LICENSE
DELETED
|
File without changes
|