@veridex/sdk 1.0.0-beta.9 → 1.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.
Files changed (130) hide show
  1. package/LICENSE +170 -21
  2. package/README.md +574 -117
  3. package/dist/EVMClient-DtqvdfUP.d.mts +376 -0
  4. package/dist/auth/prepareAuth.d.mts +25 -0
  5. package/dist/auth/prepareAuth.js +2406 -0
  6. package/dist/auth/prepareAuth.js.map +1 -0
  7. package/dist/auth/prepareAuth.mjs +151 -0
  8. package/dist/auth/prepareAuth.mjs.map +1 -0
  9. package/dist/chains/aptos/index.d.mts +6 -5
  10. package/dist/chains/aptos/index.js +66 -39
  11. package/dist/chains/aptos/index.js.map +1 -1
  12. package/dist/chains/aptos/index.mjs +5 -547
  13. package/dist/chains/aptos/index.mjs.map +1 -1
  14. package/dist/chains/avalanche/index.d.mts +137 -0
  15. package/dist/chains/avalanche/index.js +1555 -0
  16. package/dist/chains/avalanche/index.js.map +1 -0
  17. package/dist/chains/avalanche/index.mjs +10 -0
  18. package/dist/chains/avalanche/index.mjs.map +1 -0
  19. package/dist/chains/evm/index.d.mts +5 -3
  20. package/dist/chains/evm/index.js +165 -3
  21. package/dist/chains/evm/index.js.map +1 -1
  22. package/dist/chains/evm/index.mjs +8 -1200
  23. package/dist/chains/evm/index.mjs.map +1 -1
  24. package/dist/chains/solana/index.d.mts +1 -1
  25. package/dist/chains/solana/index.js.map +1 -1
  26. package/dist/chains/solana/index.mjs +4 -486
  27. package/dist/chains/solana/index.mjs.map +1 -1
  28. package/dist/chains/stacks/index.d.mts +559 -0
  29. package/dist/chains/stacks/index.js +1207 -0
  30. package/dist/chains/stacks/index.js.map +1 -0
  31. package/dist/chains/stacks/index.mjs +71 -0
  32. package/dist/chains/stacks/index.mjs.map +1 -0
  33. package/dist/chains/starknet/index.d.mts +3 -3
  34. package/dist/chains/starknet/index.js.map +1 -1
  35. package/dist/chains/starknet/index.mjs +5 -503
  36. package/dist/chains/starknet/index.mjs.map +1 -1
  37. package/dist/chains/sui/index.d.mts +2 -2
  38. package/dist/chains/sui/index.js.map +1 -1
  39. package/dist/chains/sui/index.mjs +5 -529
  40. package/dist/chains/sui/index.mjs.map +1 -1
  41. package/dist/chunk-5T6KPH7A.mjs +1082 -0
  42. package/dist/chunk-5T6KPH7A.mjs.map +1 -0
  43. package/dist/chunk-72ZA3OYQ.mjs +20 -0
  44. package/dist/chunk-72ZA3OYQ.mjs.map +1 -0
  45. package/dist/chunk-EFIURACP.mjs +438 -0
  46. package/dist/chunk-EFIURACP.mjs.map +1 -0
  47. package/dist/chunk-F3YAGZSW.mjs +269 -0
  48. package/dist/chunk-F3YAGZSW.mjs.map +1 -0
  49. package/dist/chunk-GWJRKDSA.mjs +549 -0
  50. package/dist/chunk-GWJRKDSA.mjs.map +1 -0
  51. package/dist/chunk-M3MM4YMF.mjs +417 -0
  52. package/dist/chunk-M3MM4YMF.mjs.map +1 -0
  53. package/dist/chunk-N4A2RMUN.mjs +216 -0
  54. package/dist/chunk-N4A2RMUN.mjs.map +1 -0
  55. package/dist/chunk-NUWSMJFJ.mjs +179 -0
  56. package/dist/chunk-NUWSMJFJ.mjs.map +1 -0
  57. package/dist/chunk-OVMMTL6H.mjs +330 -0
  58. package/dist/chunk-OVMMTL6H.mjs.map +1 -0
  59. package/dist/chunk-PDHZ5X5O.mjs +565 -0
  60. package/dist/chunk-PDHZ5X5O.mjs.map +1 -0
  61. package/dist/chunk-PRHNGA4G.mjs +464 -0
  62. package/dist/chunk-PRHNGA4G.mjs.map +1 -0
  63. package/dist/chunk-Q5O3M5LP.mjs +422 -0
  64. package/dist/chunk-Q5O3M5LP.mjs.map +1 -0
  65. package/dist/chunk-QDO6NQ7P.mjs +840 -0
  66. package/dist/chunk-QDO6NQ7P.mjs.map +1 -0
  67. package/dist/chunk-QT4ZZ4GM.mjs +509 -0
  68. package/dist/chunk-QT4ZZ4GM.mjs.map +1 -0
  69. package/dist/chunk-USDA5JTN.mjs +1249 -0
  70. package/dist/chunk-USDA5JTN.mjs.map +1 -0
  71. package/dist/chunk-V636MIV3.mjs +52 -0
  72. package/dist/chunk-V636MIV3.mjs.map +1 -0
  73. package/dist/chunk-X7BZMSPQ.mjs +407 -0
  74. package/dist/chunk-X7BZMSPQ.mjs.map +1 -0
  75. package/dist/chunk-YCUJZ6Z7.mjs +829 -0
  76. package/dist/chunk-YCUJZ6Z7.mjs.map +1 -0
  77. package/dist/constants.d.mts +1 -1
  78. package/dist/constants.js +26 -12
  79. package/dist/constants.js.map +1 -1
  80. package/dist/constants.mjs +16 -375
  81. package/dist/constants.mjs.map +1 -1
  82. package/dist/index-DDalBhAm.d.mts +243 -0
  83. package/dist/index.d.mts +2508 -556
  84. package/dist/index.js +14576 -9628
  85. package/dist/index.js.map +1 -1
  86. package/dist/index.mjs +4108 -7840
  87. package/dist/index.mjs.map +1 -1
  88. package/dist/passkey.d.mts +182 -0
  89. package/dist/passkey.js +914 -0
  90. package/dist/passkey.js.map +1 -0
  91. package/dist/passkey.mjs +15 -0
  92. package/dist/passkey.mjs.map +1 -0
  93. package/dist/payload.js.map +1 -1
  94. package/dist/payload.mjs +25 -244
  95. package/dist/payload.mjs.map +1 -1
  96. package/dist/portfolio-V347KZOL.mjs +13 -0
  97. package/dist/portfolio-V347KZOL.mjs.map +1 -0
  98. package/dist/queries/index.js +145 -12
  99. package/dist/queries/index.js.map +1 -1
  100. package/dist/queries/index.mjs +14 -1496
  101. package/dist/queries/index.mjs.map +1 -1
  102. package/dist/{types-FJL7j6gQ.d.ts → types-B7V5VNbO.d.mts} +6 -2
  103. package/dist/{types-ChIsqCiw.d.mts → types-DP2CQT8p.d.mts} +12 -1
  104. package/dist/types.d.mts +16 -0
  105. package/dist/types.js.map +1 -1
  106. package/dist/utils.js +25 -11
  107. package/dist/utils.js.map +1 -1
  108. package/dist/utils.mjs +19 -371
  109. package/dist/utils.mjs.map +1 -1
  110. package/dist/wormhole.js.map +1 -1
  111. package/dist/wormhole.mjs +25 -397
  112. package/dist/wormhole.mjs.map +1 -1
  113. package/package.json +28 -3
  114. package/scripts/patch-noble-curves.js +78 -0
  115. package/dist/chains/aptos/index.d.ts +0 -145
  116. package/dist/chains/evm/index.d.ts +0 -5
  117. package/dist/chains/solana/index.d.ts +0 -116
  118. package/dist/chains/starknet/index.d.ts +0 -172
  119. package/dist/chains/sui/index.d.ts +0 -182
  120. package/dist/constants.d.ts +0 -150
  121. package/dist/index-0NXfbk0z.d.ts +0 -637
  122. package/dist/index-D0dLVjTA.d.mts +0 -637
  123. package/dist/index.d.ts +0 -3123
  124. package/dist/payload.d.ts +0 -125
  125. package/dist/queries/index.d.ts +0 -148
  126. package/dist/types-ChIsqCiw.d.ts +0 -565
  127. package/dist/types-FJL7j6gQ.d.mts +0 -172
  128. package/dist/types.d.ts +0 -407
  129. package/dist/utils.d.ts +0 -81
  130. package/dist/wormhole.d.ts +0 -167
@@ -0,0 +1,1555 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/chains/avalanche/index.ts
21
+ var avalanche_exports = {};
22
+ __export(avalanche_exports, {
23
+ AvalancheClient: () => AvalancheClient
24
+ });
25
+ module.exports = __toCommonJS(avalanche_exports);
26
+
27
+ // src/chains/avalanche/AvalancheClient.ts
28
+ var import_ethers3 = require("ethers");
29
+
30
+ // src/chains/evm/EVMClient.ts
31
+ var import_ethers2 = require("ethers");
32
+
33
+ // src/payload.ts
34
+ var import_ethers = require("ethers");
35
+
36
+ // src/constants.ts
37
+ var ACTION_TRANSFER = 1;
38
+ var ACTION_EXECUTE = 2;
39
+ var ACTION_BRIDGE = 4;
40
+
41
+ // src/payload.ts
42
+ function encodeTransferAction(token, recipient, amount) {
43
+ const tokenPadded = padTo32Bytes(token);
44
+ const recipientPadded = padTo32Bytes(recipient);
45
+ const amountBytes = import_ethers.ethers.zeroPadValue(import_ethers.ethers.toBeHex(amount), 32);
46
+ return import_ethers.ethers.concat([
47
+ import_ethers.ethers.toBeHex(ACTION_TRANSFER, 1),
48
+ tokenPadded,
49
+ recipientPadded,
50
+ amountBytes
51
+ ]);
52
+ }
53
+ function encodeBridgeAction(token, amount, targetChain, recipient) {
54
+ const tokenPadded = padTo32Bytes(token);
55
+ const amountBytes = import_ethers.ethers.zeroPadValue(import_ethers.ethers.toBeHex(amount), 32);
56
+ const targetChainBytes = import_ethers.ethers.toBeHex(targetChain, 2);
57
+ const recipientPadded = padTo32Bytes(recipient);
58
+ return import_ethers.ethers.concat([
59
+ import_ethers.ethers.toBeHex(ACTION_BRIDGE, 1),
60
+ tokenPadded,
61
+ amountBytes,
62
+ targetChainBytes,
63
+ recipientPadded
64
+ ]);
65
+ }
66
+ function encodeExecuteAction(target, value, data) {
67
+ const targetPadded = padTo32Bytes(target);
68
+ const valueBytes = import_ethers.ethers.zeroPadValue(import_ethers.ethers.toBeHex(value), 32);
69
+ const dataBytes = import_ethers.ethers.getBytes(data);
70
+ const dataLengthBytes = import_ethers.ethers.toBeHex(dataBytes.length, 2);
71
+ return import_ethers.ethers.concat([
72
+ import_ethers.ethers.toBeHex(ACTION_EXECUTE, 1),
73
+ targetPadded,
74
+ valueBytes,
75
+ dataLengthBytes,
76
+ data
77
+ ]);
78
+ }
79
+ function padTo32Bytes(address) {
80
+ if (address.toLowerCase() === "native") {
81
+ return "0x" + "0".repeat(64);
82
+ }
83
+ if (address.startsWith("0x")) {
84
+ const hex2 = address.replace("0x", "");
85
+ if (!/^[0-9a-fA-F]*$/.test(hex2)) {
86
+ throw new Error(`Invalid address: ${address}. Expected hex string or 'native'.`);
87
+ }
88
+ return "0x" + hex2.padStart(64, "0");
89
+ }
90
+ const base58Chars = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
91
+ for (const char of address) {
92
+ if (!base58Chars.includes(char)) {
93
+ throw new Error(`Invalid address: ${address}. Contains invalid base58 character '${char}'.`);
94
+ }
95
+ }
96
+ let value = BigInt(0);
97
+ for (const char of address) {
98
+ value = value * 58n + BigInt(base58Chars.indexOf(char));
99
+ }
100
+ let hex = value.toString(16);
101
+ if (hex.length > 64) {
102
+ throw new Error(`Invalid address: ${address}. Decoded value too large for 32 bytes.`);
103
+ }
104
+ return "0x" + hex.padStart(64, "0");
105
+ }
106
+
107
+ // src/chains/evm/EVMClient.ts
108
+ var PROXY_BYTECODE_PREFIX = "0x3d602d80600a3d3981f3363d3d373d3d3d363d73";
109
+ var PROXY_BYTECODE_SUFFIX = "5af43d82803e903d91602b57fd5bf3";
110
+ var ERC20_ABI = [
111
+ "function balanceOf(address owner) view returns (uint256)",
112
+ "function decimals() view returns (uint8)",
113
+ "function symbol() view returns (string)",
114
+ "function name() view returns (string)",
115
+ "function allowance(address owner, address spender) view returns (uint256)",
116
+ "function transfer(address to, uint256 amount) returns (bool)",
117
+ "function approve(address spender, uint256 amount) returns (bool)"
118
+ ];
119
+ var HUB_ABI = [
120
+ "function dispatch(tuple(bytes authenticatorData, string clientDataJSON, uint256 challengeIndex, uint256 typeIndex, uint256 r, uint256 s) signature, uint256 publicKeyX, uint256 publicKeyY, uint16 targetChain, bytes actionPayload, uint256 nonce) payable returns (uint64 sequence)",
121
+ "function userNonces(bytes32 userKeyHash) view returns (uint256)",
122
+ "function getMessageFee() view returns (uint256)",
123
+ "function getVaultAddress(bytes32 userKeyHash) view returns (address)",
124
+ "function vaultExists(bytes32 userKeyHash) view returns (bool)",
125
+ "function createVault(bytes32 userKeyHash) returns (address)",
126
+ // Issue #9/#10: New Hub methods for Query-based execution
127
+ "function getUserState(bytes32 userKeyHash) view returns (bytes32 keyHash, uint256 nonce, bytes32 lastActionHash)",
128
+ "function getUserLastActionHash(bytes32 userKeyHash) view returns (bytes32)",
129
+ // Issue #13: Session key management
130
+ "function registerSession(tuple(bytes authenticatorData, string clientDataJSON, uint256 challengeIndex, uint256 typeIndex, uint256 r, uint256 s) auth, uint256 publicKeyX, uint256 publicKeyY, bytes32 sessionKeyHash, uint256 duration, uint256 maxValue, bool requireUV) external",
131
+ "function isSessionActive(bytes32 userKeyHash, bytes32 sessionKeyHash) view returns (bool active, uint256 expiry, uint256 maxValue, uint256 sessionIndex)",
132
+ "function revokeSession(tuple(bytes authenticatorData, string clientDataJSON, uint256 challengeIndex, uint256 typeIndex, uint256 r, uint256 s) auth, uint256 publicKeyX, uint256 publicKeyY, bytes32 sessionKeyHash, bool requireUV) external",
133
+ "function getUserSessions(bytes32 userKeyHash) view returns (tuple(bytes32 sessionKeyHash, uint256 expiry, uint256 maxValue, bool revoked)[])",
134
+ "function getUserSessionCount(bytes32 userKeyHash) view returns (uint256)",
135
+ // Issue #22: Backup Passkey / Multi-Key Identity management
136
+ "function registerIdentity(tuple(bytes authenticatorData, string clientDataJSON, uint256 challengeIndex, uint256 typeIndex, uint256 r, uint256 s) auth, uint256 publicKeyX, uint256 publicKeyY) external",
137
+ "function addBackupKey(tuple(bytes authenticatorData, string clientDataJSON, uint256 challengeIndex, uint256 typeIndex, uint256 r, uint256 s) auth, uint256 publicKeyX, uint256 publicKeyY, uint256 newPublicKeyX, uint256 newPublicKeyY, uint256 nonce) external payable returns (uint64 sequence)",
138
+ "function removeKey(tuple(bytes authenticatorData, string clientDataJSON, uint256 challengeIndex, uint256 typeIndex, uint256 r, uint256 s) auth, uint256 publicKeyX, uint256 publicKeyY, bytes32 keyToRemove, uint256 nonce) external payable returns (uint64 sequence)",
139
+ "function getIdentityForKey(bytes32 keyHash) view returns (bytes32)",
140
+ "function getAuthorizedKeys(bytes32 identity) view returns (bytes32[])",
141
+ "function getAuthorizedKeyCount(bytes32 identity) view returns (uint256)",
142
+ "function isAuthorizedForIdentity(bytes32 identity, bytes32 keyHash) view returns (bool)",
143
+ "function isIdentityRoot(bytes32 keyHash) view returns (bool)",
144
+ "function getIdentityState(bytes32 keyHash) view returns (bytes32 identity, uint256 keyCount, uint256 maxKeys, bool isRoot)",
145
+ // Issue #23: Social Recovery / Guardian Management
146
+ "function setupGuardians(tuple(bytes authenticatorData, string clientDataJSON, uint256 challengeIndex, uint256 typeIndex, uint256 r, uint256 s) auth, uint256 publicKeyX, uint256 publicKeyY, bytes32[] guardians, uint256 threshold) external payable returns (uint64 sequence)",
147
+ "function addGuardian(tuple(bytes authenticatorData, string clientDataJSON, uint256 challengeIndex, uint256 typeIndex, uint256 r, uint256 s) auth, uint256 publicKeyX, uint256 publicKeyY, bytes32 guardianKeyHash) external payable returns (uint64 sequence)",
148
+ "function removeGuardian(tuple(bytes authenticatorData, string clientDataJSON, uint256 challengeIndex, uint256 typeIndex, uint256 r, uint256 s) auth, uint256 publicKeyX, uint256 publicKeyY, bytes32 guardianKeyHash) external payable returns (uint64 sequence)",
149
+ "function initiateRecovery(tuple(bytes authenticatorData, string clientDataJSON, uint256 challengeIndex, uint256 typeIndex, uint256 r, uint256 s) auth, uint256 publicKeyX, uint256 publicKeyY, bytes32 identityToRecover, bytes32 newOwnerKeyHash) external payable returns (uint64 sequence)",
150
+ "function approveRecovery(tuple(bytes authenticatorData, string clientDataJSON, uint256 challengeIndex, uint256 typeIndex, uint256 r, uint256 s) auth, uint256 publicKeyX, uint256 publicKeyY, bytes32 identityToRecover) external payable returns (uint64 sequence)",
151
+ "function executeRecovery(bytes32 identityToRecover, uint256 newPublicKeyX, uint256 newPublicKeyY) external payable returns (uint64 sequence)",
152
+ "function cancelRecovery(tuple(bytes authenticatorData, string clientDataJSON, uint256 challengeIndex, uint256 typeIndex, uint256 r, uint256 s) auth, uint256 publicKeyX, uint256 publicKeyY) external payable returns (uint64 sequence)",
153
+ "function getGuardians(bytes32 identityKeyHash) view returns (bytes32[] guardians, uint256 threshold, bool isConfigured)",
154
+ "function getRecoveryStatus(bytes32 identityKeyHash) view returns (bool isActive, bytes32 newOwnerKeyHash, uint256 initiatedAt, uint256 approvalCount, uint256 threshold, uint256 canExecuteAt, uint256 expiresAt)",
155
+ "function hasGuardianApproved(bytes32 identityKeyHash, bytes32 guardianKeyHash) view returns (bool hasApproved)",
156
+ // ADR-0037: Threshold Multisig
157
+ "function configureTransactionPolicy(tuple(bytes authenticatorData, string clientDataJSON, uint256 challengeIndex, uint256 typeIndex, uint256 r, uint256 s) auth, uint256 publicKeyX, uint256 publicKeyY, uint256 threshold, uint256 protectedActionMask, uint256 proposalTtl, bool disableSessions) external",
158
+ "function createTransactionProposal(tuple(bytes authenticatorData, string clientDataJSON, uint256 challengeIndex, uint256 typeIndex, uint256 r, uint256 s) auth, uint256 publicKeyX, uint256 publicKeyY, uint16 targetChain, bytes actionPayload) external returns (bytes32 proposalId)",
159
+ "function approveTransactionProposal(tuple(bytes authenticatorData, string clientDataJSON, uint256 challengeIndex, uint256 typeIndex, uint256 r, uint256 s) auth, uint256 publicKeyX, uint256 publicKeyY, bytes32 proposalId) external returns (uint256 approvalCount, bool thresholdReached)",
160
+ "function cancelTransactionProposal(tuple(bytes authenticatorData, string clientDataJSON, uint256 challengeIndex, uint256 typeIndex, uint256 r, uint256 s) auth, uint256 publicKeyX, uint256 publicKeyY, bytes32 proposalId) external",
161
+ "function executeTransactionProposal(bytes32 proposalId) external payable returns (uint64 sequence)",
162
+ "function getTransactionPolicy(bytes32 identityKeyHash) view returns (bool enabled, uint256 threshold, uint256 protectedActionMask, uint256 proposalTtl, bool disableSessions)",
163
+ "function getTransactionProposal(bytes32 proposalId) view returns (bytes32 identityKeyHash, bytes32 proposerKeyHash, uint16 targetChain, uint8 actionType, bytes32 actionHash, uint256 createdAt, uint256 expiresAt, uint256 approvalCount, uint256 requiredThreshold, uint8 state)",
164
+ "function hasApprovedTransactionProposal(bytes32 proposalId, bytes32 keyHash) view returns (bool)"
165
+ ];
166
+ var FACTORY_ABI = [
167
+ "function createVault(bytes32 ownerKeyHash) returns (address vault)",
168
+ "function getVault(bytes32 ownerKeyHash) view returns (address)",
169
+ "function computeVaultAddress(bytes32 ownerKeyHash) view returns (address vault)",
170
+ "function vaultExists(bytes32 ownerKeyHash) view returns (bool)",
171
+ "function implementation() view returns (address)"
172
+ ];
173
+ var EVMClient = class {
174
+ config;
175
+ provider;
176
+ hubContract;
177
+ factoryContract = null;
178
+ cachedImplementation = null;
179
+ constructor(config) {
180
+ this.config = {
181
+ name: config.name ?? `EVM Chain ${config.chainId}`,
182
+ chainId: config.chainId,
183
+ wormholeChainId: config.wormholeChainId,
184
+ rpcUrl: config.rpcUrl,
185
+ explorerUrl: config.explorerUrl ?? "",
186
+ isEvm: true,
187
+ contracts: {
188
+ hub: config.hubContractAddress,
189
+ vaultFactory: config.vaultFactory,
190
+ vaultImplementation: config.vaultImplementation,
191
+ wormholeCoreBridge: config.wormholeCoreBridge,
192
+ tokenBridge: config.tokenBridge
193
+ }
194
+ };
195
+ this.provider = new import_ethers2.ethers.JsonRpcProvider(config.rpcUrl);
196
+ this.hubContract = new import_ethers2.ethers.Contract(
197
+ config.hubContractAddress,
198
+ HUB_ABI,
199
+ this.provider
200
+ );
201
+ if (config.vaultFactory) {
202
+ this.factoryContract = new import_ethers2.ethers.Contract(
203
+ config.vaultFactory,
204
+ FACTORY_ABI,
205
+ this.provider
206
+ );
207
+ }
208
+ if (config.vaultImplementation) {
209
+ this.cachedImplementation = config.vaultImplementation;
210
+ }
211
+ }
212
+ getConfig() {
213
+ return this.config;
214
+ }
215
+ async getNonce(userKeyHash) {
216
+ const nonce = await this.hubContract.userNonces(userKeyHash);
217
+ return BigInt(nonce.toString());
218
+ }
219
+ /**
220
+ * Get user state from Hub (Issue #9/#10)
221
+ * Returns comprehensive state including last action hash
222
+ */
223
+ async getUserState(userKeyHash) {
224
+ try {
225
+ const result = await this.hubContract.getUserState(userKeyHash);
226
+ return {
227
+ keyHash: result[0],
228
+ nonce: BigInt(result[1].toString()),
229
+ lastActionHash: result[2]
230
+ };
231
+ } catch (error) {
232
+ const nonce = await this.getNonce(userKeyHash);
233
+ return {
234
+ keyHash: userKeyHash,
235
+ nonce,
236
+ lastActionHash: import_ethers2.ethers.ZeroHash
237
+ };
238
+ }
239
+ }
240
+ /**
241
+ * Get user's last action hash from Hub (Issue #9/#10)
242
+ * Returns zero hash if user has no actions yet
243
+ */
244
+ async getUserLastActionHash(userKeyHash) {
245
+ try {
246
+ return await this.hubContract.getUserLastActionHash(userKeyHash);
247
+ } catch (error) {
248
+ return import_ethers2.ethers.ZeroHash;
249
+ }
250
+ }
251
+ // ==========================================================================
252
+ // Session Management Methods (Issue #13)
253
+ // ==========================================================================
254
+ /**
255
+ * Register a new session key for temporary authentication
256
+ * Enables native L1 speed for repeat transactions without biometric auth
257
+ *
258
+ * @param params Session registration parameters
259
+ * @param signer Ethereum signer to pay gas
260
+ * @returns Transaction receipt
261
+ */
262
+ async registerSession(params, signer) {
263
+ const hubWithSigner = this.hubContract.connect(signer);
264
+ const authTuple = {
265
+ authenticatorData: params.signature.authenticatorData,
266
+ clientDataJSON: params.signature.clientDataJSON,
267
+ challengeIndex: params.signature.challengeIndex,
268
+ typeIndex: params.signature.typeIndex,
269
+ r: params.signature.r,
270
+ s: params.signature.s
271
+ };
272
+ const tx = await hubWithSigner.registerSession(
273
+ authTuple,
274
+ params.publicKeyX,
275
+ params.publicKeyY,
276
+ params.sessionKeyHash,
277
+ params.duration,
278
+ params.maxValue,
279
+ params.requireUV
280
+ );
281
+ return await tx.wait();
282
+ }
283
+ /**
284
+ * Check if a session is currently active (queryable via Wormhole CCQ)
285
+ *
286
+ * @param userKeyHash Hash of the user's Passkey public key
287
+ * @param sessionKeyHash Hash of the session key to check
288
+ * @returns Session validation result
289
+ */
290
+ async isSessionActive(userKeyHash, sessionKeyHash) {
291
+ const result = await this.hubContract.isSessionActive(userKeyHash, sessionKeyHash);
292
+ return {
293
+ active: result[0],
294
+ expiry: Number(result[1]),
295
+ maxValue: BigInt(result[2].toString()),
296
+ sessionIndex: Number(result[3])
297
+ };
298
+ }
299
+ /**
300
+ * Revoke a session key immediately
301
+ *
302
+ * @param params Session revocation parameters
303
+ * @param signer Ethereum signer to pay gas
304
+ * @returns Transaction receipt
305
+ */
306
+ async revokeSession(params, signer) {
307
+ const hubWithSigner = this.hubContract.connect(signer);
308
+ const authTuple = {
309
+ authenticatorData: params.signature.authenticatorData,
310
+ clientDataJSON: params.signature.clientDataJSON,
311
+ challengeIndex: params.signature.challengeIndex,
312
+ typeIndex: params.signature.typeIndex,
313
+ r: params.signature.r,
314
+ s: params.signature.s
315
+ };
316
+ const tx = await hubWithSigner.revokeSession(
317
+ authTuple,
318
+ params.publicKeyX,
319
+ params.publicKeyY,
320
+ params.sessionKeyHash,
321
+ params.requireUV
322
+ );
323
+ return await tx.wait();
324
+ }
325
+ /**
326
+ * Get all sessions for a user
327
+ *
328
+ * @param userKeyHash Hash of the user's Passkey public key
329
+ * @returns Array of all sessions (active and expired/revoked)
330
+ */
331
+ async getUserSessions(userKeyHash) {
332
+ const sessions = await this.hubContract.getUserSessions(userKeyHash);
333
+ return sessions.map((s) => ({
334
+ sessionKeyHash: s.sessionKeyHash,
335
+ expiry: Number(s.expiry),
336
+ maxValue: BigInt(s.maxValue.toString()),
337
+ revoked: s.revoked
338
+ }));
339
+ }
340
+ /**
341
+ * Get the number of sessions for a user
342
+ *
343
+ * @param userKeyHash Hash of the user's Passkey public key
344
+ * @returns Number of sessions
345
+ */
346
+ async getUserSessionCount(userKeyHash) {
347
+ const count = await this.hubContract.getUserSessionCount(userKeyHash);
348
+ return Number(count);
349
+ }
350
+ async getMessageFee() {
351
+ const fee = await this.hubContract.getMessageFee();
352
+ return BigInt(fee.toString());
353
+ }
354
+ async buildTransferPayload(params) {
355
+ return encodeTransferAction(
356
+ params.token,
357
+ params.recipient,
358
+ params.amount
359
+ );
360
+ }
361
+ async buildExecutePayload(params) {
362
+ return encodeExecuteAction(
363
+ params.target,
364
+ params.value,
365
+ params.data
366
+ );
367
+ }
368
+ async buildBridgePayload(params) {
369
+ return encodeBridgeAction(
370
+ params.token,
371
+ params.amount,
372
+ params.destinationChain,
373
+ params.recipient
374
+ );
375
+ }
376
+ async dispatch(signature, publicKeyX, publicKeyY, targetChain, actionPayload, nonce, signer) {
377
+ const hubWithSigner = this.hubContract.connect(signer);
378
+ const signatureTuple = {
379
+ authenticatorData: signature.authenticatorData,
380
+ clientDataJSON: signature.clientDataJSON,
381
+ challengeIndex: signature.challengeIndex,
382
+ typeIndex: signature.typeIndex,
383
+ r: signature.r,
384
+ s: signature.s
385
+ };
386
+ const messageFee = await this.getMessageFee();
387
+ const tx = await hubWithSigner.dispatch(
388
+ signatureTuple,
389
+ publicKeyX,
390
+ publicKeyY,
391
+ targetChain,
392
+ actionPayload,
393
+ nonce,
394
+ { value: messageFee }
395
+ );
396
+ const receipt = await tx.wait();
397
+ const dispatchEvent = receipt.logs.find((log) => {
398
+ try {
399
+ const parsed = hubWithSigner.interface.parseLog(log);
400
+ return parsed?.name === "ActionDispatched";
401
+ } catch {
402
+ return false;
403
+ }
404
+ });
405
+ let sequence = 0n;
406
+ if (dispatchEvent) {
407
+ const parsed = hubWithSigner.interface.parseLog(dispatchEvent);
408
+ sequence = BigInt(parsed?.args?.sequence?.toString() ?? "0");
409
+ }
410
+ const keyHash = import_ethers2.ethers.keccak256(
411
+ import_ethers2.ethers.AbiCoder.defaultAbiCoder().encode(
412
+ ["uint256", "uint256"],
413
+ [publicKeyX, publicKeyY]
414
+ )
415
+ );
416
+ return {
417
+ transactionHash: receipt.hash,
418
+ sequence,
419
+ userKeyHash: keyHash,
420
+ targetChain,
421
+ blockNumber: receipt.blockNumber
422
+ };
423
+ }
424
+ /**
425
+ * Dispatch an action to the Hub via relayer (gasless)
426
+ * The relayer pays for gas and submits the transaction on behalf of the user
427
+ */
428
+ async dispatchGasless(signature, publicKeyX, publicKeyY, targetChain, actionPayload, nonce, relayerUrl) {
429
+ const keyHash = import_ethers2.ethers.keccak256(
430
+ import_ethers2.ethers.AbiCoder.defaultAbiCoder().encode(
431
+ ["uint256", "uint256"],
432
+ [publicKeyX, publicKeyY]
433
+ )
434
+ );
435
+ const message = import_ethers2.ethers.keccak256(
436
+ import_ethers2.ethers.AbiCoder.defaultAbiCoder().encode(
437
+ ["bytes32", "uint16", "bytes", "uint256"],
438
+ [keyHash, targetChain, actionPayload, nonce]
439
+ )
440
+ );
441
+ const request = {
442
+ messageHash: message,
443
+ r: "0x" + signature.r.toString(16).padStart(64, "0"),
444
+ s: "0x" + signature.s.toString(16).padStart(64, "0"),
445
+ publicKeyX: "0x" + publicKeyX.toString(16).padStart(64, "0"),
446
+ publicKeyY: "0x" + publicKeyY.toString(16).padStart(64, "0"),
447
+ targetChain,
448
+ actionPayload,
449
+ nonce: Number(nonce)
450
+ };
451
+ const response = await fetch(`${relayerUrl}/api/v1/submit`, {
452
+ method: "POST",
453
+ headers: {
454
+ "Content-Type": "application/json"
455
+ },
456
+ body: JSON.stringify(request)
457
+ });
458
+ if (!response.ok) {
459
+ const error = await response.json().catch(() => ({ error: response.statusText }));
460
+ throw new Error(`Relayer submission failed: ${error.error || response.statusText}`);
461
+ }
462
+ const result = await response.json();
463
+ if (!result.success) {
464
+ throw new Error(`Relayer submission failed: ${result.error}`);
465
+ }
466
+ return {
467
+ transactionHash: result.txHash,
468
+ sequence: BigInt(result.sequence || "0"),
469
+ userKeyHash: keyHash,
470
+ targetChain
471
+ };
472
+ }
473
+ async getVaultAddress(userKeyHash) {
474
+ try {
475
+ if (this.factoryContract) {
476
+ const address2 = await this.factoryContract.getVault(userKeyHash);
477
+ if (address2 !== import_ethers2.ethers.ZeroAddress) {
478
+ return address2;
479
+ }
480
+ }
481
+ const address = await this.hubContract.getVaultAddress(userKeyHash);
482
+ if (address === import_ethers2.ethers.ZeroAddress) {
483
+ return null;
484
+ }
485
+ return address;
486
+ } catch (error) {
487
+ console.error("Error getting vault address:", error);
488
+ return null;
489
+ }
490
+ }
491
+ /**
492
+ * Compute vault address deterministically without querying the chain
493
+ * Uses CREATE2 with EIP-1167 minimal proxy pattern
494
+ */
495
+ computeVaultAddress(userKeyHash) {
496
+ const factoryAddress = this.getFactoryAddress();
497
+ const implementationAddress = this.getImplementationAddress();
498
+ if (!factoryAddress || !implementationAddress) {
499
+ throw new Error("Factory and implementation addresses required for address computation");
500
+ }
501
+ const salt = import_ethers2.ethers.keccak256(
502
+ import_ethers2.ethers.solidityPacked(
503
+ ["address", "bytes32"],
504
+ [factoryAddress, userKeyHash]
505
+ )
506
+ );
507
+ const initCode = this.buildProxyInitCode(implementationAddress);
508
+ const initCodeHash = import_ethers2.ethers.keccak256(initCode);
509
+ const create2Data = import_ethers2.ethers.solidityPacked(
510
+ ["bytes1", "address", "bytes32", "bytes32"],
511
+ ["0xff", factoryAddress, salt, initCodeHash]
512
+ );
513
+ const hash = import_ethers2.ethers.keccak256(create2Data);
514
+ return import_ethers2.ethers.getAddress("0x" + hash.slice(26));
515
+ }
516
+ /**
517
+ * Build EIP-1167 minimal proxy initcode
518
+ */
519
+ buildProxyInitCode(implementationAddress) {
520
+ const impl = implementationAddress.toLowerCase().replace("0x", "");
521
+ return PROXY_BYTECODE_PREFIX + impl + PROXY_BYTECODE_SUFFIX;
522
+ }
523
+ async vaultExists(userKeyHash) {
524
+ try {
525
+ if (this.factoryContract) {
526
+ return await this.factoryContract.vaultExists(userKeyHash);
527
+ }
528
+ if (this.hubContract.vaultExists) {
529
+ try {
530
+ return await this.hubContract.vaultExists(userKeyHash);
531
+ } catch {
532
+ return false;
533
+ }
534
+ }
535
+ return false;
536
+ } catch {
537
+ return false;
538
+ }
539
+ }
540
+ async createVault(userKeyHash, signer) {
541
+ const exists = await this.vaultExists(userKeyHash);
542
+ if (exists) {
543
+ const address = await this.getVaultAddress(userKeyHash);
544
+ if (address) {
545
+ return {
546
+ address,
547
+ transactionHash: "",
548
+ blockNumber: 0,
549
+ gasUsed: 0n,
550
+ alreadyExisted: true
551
+ };
552
+ }
553
+ }
554
+ let tx;
555
+ if (this.factoryContract) {
556
+ const factoryWithSigner = this.factoryContract.connect(signer);
557
+ tx = await factoryWithSigner.createVault(userKeyHash);
558
+ } else {
559
+ const hubWithSigner = this.hubContract.connect(signer);
560
+ tx = await hubWithSigner.createVault(userKeyHash);
561
+ }
562
+ const receipt = await tx.wait();
563
+ if (!receipt) {
564
+ throw new Error("Transaction failed - no receipt");
565
+ }
566
+ const vaultAddress = await this.getVaultAddress(userKeyHash);
567
+ if (!vaultAddress) {
568
+ throw new Error("Failed to create vault - address not found after creation");
569
+ }
570
+ return {
571
+ address: vaultAddress,
572
+ transactionHash: receipt.hash,
573
+ blockNumber: receipt.blockNumber,
574
+ gasUsed: receipt.gasUsed,
575
+ alreadyExisted: false
576
+ };
577
+ }
578
+ /**
579
+ * Create a vault with a sponsor wallet paying for gas
580
+ *
581
+ * @param userKeyHash - The user's passkey hash
582
+ * @param sponsorPrivateKey - Private key of the wallet that will pay gas
583
+ * @param rpcUrl - Optional RPC URL to use (defaults to client's RPC)
584
+ * @returns VaultCreationResult with address and transaction details
585
+ */
586
+ async createVaultSponsored(userKeyHash, sponsorPrivateKey, rpcUrl) {
587
+ const exists = await this.vaultExists(userKeyHash);
588
+ if (exists) {
589
+ const address = await this.getVaultAddress(userKeyHash);
590
+ if (address) {
591
+ return {
592
+ address,
593
+ transactionHash: "",
594
+ blockNumber: 0,
595
+ gasUsed: 0n,
596
+ alreadyExisted: true
597
+ };
598
+ }
599
+ }
600
+ const provider = rpcUrl ? new import_ethers2.ethers.JsonRpcProvider(rpcUrl) : this.provider;
601
+ const sponsorWallet = new import_ethers2.ethers.Wallet(sponsorPrivateKey, provider);
602
+ const sponsorBalance = await provider.getBalance(sponsorWallet.address);
603
+ const estimatedGas = await this.estimateVaultCreationGas(userKeyHash);
604
+ const feeData = await provider.getFeeData();
605
+ const estimatedCost = estimatedGas * (feeData.gasPrice ?? 1000000000n);
606
+ if (sponsorBalance < estimatedCost) {
607
+ throw new Error(
608
+ `Sponsor wallet has insufficient funds. Balance: ${import_ethers2.ethers.formatEther(sponsorBalance)} ETH, Estimated cost: ${import_ethers2.ethers.formatEther(estimatedCost)} ETH`
609
+ );
610
+ }
611
+ let tx;
612
+ if (this.factoryContract) {
613
+ const factoryWithSponsor = this.factoryContract.connect(sponsorWallet);
614
+ tx = await factoryWithSponsor.createVault(userKeyHash);
615
+ } else {
616
+ const hubWithSponsor = this.hubContract.connect(sponsorWallet);
617
+ tx = await hubWithSponsor.createVault(userKeyHash);
618
+ }
619
+ const receipt = await tx.wait();
620
+ if (!receipt) {
621
+ throw new Error("Transaction failed - no receipt");
622
+ }
623
+ const vaultAddress = await this.getVaultAddress(userKeyHash);
624
+ if (!vaultAddress) {
625
+ throw new Error("Failed to create vault - address not found after creation");
626
+ }
627
+ return {
628
+ address: vaultAddress,
629
+ transactionHash: receipt.hash,
630
+ blockNumber: receipt.blockNumber,
631
+ gasUsed: receipt.gasUsed,
632
+ alreadyExisted: false,
633
+ sponsoredBy: sponsorWallet.address
634
+ };
635
+ }
636
+ async estimateVaultCreationGas(userKeyHash) {
637
+ try {
638
+ if (this.factoryContract) {
639
+ return await this.factoryContract.createVault.estimateGas(userKeyHash);
640
+ }
641
+ return await this.hubContract.createVault.estimateGas(userKeyHash);
642
+ } catch (error) {
643
+ console.warn("Gas estimation failed, returning default:", error);
644
+ return 150000n;
645
+ }
646
+ }
647
+ getFactoryAddress() {
648
+ return this.config.contracts.vaultFactory;
649
+ }
650
+ getImplementationAddress() {
651
+ return this.config.contracts.vaultImplementation ?? this.cachedImplementation ?? void 0;
652
+ }
653
+ /**
654
+ * Fetch implementation address from factory contract
655
+ */
656
+ async fetchImplementationAddress() {
657
+ if (this.cachedImplementation) {
658
+ return this.cachedImplementation;
659
+ }
660
+ if (!this.factoryContract) {
661
+ return null;
662
+ }
663
+ try {
664
+ this.cachedImplementation = await this.factoryContract.implementation();
665
+ return this.cachedImplementation;
666
+ } catch (error) {
667
+ console.error("Error fetching implementation address:", error);
668
+ return null;
669
+ }
670
+ }
671
+ /**
672
+ * Get the provider instance
673
+ */
674
+ getProvider() {
675
+ return this.provider;
676
+ }
677
+ // ========================================================================
678
+ // Balance Methods (Phase 2)
679
+ // ========================================================================
680
+ /**
681
+ * Get native token balance for an address
682
+ */
683
+ async getNativeBalance(address) {
684
+ return await this.provider.getBalance(address);
685
+ }
686
+ /**
687
+ * Get ERC20 token balance for an address
688
+ */
689
+ async getTokenBalance(tokenAddress, ownerAddress) {
690
+ const contract = new import_ethers2.ethers.Contract(tokenAddress, ERC20_ABI, this.provider);
691
+ return await contract.balanceOf(ownerAddress);
692
+ }
693
+ /**
694
+ * Get token allowance
695
+ */
696
+ async getTokenAllowance(tokenAddress, ownerAddress, spenderAddress) {
697
+ const contract = new import_ethers2.ethers.Contract(tokenAddress, ERC20_ABI, this.provider);
698
+ return await contract.allowance(ownerAddress, spenderAddress);
699
+ }
700
+ /**
701
+ * Estimate gas for a dispatch transaction
702
+ */
703
+ async estimateDispatchGas(signature, publicKeyX, publicKeyY, targetChain, actionPayload, nonce) {
704
+ const signatureTuple = {
705
+ authenticatorData: signature.authenticatorData,
706
+ clientDataJSON: signature.clientDataJSON,
707
+ challengeIndex: signature.challengeIndex,
708
+ typeIndex: signature.typeIndex,
709
+ r: signature.r,
710
+ s: signature.s
711
+ };
712
+ const messageFee = await this.getMessageFee();
713
+ try {
714
+ const gasEstimate = await this.hubContract.dispatch.estimateGas(
715
+ signatureTuple,
716
+ publicKeyX,
717
+ publicKeyY,
718
+ targetChain,
719
+ actionPayload,
720
+ nonce,
721
+ { value: messageFee }
722
+ );
723
+ return gasEstimate;
724
+ } catch (error) {
725
+ console.warn("Gas estimation failed, using default:", error);
726
+ return 500000n;
727
+ }
728
+ }
729
+ /**
730
+ * Get current gas price
731
+ */
732
+ async getGasPrice() {
733
+ const feeData = await this.provider.getFeeData();
734
+ return feeData.gasPrice ?? feeData.maxFeePerGas ?? 0n;
735
+ }
736
+ /**
737
+ * Get current block number
738
+ */
739
+ async getBlockNumber() {
740
+ return await this.provider.getBlockNumber();
741
+ }
742
+ /**
743
+ * Get transaction receipt
744
+ */
745
+ async getTransactionReceipt(hash) {
746
+ return await this.provider.getTransactionReceipt(hash);
747
+ }
748
+ /**
749
+ * Wait for transaction confirmation
750
+ */
751
+ async waitForTransaction(hash, confirmations = 1) {
752
+ return await this.provider.waitForTransaction(hash, confirmations);
753
+ }
754
+ // ==========================================================================
755
+ // Backup Passkey / Multi-Key Identity Methods (Issue #22)
756
+ // ==========================================================================
757
+ /**
758
+ * Get the identity for a given key hash
759
+ * Returns zero hash if key is not registered to any identity
760
+ *
761
+ * @param keyHash Hash of the passkey to look up
762
+ * @returns Identity (first passkey's keyHash) or zero hash
763
+ */
764
+ async getIdentityForKey(keyHash) {
765
+ try {
766
+ return await this.hubContract.getIdentityForKey(keyHash);
767
+ } catch (error) {
768
+ return import_ethers2.ethers.ZeroHash;
769
+ }
770
+ }
771
+ /**
772
+ * Get all authorized keys for an identity
773
+ *
774
+ * @param identity The identity key hash (first passkey's keyHash)
775
+ * @returns Array of authorized key hashes
776
+ */
777
+ async getAuthorizedKeys(identity) {
778
+ try {
779
+ return await this.hubContract.getAuthorizedKeys(identity);
780
+ } catch (error) {
781
+ return [];
782
+ }
783
+ }
784
+ /**
785
+ * Get count of authorized keys for an identity
786
+ *
787
+ * @param identity The identity key hash
788
+ * @returns Number of authorized keys
789
+ */
790
+ async getAuthorizedKeyCount(identity) {
791
+ try {
792
+ const count = await this.hubContract.getAuthorizedKeyCount(identity);
793
+ return Number(count);
794
+ } catch (error) {
795
+ return 0;
796
+ }
797
+ }
798
+ /**
799
+ * Check if a key is authorized for an identity
800
+ *
801
+ * @param identity The identity key hash
802
+ * @param keyHash The key hash to check
803
+ * @returns Whether the key is authorized
804
+ */
805
+ async isAuthorizedForIdentity(identity, keyHash) {
806
+ try {
807
+ return await this.hubContract.isAuthorizedForIdentity(identity, keyHash);
808
+ } catch (error) {
809
+ return false;
810
+ }
811
+ }
812
+ /**
813
+ * Check if a key is the root identity key
814
+ *
815
+ * @param keyHash The key hash to check
816
+ * @returns Whether the key is a root identity
817
+ */
818
+ async isIdentityRootKey(keyHash) {
819
+ try {
820
+ return await this.hubContract.isIdentityRoot(keyHash);
821
+ } catch (error) {
822
+ return false;
823
+ }
824
+ }
825
+ /**
826
+ * Get comprehensive identity state for a key
827
+ *
828
+ * @param keyHash Hash of any key in the identity
829
+ * @returns Identity state including count, max, and root status
830
+ */
831
+ async getIdentityState(keyHash) {
832
+ try {
833
+ const result = await this.hubContract.getIdentityState(keyHash);
834
+ return {
835
+ identity: result[0],
836
+ keyCount: Number(result[1]),
837
+ maxKeys: Number(result[2]),
838
+ isRoot: result[3]
839
+ };
840
+ } catch (error) {
841
+ return {
842
+ identity: import_ethers2.ethers.ZeroHash,
843
+ keyCount: 0,
844
+ maxKeys: 5,
845
+ isRoot: false
846
+ };
847
+ }
848
+ }
849
+ /**
850
+ * Register a new identity with the first passkey
851
+ * This makes the passkey the root identity key
852
+ *
853
+ * @param signature WebAuthn signature
854
+ * @param publicKeyX Passkey public key X coordinate
855
+ * @param publicKeyY Passkey public key Y coordinate
856
+ * @param signer Ethereum signer to pay gas
857
+ * @returns Transaction receipt and identity hash
858
+ */
859
+ async registerIdentity(signature, publicKeyX, publicKeyY, signer) {
860
+ const hubWithSigner = this.hubContract.connect(signer);
861
+ const authTuple = {
862
+ authenticatorData: signature.authenticatorData,
863
+ clientDataJSON: signature.clientDataJSON,
864
+ challengeIndex: signature.challengeIndex,
865
+ typeIndex: signature.typeIndex,
866
+ r: signature.r,
867
+ s: signature.s
868
+ };
869
+ const tx = await hubWithSigner.registerIdentity(
870
+ authTuple,
871
+ publicKeyX,
872
+ publicKeyY
873
+ );
874
+ const receipt = await tx.wait();
875
+ const keyHash = import_ethers2.ethers.keccak256(
876
+ import_ethers2.ethers.solidityPacked(["uint256", "uint256"], [publicKeyX, publicKeyY])
877
+ );
878
+ return { receipt, identity: keyHash };
879
+ }
880
+ /**
881
+ * Add a backup passkey to an existing identity
882
+ * Requires WebAuthn signature from an authorized key
883
+ *
884
+ * @param signature WebAuthn signature from existing authorized key
885
+ * @param publicKeyX Existing key's X coordinate
886
+ * @param publicKeyY Existing key's Y coordinate
887
+ * @param newPublicKeyX New backup key's X coordinate
888
+ * @param newPublicKeyY New backup key's Y coordinate
889
+ * @param nonce Current nonce for the signing key
890
+ * @param signer Ethereum signer to pay gas
891
+ * @returns Transaction receipt and sequence number
892
+ */
893
+ async addBackupKey(signature, publicKeyX, publicKeyY, newPublicKeyX, newPublicKeyY, nonce, signer) {
894
+ const hubWithSigner = this.hubContract.connect(signer);
895
+ const authTuple = {
896
+ authenticatorData: signature.authenticatorData,
897
+ clientDataJSON: signature.clientDataJSON,
898
+ challengeIndex: signature.challengeIndex,
899
+ typeIndex: signature.typeIndex,
900
+ r: signature.r,
901
+ s: signature.s
902
+ };
903
+ const messageFee = await this.getMessageFee();
904
+ const tx = await hubWithSigner.addBackupKey(
905
+ authTuple,
906
+ publicKeyX,
907
+ publicKeyY,
908
+ newPublicKeyX,
909
+ newPublicKeyY,
910
+ nonce,
911
+ { value: messageFee }
912
+ );
913
+ const receipt = await tx.wait();
914
+ let sequence = 0n;
915
+ for (const log of receipt.logs) {
916
+ try {
917
+ const parsed = this.hubContract.interface.parseLog({
918
+ topics: log.topics,
919
+ data: log.data
920
+ });
921
+ if (parsed?.name === "Dispatched") {
922
+ sequence = BigInt(parsed.args[3]);
923
+ break;
924
+ }
925
+ } catch {
926
+ }
927
+ }
928
+ return { receipt, sequence };
929
+ }
930
+ /**
931
+ * Remove a passkey from an identity
932
+ * Cannot remove the last remaining key
933
+ *
934
+ * @param signature WebAuthn signature from an authorized key
935
+ * @param publicKeyX Signing key's X coordinate
936
+ * @param publicKeyY Signing key's Y coordinate
937
+ * @param keyToRemove Hash of the key to remove
938
+ * @param nonce Current nonce for the signing key
939
+ * @param signer Ethereum signer to pay gas
940
+ * @returns Transaction receipt and sequence number
941
+ */
942
+ async removeKey(signature, publicKeyX, publicKeyY, keyToRemove, nonce, signer) {
943
+ const hubWithSigner = this.hubContract.connect(signer);
944
+ const authTuple = {
945
+ authenticatorData: signature.authenticatorData,
946
+ clientDataJSON: signature.clientDataJSON,
947
+ challengeIndex: signature.challengeIndex,
948
+ typeIndex: signature.typeIndex,
949
+ r: signature.r,
950
+ s: signature.s
951
+ };
952
+ const messageFee = await this.getMessageFee();
953
+ const tx = await hubWithSigner.removeKey(
954
+ authTuple,
955
+ publicKeyX,
956
+ publicKeyY,
957
+ keyToRemove,
958
+ nonce,
959
+ { value: messageFee }
960
+ );
961
+ const receipt = await tx.wait();
962
+ let sequence = 0n;
963
+ for (const log of receipt.logs) {
964
+ try {
965
+ const parsed = this.hubContract.interface.parseLog({
966
+ topics: log.topics,
967
+ data: log.data
968
+ });
969
+ if (parsed?.name === "Dispatched") {
970
+ sequence = BigInt(parsed.args[3]);
971
+ break;
972
+ }
973
+ } catch {
974
+ }
975
+ }
976
+ return { receipt, sequence };
977
+ }
978
+ // =========================================================================
979
+ // SOCIAL RECOVERY METHODS (Issue #23)
980
+ // =========================================================================
981
+ /**
982
+ * Setup guardians for an identity
983
+ * @param signature WebAuthn signature from owner
984
+ * @param publicKeyX Owner's public key X coordinate
985
+ * @param publicKeyY Owner's public key Y coordinate
986
+ * @param guardians Array of guardian key hashes
987
+ * @param threshold Required approvals for recovery
988
+ * @param signer Ethers signer for transaction
989
+ */
990
+ async setupGuardians(signature, publicKeyX, publicKeyY, guardians, threshold, signer) {
991
+ const hubWithSigner = this.hubContract.connect(signer);
992
+ const authTuple = {
993
+ authenticatorData: signature.authenticatorData,
994
+ clientDataJSON: signature.clientDataJSON,
995
+ challengeIndex: signature.challengeIndex,
996
+ typeIndex: signature.typeIndex,
997
+ r: signature.r,
998
+ s: signature.s
999
+ };
1000
+ const messageFee = await this.getMessageFee();
1001
+ const tx = await hubWithSigner.setupGuardians(
1002
+ authTuple,
1003
+ publicKeyX,
1004
+ publicKeyY,
1005
+ guardians,
1006
+ threshold,
1007
+ { value: messageFee }
1008
+ );
1009
+ const receipt = await tx.wait();
1010
+ const sequence = this._extractSequenceFromReceipt(receipt);
1011
+ return { receipt, sequence };
1012
+ }
1013
+ /**
1014
+ * Add a guardian to an identity
1015
+ */
1016
+ async addGuardian(signature, publicKeyX, publicKeyY, guardianKeyHash, signer) {
1017
+ const hubWithSigner = this.hubContract.connect(signer);
1018
+ const authTuple = {
1019
+ authenticatorData: signature.authenticatorData,
1020
+ clientDataJSON: signature.clientDataJSON,
1021
+ challengeIndex: signature.challengeIndex,
1022
+ typeIndex: signature.typeIndex,
1023
+ r: signature.r,
1024
+ s: signature.s
1025
+ };
1026
+ const messageFee = await this.getMessageFee();
1027
+ const tx = await hubWithSigner.addGuardian(
1028
+ authTuple,
1029
+ publicKeyX,
1030
+ publicKeyY,
1031
+ guardianKeyHash,
1032
+ { value: messageFee }
1033
+ );
1034
+ const receipt = await tx.wait();
1035
+ const sequence = this._extractSequenceFromReceipt(receipt);
1036
+ return { receipt, sequence };
1037
+ }
1038
+ /**
1039
+ * Remove a guardian from an identity
1040
+ */
1041
+ async removeGuardian(signature, publicKeyX, publicKeyY, guardianKeyHash, signer) {
1042
+ const hubWithSigner = this.hubContract.connect(signer);
1043
+ const authTuple = {
1044
+ authenticatorData: signature.authenticatorData,
1045
+ clientDataJSON: signature.clientDataJSON,
1046
+ challengeIndex: signature.challengeIndex,
1047
+ typeIndex: signature.typeIndex,
1048
+ r: signature.r,
1049
+ s: signature.s
1050
+ };
1051
+ const messageFee = await this.getMessageFee();
1052
+ const tx = await hubWithSigner.removeGuardian(
1053
+ authTuple,
1054
+ publicKeyX,
1055
+ publicKeyY,
1056
+ guardianKeyHash,
1057
+ { value: messageFee }
1058
+ );
1059
+ const receipt = await tx.wait();
1060
+ const sequence = this._extractSequenceFromReceipt(receipt);
1061
+ return { receipt, sequence };
1062
+ }
1063
+ /**
1064
+ * Initiate recovery as a guardian
1065
+ */
1066
+ async initiateRecovery(signature, publicKeyX, publicKeyY, identityToRecover, newOwnerKeyHash, signer) {
1067
+ const hubWithSigner = this.hubContract.connect(signer);
1068
+ const authTuple = {
1069
+ authenticatorData: signature.authenticatorData,
1070
+ clientDataJSON: signature.clientDataJSON,
1071
+ challengeIndex: signature.challengeIndex,
1072
+ typeIndex: signature.typeIndex,
1073
+ r: signature.r,
1074
+ s: signature.s
1075
+ };
1076
+ const messageFee = await this.getMessageFee();
1077
+ const tx = await hubWithSigner.initiateRecovery(
1078
+ authTuple,
1079
+ publicKeyX,
1080
+ publicKeyY,
1081
+ identityToRecover,
1082
+ newOwnerKeyHash,
1083
+ { value: messageFee }
1084
+ );
1085
+ const receipt = await tx.wait();
1086
+ const sequence = this._extractSequenceFromReceipt(receipt);
1087
+ return { receipt, sequence };
1088
+ }
1089
+ /**
1090
+ * Approve recovery as a guardian
1091
+ */
1092
+ async approveRecovery(signature, publicKeyX, publicKeyY, identityToRecover, signer) {
1093
+ const hubWithSigner = this.hubContract.connect(signer);
1094
+ const authTuple = {
1095
+ authenticatorData: signature.authenticatorData,
1096
+ clientDataJSON: signature.clientDataJSON,
1097
+ challengeIndex: signature.challengeIndex,
1098
+ typeIndex: signature.typeIndex,
1099
+ r: signature.r,
1100
+ s: signature.s
1101
+ };
1102
+ const messageFee = await this.getMessageFee();
1103
+ const tx = await hubWithSigner.approveRecovery(
1104
+ authTuple,
1105
+ publicKeyX,
1106
+ publicKeyY,
1107
+ identityToRecover,
1108
+ { value: messageFee }
1109
+ );
1110
+ const receipt = await tx.wait();
1111
+ const sequence = this._extractSequenceFromReceipt(receipt);
1112
+ return { receipt, sequence };
1113
+ }
1114
+ /**
1115
+ * Execute recovery after timelock (anyone can call)
1116
+ */
1117
+ async executeRecovery(identityToRecover, newPublicKeyX, newPublicKeyY, signer) {
1118
+ const hubWithSigner = this.hubContract.connect(signer);
1119
+ const messageFee = await this.getMessageFee();
1120
+ const tx = await hubWithSigner.executeRecovery(
1121
+ identityToRecover,
1122
+ newPublicKeyX,
1123
+ newPublicKeyY,
1124
+ { value: messageFee }
1125
+ );
1126
+ const receipt = await tx.wait();
1127
+ const sequence = this._extractSequenceFromReceipt(receipt);
1128
+ return { receipt, sequence };
1129
+ }
1130
+ /**
1131
+ * Cancel recovery as owner
1132
+ */
1133
+ async cancelRecovery(signature, publicKeyX, publicKeyY, signer) {
1134
+ const hubWithSigner = this.hubContract.connect(signer);
1135
+ const authTuple = {
1136
+ authenticatorData: signature.authenticatorData,
1137
+ clientDataJSON: signature.clientDataJSON,
1138
+ challengeIndex: signature.challengeIndex,
1139
+ typeIndex: signature.typeIndex,
1140
+ r: signature.r,
1141
+ s: signature.s
1142
+ };
1143
+ const messageFee = await this.getMessageFee();
1144
+ const tx = await hubWithSigner.cancelRecovery(
1145
+ authTuple,
1146
+ publicKeyX,
1147
+ publicKeyY,
1148
+ { value: messageFee }
1149
+ );
1150
+ const receipt = await tx.wait();
1151
+ const sequence = this._extractSequenceFromReceipt(receipt);
1152
+ return { receipt, sequence };
1153
+ }
1154
+ /**
1155
+ * Get guardians for an identity
1156
+ */
1157
+ async getGuardians(identityKeyHash) {
1158
+ const result = await this.hubContract.getGuardians(identityKeyHash);
1159
+ return {
1160
+ guardians: result.guardians,
1161
+ threshold: result.threshold,
1162
+ isConfigured: result.isConfigured
1163
+ };
1164
+ }
1165
+ /**
1166
+ * Get recovery status for an identity
1167
+ */
1168
+ async getRecoveryStatus(identityKeyHash) {
1169
+ const result = await this.hubContract.getRecoveryStatus(identityKeyHash);
1170
+ return {
1171
+ isActive: result.isActive,
1172
+ newOwnerKeyHash: result.newOwnerKeyHash,
1173
+ initiatedAt: result.initiatedAt,
1174
+ approvalCount: result.approvalCount,
1175
+ threshold: result.threshold,
1176
+ canExecuteAt: result.canExecuteAt,
1177
+ expiresAt: result.expiresAt
1178
+ };
1179
+ }
1180
+ /**
1181
+ * Check if a guardian has approved recovery
1182
+ */
1183
+ async hasGuardianApproved(identityKeyHash, guardianKeyHash) {
1184
+ return this.hubContract.hasGuardianApproved(identityKeyHash, guardianKeyHash);
1185
+ }
1186
+ // ========================================================================
1187
+ // Threshold Multisig (ADR-0037)
1188
+ // ========================================================================
1189
+ async configureTransactionPolicy(signature, publicKeyX, publicKeyY, threshold, protectedActionMask, proposalTtl, disableSessions, signer) {
1190
+ const s = signer;
1191
+ const hub = this.hubContract.connect(s);
1192
+ const authTuple = this._toAuthTuple(signature);
1193
+ const tx = await hub.configureTransactionPolicy(
1194
+ authTuple,
1195
+ publicKeyX,
1196
+ publicKeyY,
1197
+ threshold,
1198
+ protectedActionMask,
1199
+ proposalTtl,
1200
+ disableSessions
1201
+ );
1202
+ const receipt = await tx.wait();
1203
+ return { receipt };
1204
+ }
1205
+ async getTransactionPolicy(identityKeyHash) {
1206
+ const result = await this.hubContract.getTransactionPolicy(identityKeyHash);
1207
+ return {
1208
+ enabled: result.enabled,
1209
+ threshold: Number(result.threshold),
1210
+ protectedActionMask: Number(result.protectedActionMask),
1211
+ proposalTtl: Number(result.proposalTtl),
1212
+ disableSessions: result.disableSessions
1213
+ };
1214
+ }
1215
+ async createTransactionProposal(signature, publicKeyX, publicKeyY, targetChain, actionPayload, signer) {
1216
+ const s = signer;
1217
+ const hub = this.hubContract.connect(s);
1218
+ const authTuple = this._toAuthTuple(signature);
1219
+ const tx = await hub.createTransactionProposal(
1220
+ authTuple,
1221
+ publicKeyX,
1222
+ publicKeyY,
1223
+ targetChain,
1224
+ actionPayload
1225
+ );
1226
+ const receipt = await tx.wait();
1227
+ let proposalId = import_ethers2.ethers.ZeroHash;
1228
+ for (const log of receipt.logs) {
1229
+ try {
1230
+ const parsed = this.hubContract.interface.parseLog({
1231
+ topics: log.topics,
1232
+ data: log.data
1233
+ });
1234
+ if (parsed?.name === "ProposalCreated") {
1235
+ proposalId = parsed.args.proposalId;
1236
+ break;
1237
+ }
1238
+ } catch {
1239
+ }
1240
+ }
1241
+ return { proposalId, receipt, sequence: 0n };
1242
+ }
1243
+ async approveTransactionProposal(signature, publicKeyX, publicKeyY, proposalId, signer) {
1244
+ const s = signer;
1245
+ const hub = this.hubContract.connect(s);
1246
+ const authTuple = this._toAuthTuple(signature);
1247
+ const tx = await hub.approveTransactionProposal(
1248
+ authTuple,
1249
+ publicKeyX,
1250
+ publicKeyY,
1251
+ proposalId
1252
+ );
1253
+ const receipt = await tx.wait();
1254
+ let approvalCount = 0;
1255
+ let thresholdReached = false;
1256
+ for (const log of receipt.logs) {
1257
+ try {
1258
+ const parsed = this.hubContract.interface.parseLog({
1259
+ topics: log.topics,
1260
+ data: log.data
1261
+ });
1262
+ if (parsed?.name === "ProposalApproved") {
1263
+ approvalCount = Number(parsed.args.currentApprovals);
1264
+ thresholdReached = Number(parsed.args.currentApprovals) >= Number(parsed.args.requiredThreshold);
1265
+ break;
1266
+ }
1267
+ } catch {
1268
+ }
1269
+ }
1270
+ return { receipt, approvalCount, thresholdReached };
1271
+ }
1272
+ async cancelTransactionProposal(signature, publicKeyX, publicKeyY, proposalId, signer) {
1273
+ const s = signer;
1274
+ const hub = this.hubContract.connect(s);
1275
+ const authTuple = this._toAuthTuple(signature);
1276
+ const tx = await hub.cancelTransactionProposal(
1277
+ authTuple,
1278
+ publicKeyX,
1279
+ publicKeyY,
1280
+ proposalId
1281
+ );
1282
+ const receipt = await tx.wait();
1283
+ return { receipt };
1284
+ }
1285
+ async executeTransactionProposal(proposalId, signer) {
1286
+ const s = signer;
1287
+ const hub = this.hubContract.connect(s);
1288
+ const fee = await this.hubContract.getMessageFee();
1289
+ const tx = await hub.executeTransactionProposal(proposalId, { value: fee });
1290
+ const receipt = await tx.wait();
1291
+ const sequence = this._extractSequenceFromReceipt(receipt);
1292
+ return { receipt, sequence };
1293
+ }
1294
+ async getTransactionProposal(proposalId) {
1295
+ const result = await this.hubContract.getTransactionProposal(proposalId);
1296
+ return {
1297
+ identityKeyHash: result.identityKeyHash,
1298
+ proposerKeyHash: result.proposerKeyHash,
1299
+ targetChain: Number(result.targetChain),
1300
+ actionType: Number(result.actionType),
1301
+ actionHash: result.actionHash,
1302
+ createdAt: result.createdAt,
1303
+ expiresAt: result.expiresAt,
1304
+ approvalCount: Number(result.approvalCount),
1305
+ requiredThreshold: Number(result.requiredThreshold),
1306
+ state: Number(result.state)
1307
+ };
1308
+ }
1309
+ async hasApprovedTransactionProposal(proposalId, keyHash) {
1310
+ return this.hubContract.hasApprovedTransactionProposal(proposalId, keyHash);
1311
+ }
1312
+ /**
1313
+ * Convert a WebAuthnSignature to the struct tuple expected by the Hub contract
1314
+ */
1315
+ _toAuthTuple(sig) {
1316
+ return {
1317
+ authenticatorData: sig.authenticatorData,
1318
+ clientDataJSON: sig.clientDataJSON,
1319
+ challengeIndex: sig.challengeIndex,
1320
+ typeIndex: sig.typeIndex,
1321
+ r: sig.r,
1322
+ s: sig.s
1323
+ };
1324
+ }
1325
+ /**
1326
+ * Helper to extract sequence from transaction receipt
1327
+ */
1328
+ _extractSequenceFromReceipt(receipt) {
1329
+ for (const log of receipt.logs) {
1330
+ try {
1331
+ const parsed = this.hubContract.interface.parseLog({
1332
+ topics: log.topics,
1333
+ data: log.data
1334
+ });
1335
+ if (parsed?.name === "Dispatch") {
1336
+ return BigInt(parsed.args.sequence);
1337
+ }
1338
+ } catch {
1339
+ }
1340
+ }
1341
+ return 0n;
1342
+ }
1343
+ };
1344
+
1345
+ // src/chains/avalanche/AvalancheClient.ts
1346
+ var ACP204_PRECOMPILE = "0x0000000000000000000000000000000000000100";
1347
+ var CHAINLINK_AGGREGATOR_ABI = [
1348
+ "function latestRoundData() view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)",
1349
+ "function decimals() view returns (uint8)"
1350
+ ];
1351
+ var ICM_SPOKE_ABI = [
1352
+ "function verifySession(bytes32 sessionKeyHash, uint256 amount) view returns (bool valid, uint256 remainingBudget)",
1353
+ "function getSession(bytes32 sessionKeyHash) view returns (bytes32 userKeyHash, uint256 expiry, uint256 maxValue, uint256 totalBudget, uint256 spent, bool active)",
1354
+ "function getStatus() view returns (bool paused, uint256 totalMessages, uint256 totalSessions, uint256 totalPayments)",
1355
+ "function isKeyAuthorized(bytes32 identityKeyHash, bytes32 keyHash) view returns (bool)"
1356
+ ];
1357
+ var P256_VERIFIER_ABI = [
1358
+ "function isPrecompileAvailable() view returns (bool available)",
1359
+ "function computeKeyHash(uint256 x, uint256 y) view returns (bytes32)"
1360
+ ];
1361
+ var AvalancheClient = class extends EVMClient {
1362
+ avaxProvider;
1363
+ p256VerifierAddress;
1364
+ icmSpokeAddress;
1365
+ chainlinkAvaxUsdFeed;
1366
+ chainlinkUsdcUsdFeed;
1367
+ chainlinkUsdtUsdFeed;
1368
+ // Price cache (avoid excessive RPC calls)
1369
+ priceCache = /* @__PURE__ */ new Map();
1370
+ CACHE_TTL_MS = 3e4;
1371
+ // 30 seconds
1372
+ constructor(config) {
1373
+ super(config);
1374
+ this.avaxProvider = new import_ethers3.ethers.JsonRpcProvider(config.rpcUrl);
1375
+ this.p256VerifierAddress = config.p256VerifierAddress || "";
1376
+ this.icmSpokeAddress = config.icmSpokeAddress || "";
1377
+ this.chainlinkAvaxUsdFeed = config.chainlinkAvaxUsdFeed || "";
1378
+ this.chainlinkUsdcUsdFeed = config.chainlinkUsdcUsdFeed || "";
1379
+ this.chainlinkUsdtUsdFeed = config.chainlinkUsdtUsdFeed || "";
1380
+ }
1381
+ // ========================================================================
1382
+ // ACP-204 Precompile Utilities
1383
+ // ========================================================================
1384
+ /**
1385
+ * Check if the ACP-204 secp256r1 precompile is live on this chain.
1386
+ * Returns true on Avalanche C-Chain (mainnet + Fuji), false elsewhere.
1387
+ */
1388
+ async isACP204Available() {
1389
+ if (this.p256VerifierAddress) {
1390
+ try {
1391
+ const verifier = new import_ethers3.ethers.Contract(
1392
+ this.p256VerifierAddress,
1393
+ P256_VERIFIER_ABI,
1394
+ this.avaxProvider
1395
+ );
1396
+ return await verifier.isPrecompileAvailable();
1397
+ } catch {
1398
+ }
1399
+ }
1400
+ try {
1401
+ const zeroInput = new Uint8Array(160);
1402
+ const result = await this.avaxProvider.call({
1403
+ to: ACP204_PRECOMPILE,
1404
+ data: import_ethers3.ethers.hexlify(zeroInput)
1405
+ });
1406
+ return result.length === 66;
1407
+ } catch {
1408
+ return false;
1409
+ }
1410
+ }
1411
+ /**
1412
+ * Get the estimated gas cost (in wei) for a single P-256 verification.
1413
+ * Deterministic on Avalanche: 6,900 gas for precompile + ~300 staticcall overhead.
1414
+ */
1415
+ async estimatePasskeyVerificationGas() {
1416
+ const feeData = await this.avaxProvider.getFeeData();
1417
+ const gasPrice = feeData.gasPrice || import_ethers3.ethers.parseUnits("25", "gwei");
1418
+ return 7200n * gasPrice;
1419
+ }
1420
+ /**
1421
+ * Get estimated USD cost for a passkey verification.
1422
+ */
1423
+ async estimatePasskeyVerificationCostUSD() {
1424
+ const gasCostWei = await this.estimatePasskeyVerificationGas();
1425
+ return this.convertAvaxToUsd(gasCostWei);
1426
+ }
1427
+ // ========================================================================
1428
+ // Chainlink Price Feeds
1429
+ // ========================================================================
1430
+ /**
1431
+ * Get current AVAX/USD price from Chainlink.
1432
+ * Cached for 30 seconds to avoid excessive RPC calls.
1433
+ */
1434
+ async getAvaxPriceUSD() {
1435
+ return this._getChainlinkPrice(this.chainlinkAvaxUsdFeed, "avax-usd");
1436
+ }
1437
+ /**
1438
+ * Get USDC/USD price (for stablecoin verification).
1439
+ */
1440
+ async getUsdcPriceUSD() {
1441
+ if (!this.chainlinkUsdcUsdFeed) return 1;
1442
+ return this._getChainlinkPrice(this.chainlinkUsdcUsdFeed, "usdc-usd");
1443
+ }
1444
+ /**
1445
+ * Get USDT/USD price.
1446
+ */
1447
+ async getUsdtPriceUSD() {
1448
+ if (!this.chainlinkUsdtUsdFeed) return 1;
1449
+ return this._getChainlinkPrice(this.chainlinkUsdtUsdFeed, "usdt-usd");
1450
+ }
1451
+ /**
1452
+ * Convert a USD amount to AVAX wei using live Chainlink prices.
1453
+ */
1454
+ async convertUsdToAvax(usdAmount) {
1455
+ const avaxPrice = await this.getAvaxPriceUSD();
1456
+ if (avaxPrice <= 0) throw new Error("Invalid AVAX price from Chainlink");
1457
+ const avaxAmount = usdAmount / avaxPrice;
1458
+ return import_ethers3.ethers.parseEther(avaxAmount.toFixed(18));
1459
+ }
1460
+ /**
1461
+ * Convert AVAX wei to USD using live Chainlink prices.
1462
+ */
1463
+ async convertAvaxToUsd(avaxWei) {
1464
+ const avaxPrice = await this.getAvaxPriceUSD();
1465
+ return Number(import_ethers3.ethers.formatEther(avaxWei)) * avaxPrice;
1466
+ }
1467
+ // ========================================================================
1468
+ // ICM Spoke Queries
1469
+ // ========================================================================
1470
+ /**
1471
+ * Verify a session is valid on the ICM Spoke (cross-L1 verification).
1472
+ */
1473
+ async verifyICMSession(sessionKeyHash, amount) {
1474
+ if (!this.icmSpokeAddress) {
1475
+ throw new Error("ICM Spoke address not configured");
1476
+ }
1477
+ const spoke = new import_ethers3.ethers.Contract(this.icmSpokeAddress, ICM_SPOKE_ABI, this.avaxProvider);
1478
+ const [valid, remainingBudget] = await spoke.verifySession(sessionKeyHash, amount);
1479
+ return { valid, remainingBudget: BigInt(remainingBudget) };
1480
+ }
1481
+ /**
1482
+ * Get status of the ICM Spoke (paused, message count, session count).
1483
+ */
1484
+ async getICMSpokeStatus() {
1485
+ if (!this.icmSpokeAddress) {
1486
+ throw new Error("ICM Spoke address not configured");
1487
+ }
1488
+ const spoke = new import_ethers3.ethers.Contract(this.icmSpokeAddress, ICM_SPOKE_ABI, this.avaxProvider);
1489
+ const [paused, totalMessages, totalSessions, totalPayments] = await spoke.getStatus();
1490
+ return {
1491
+ paused,
1492
+ totalMessages: BigInt(totalMessages),
1493
+ totalSessions: BigInt(totalSessions),
1494
+ totalPayments: BigInt(totalPayments)
1495
+ };
1496
+ }
1497
+ /**
1498
+ * Check if a key is authorized for an identity on the ICM Spoke.
1499
+ */
1500
+ async isKeyAuthorizedOnSpoke(identityKeyHash, keyHash) {
1501
+ if (!this.icmSpokeAddress) return false;
1502
+ const spoke = new import_ethers3.ethers.Contract(this.icmSpokeAddress, ICM_SPOKE_ABI, this.avaxProvider);
1503
+ return spoke.isKeyAuthorized(identityKeyHash, keyHash);
1504
+ }
1505
+ // ========================================================================
1506
+ // ICM-Aware Routing
1507
+ // ========================================================================
1508
+ /**
1509
+ * Determine whether a cross-chain message should use Teleporter (ICM) or Wormhole.
1510
+ *
1511
+ * Rule: If the target chain is within the Avalanche ecosystem (C-Chain ID or
1512
+ * an Avalanche L1), use ICM/Teleporter for lower latency and no guardian overhead.
1513
+ * Otherwise, fall back to Wormhole VAAs for cross-ecosystem messaging.
1514
+ *
1515
+ * @param targetWormholeChainId Wormhole chain ID of the destination
1516
+ * @returns 'icm' | 'wormhole'
1517
+ */
1518
+ getRoutingStrategy(targetWormholeChainId) {
1519
+ const avalancheEcosystemChainIds = /* @__PURE__ */ new Set([6]);
1520
+ return avalancheEcosystemChainIds.has(targetWormholeChainId) ? "icm" : "wormhole";
1521
+ }
1522
+ // ========================================================================
1523
+ // Accessors
1524
+ // ========================================================================
1525
+ getP256VerifierAddress() {
1526
+ return this.p256VerifierAddress;
1527
+ }
1528
+ getICMSpokeAddress() {
1529
+ return this.icmSpokeAddress;
1530
+ }
1531
+ getChainlinkAvaxUsdFeed() {
1532
+ return this.chainlinkAvaxUsdFeed;
1533
+ }
1534
+ // ========================================================================
1535
+ // Private Helpers
1536
+ // ========================================================================
1537
+ async _getChainlinkPrice(feedAddress, cacheKey) {
1538
+ if (!feedAddress) throw new Error(`Chainlink feed not configured for ${cacheKey}`);
1539
+ const cached = this.priceCache.get(cacheKey);
1540
+ if (cached && Date.now() - cached.timestamp < this.CACHE_TTL_MS) {
1541
+ return cached.price;
1542
+ }
1543
+ const aggregator = new import_ethers3.ethers.Contract(feedAddress, CHAINLINK_AGGREGATOR_ABI, this.avaxProvider);
1544
+ const [, answer] = await aggregator.latestRoundData();
1545
+ const decimals = await aggregator.decimals();
1546
+ const price = Number(answer) / 10 ** Number(decimals);
1547
+ this.priceCache.set(cacheKey, { price, timestamp: Date.now() });
1548
+ return price;
1549
+ }
1550
+ };
1551
+ // Annotate the CommonJS export names for ESM import in node:
1552
+ 0 && (module.exports = {
1553
+ AvalancheClient
1554
+ });
1555
+ //# sourceMappingURL=index.js.map