@veridex/sdk 1.0.0-beta.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/CHANGELOG.md +73 -0
- package/LICENSE +21 -0
- package/README.md +212 -0
- package/dist/chains/aptos/index.d.mts +140 -0
- package/dist/chains/aptos/index.d.ts +140 -0
- package/dist/chains/aptos/index.js +563 -0
- package/dist/chains/aptos/index.js.map +1 -0
- package/dist/chains/aptos/index.mjs +536 -0
- package/dist/chains/aptos/index.mjs.map +1 -0
- package/dist/chains/evm/index.d.mts +5 -0
- package/dist/chains/evm/index.d.ts +5 -0
- package/dist/chains/evm/index.js +1233 -0
- package/dist/chains/evm/index.js.map +1 -0
- package/dist/chains/evm/index.mjs +1205 -0
- package/dist/chains/evm/index.mjs.map +1 -0
- package/dist/chains/solana/index.d.mts +116 -0
- package/dist/chains/solana/index.d.ts +116 -0
- package/dist/chains/solana/index.js +513 -0
- package/dist/chains/solana/index.js.map +1 -0
- package/dist/chains/solana/index.mjs +491 -0
- package/dist/chains/solana/index.mjs.map +1 -0
- package/dist/chains/starknet/index.d.mts +172 -0
- package/dist/chains/starknet/index.d.ts +172 -0
- package/dist/chains/starknet/index.js +534 -0
- package/dist/chains/starknet/index.js.map +1 -0
- package/dist/chains/starknet/index.mjs +507 -0
- package/dist/chains/starknet/index.mjs.map +1 -0
- package/dist/chains/sui/index.d.mts +182 -0
- package/dist/chains/sui/index.d.ts +182 -0
- package/dist/chains/sui/index.js +560 -0
- package/dist/chains/sui/index.js.map +1 -0
- package/dist/chains/sui/index.mjs +533 -0
- package/dist/chains/sui/index.mjs.map +1 -0
- package/dist/constants.d.mts +150 -0
- package/dist/constants.d.ts +150 -0
- package/dist/constants.js +430 -0
- package/dist/constants.js.map +1 -0
- package/dist/constants.mjs +392 -0
- package/dist/constants.mjs.map +1 -0
- package/dist/index-0NXfbk0z.d.ts +637 -0
- package/dist/index-D0dLVjTA.d.mts +637 -0
- package/dist/index.d.mts +3101 -0
- package/dist/index.d.ts +3101 -0
- package/dist/index.js +13186 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +13011 -0
- package/dist/index.mjs.map +1 -0
- package/dist/payload.d.mts +125 -0
- package/dist/payload.d.ts +125 -0
- package/dist/payload.js +315 -0
- package/dist/payload.js.map +1 -0
- package/dist/payload.mjs +269 -0
- package/dist/payload.mjs.map +1 -0
- package/dist/queries/index.d.mts +148 -0
- package/dist/queries/index.d.ts +148 -0
- package/dist/queries/index.js +1533 -0
- package/dist/queries/index.js.map +1 -0
- package/dist/queries/index.mjs +1508 -0
- package/dist/queries/index.mjs.map +1 -0
- package/dist/types-ChIsqCiw.d.mts +565 -0
- package/dist/types-ChIsqCiw.d.ts +565 -0
- package/dist/types-FJL7j6gQ.d.mts +172 -0
- package/dist/types-FJL7j6gQ.d.ts +172 -0
- package/dist/types.d.mts +407 -0
- package/dist/types.d.ts +407 -0
- package/dist/types.js +19 -0
- package/dist/types.js.map +1 -0
- package/dist/types.mjs +1 -0
- package/dist/types.mjs.map +1 -0
- package/dist/utils.d.mts +81 -0
- package/dist/utils.d.ts +81 -0
- package/dist/utils.js +430 -0
- package/dist/utils.js.map +1 -0
- package/dist/utils.mjs +390 -0
- package/dist/utils.mjs.map +1 -0
- package/dist/wormhole.d.mts +167 -0
- package/dist/wormhole.d.ts +167 -0
- package/dist/wormhole.js +468 -0
- package/dist/wormhole.js.map +1 -0
- package/dist/wormhole.mjs +422 -0
- package/dist/wormhole.mjs.map +1 -0
- package/package.json +151 -0
|
@@ -0,0 +1,1205 @@
|
|
|
1
|
+
// src/chains/evm/EVMClient.ts
|
|
2
|
+
import { ethers as ethers2 } from "ethers";
|
|
3
|
+
|
|
4
|
+
// src/payload.ts
|
|
5
|
+
import { ethers } from "ethers";
|
|
6
|
+
|
|
7
|
+
// src/constants.ts
|
|
8
|
+
var ACTION_TRANSFER = 1;
|
|
9
|
+
var ACTION_EXECUTE = 2;
|
|
10
|
+
var ACTION_BRIDGE = 4;
|
|
11
|
+
|
|
12
|
+
// src/payload.ts
|
|
13
|
+
function encodeTransferAction(token, recipient, amount) {
|
|
14
|
+
const tokenPadded = padTo32Bytes(token);
|
|
15
|
+
const recipientPadded = padTo32Bytes(recipient);
|
|
16
|
+
const amountBytes = ethers.zeroPadValue(ethers.toBeHex(amount), 32);
|
|
17
|
+
return ethers.concat([
|
|
18
|
+
ethers.toBeHex(ACTION_TRANSFER, 1),
|
|
19
|
+
tokenPadded,
|
|
20
|
+
recipientPadded,
|
|
21
|
+
amountBytes
|
|
22
|
+
]);
|
|
23
|
+
}
|
|
24
|
+
function encodeBridgeAction(token, amount, targetChain, recipient) {
|
|
25
|
+
const tokenPadded = padTo32Bytes(token);
|
|
26
|
+
const amountBytes = ethers.zeroPadValue(ethers.toBeHex(amount), 32);
|
|
27
|
+
const targetChainBytes = ethers.toBeHex(targetChain, 2);
|
|
28
|
+
const recipientPadded = padTo32Bytes(recipient);
|
|
29
|
+
return ethers.concat([
|
|
30
|
+
ethers.toBeHex(ACTION_BRIDGE, 1),
|
|
31
|
+
tokenPadded,
|
|
32
|
+
amountBytes,
|
|
33
|
+
targetChainBytes,
|
|
34
|
+
recipientPadded
|
|
35
|
+
]);
|
|
36
|
+
}
|
|
37
|
+
function encodeExecuteAction(target, value, data) {
|
|
38
|
+
const targetPadded = padTo32Bytes(target);
|
|
39
|
+
const valueBytes = ethers.zeroPadValue(ethers.toBeHex(value), 32);
|
|
40
|
+
const dataBytes = ethers.getBytes(data);
|
|
41
|
+
const dataLengthBytes = ethers.toBeHex(dataBytes.length, 2);
|
|
42
|
+
return ethers.concat([
|
|
43
|
+
ethers.toBeHex(ACTION_EXECUTE, 1),
|
|
44
|
+
targetPadded,
|
|
45
|
+
valueBytes,
|
|
46
|
+
dataLengthBytes,
|
|
47
|
+
data
|
|
48
|
+
]);
|
|
49
|
+
}
|
|
50
|
+
function padTo32Bytes(address) {
|
|
51
|
+
if (address.toLowerCase() === "native") {
|
|
52
|
+
return "0x" + "0".repeat(64);
|
|
53
|
+
}
|
|
54
|
+
if (address.startsWith("0x")) {
|
|
55
|
+
const hex2 = address.replace("0x", "");
|
|
56
|
+
if (!/^[0-9a-fA-F]*$/.test(hex2)) {
|
|
57
|
+
throw new Error(`Invalid address: ${address}. Expected hex string or 'native'.`);
|
|
58
|
+
}
|
|
59
|
+
return "0x" + hex2.padStart(64, "0");
|
|
60
|
+
}
|
|
61
|
+
const base58Chars = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
62
|
+
for (const char of address) {
|
|
63
|
+
if (!base58Chars.includes(char)) {
|
|
64
|
+
throw new Error(`Invalid address: ${address}. Contains invalid base58 character '${char}'.`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
let value = BigInt(0);
|
|
68
|
+
for (const char of address) {
|
|
69
|
+
value = value * 58n + BigInt(base58Chars.indexOf(char));
|
|
70
|
+
}
|
|
71
|
+
let hex = value.toString(16);
|
|
72
|
+
if (hex.length > 64) {
|
|
73
|
+
throw new Error(`Invalid address: ${address}. Decoded value too large for 32 bytes.`);
|
|
74
|
+
}
|
|
75
|
+
return "0x" + hex.padStart(64, "0");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// src/chains/evm/EVMClient.ts
|
|
79
|
+
var PROXY_BYTECODE_PREFIX = "0x3d602d80600a3d3981f3363d3d373d3d3d363d73";
|
|
80
|
+
var PROXY_BYTECODE_SUFFIX = "5af43d82803e903d91602b57fd5bf3";
|
|
81
|
+
var ERC20_ABI = [
|
|
82
|
+
"function balanceOf(address owner) view returns (uint256)",
|
|
83
|
+
"function decimals() view returns (uint8)",
|
|
84
|
+
"function symbol() view returns (string)",
|
|
85
|
+
"function name() view returns (string)",
|
|
86
|
+
"function allowance(address owner, address spender) view returns (uint256)",
|
|
87
|
+
"function transfer(address to, uint256 amount) returns (bool)",
|
|
88
|
+
"function approve(address spender, uint256 amount) returns (bool)"
|
|
89
|
+
];
|
|
90
|
+
var HUB_ABI = [
|
|
91
|
+
"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)",
|
|
92
|
+
"function getNonce(bytes32 userKeyHash) view returns (uint256)",
|
|
93
|
+
"function getMessageFee() view returns (uint256)",
|
|
94
|
+
"function getVaultAddress(bytes32 userKeyHash) view returns (address)",
|
|
95
|
+
"function vaultExists(bytes32 userKeyHash) view returns (bool)",
|
|
96
|
+
"function createVault(bytes32 userKeyHash) returns (address)",
|
|
97
|
+
// Issue #9/#10: New Hub methods for Query-based execution
|
|
98
|
+
"function getUserState(bytes32 userKeyHash) view returns (bytes32 keyHash, uint256 nonce, bytes32 lastActionHash)",
|
|
99
|
+
"function getUserLastActionHash(bytes32 userKeyHash) view returns (bytes32)",
|
|
100
|
+
// Issue #13: Session key management
|
|
101
|
+
"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",
|
|
102
|
+
"function isSessionActive(bytes32 userKeyHash, bytes32 sessionKeyHash) view returns (bool active, uint256 expiry, uint256 maxValue, uint256 sessionIndex)",
|
|
103
|
+
"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",
|
|
104
|
+
"function getUserSessions(bytes32 userKeyHash) view returns (tuple(bytes32 sessionKeyHash, uint256 expiry, uint256 maxValue, bool revoked)[])",
|
|
105
|
+
"function getUserSessionCount(bytes32 userKeyHash) view returns (uint256)",
|
|
106
|
+
// Issue #22: Backup Passkey / Multi-Key Identity management
|
|
107
|
+
"function registerIdentity(tuple(bytes authenticatorData, string clientDataJSON, uint256 challengeIndex, uint256 typeIndex, uint256 r, uint256 s) auth, uint256 publicKeyX, uint256 publicKeyY) external",
|
|
108
|
+
"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)",
|
|
109
|
+
"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)",
|
|
110
|
+
"function getIdentityForKey(bytes32 keyHash) view returns (bytes32)",
|
|
111
|
+
"function getAuthorizedKeys(bytes32 identity) view returns (bytes32[])",
|
|
112
|
+
"function getAuthorizedKeyCount(bytes32 identity) view returns (uint256)",
|
|
113
|
+
"function isAuthorizedForIdentity(bytes32 identity, bytes32 keyHash) view returns (bool)",
|
|
114
|
+
"function isIdentityRoot(bytes32 keyHash) view returns (bool)",
|
|
115
|
+
"function getIdentityState(bytes32 keyHash) view returns (bytes32 identity, uint256 keyCount, uint256 maxKeys, bool isRoot)",
|
|
116
|
+
// Issue #23: Social Recovery / Guardian Management
|
|
117
|
+
"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)",
|
|
118
|
+
"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)",
|
|
119
|
+
"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)",
|
|
120
|
+
"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)",
|
|
121
|
+
"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)",
|
|
122
|
+
"function executeRecovery(bytes32 identityToRecover, uint256 newPublicKeyX, uint256 newPublicKeyY) external payable returns (uint64 sequence)",
|
|
123
|
+
"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)",
|
|
124
|
+
"function getGuardians(bytes32 identityKeyHash) view returns (bytes32[] guardians, uint256 threshold, bool isConfigured)",
|
|
125
|
+
"function getRecoveryStatus(bytes32 identityKeyHash) view returns (bool isActive, bytes32 newOwnerKeyHash, uint256 initiatedAt, uint256 approvalCount, uint256 threshold, uint256 canExecuteAt, uint256 expiresAt)",
|
|
126
|
+
"function hasGuardianApproved(bytes32 identityKeyHash, bytes32 guardianKeyHash) view returns (bool hasApproved)"
|
|
127
|
+
];
|
|
128
|
+
var FACTORY_ABI = [
|
|
129
|
+
"function createVault(bytes32 ownerKeyHash) returns (address vault)",
|
|
130
|
+
"function getVault(bytes32 ownerKeyHash) view returns (address)",
|
|
131
|
+
"function computeVaultAddress(bytes32 ownerKeyHash) view returns (address vault)",
|
|
132
|
+
"function vaultExists(bytes32 ownerKeyHash) view returns (bool)",
|
|
133
|
+
"function implementation() view returns (address)"
|
|
134
|
+
];
|
|
135
|
+
var EVMClient = class {
|
|
136
|
+
config;
|
|
137
|
+
provider;
|
|
138
|
+
hubContract;
|
|
139
|
+
factoryContract = null;
|
|
140
|
+
cachedImplementation = null;
|
|
141
|
+
constructor(config) {
|
|
142
|
+
this.config = {
|
|
143
|
+
name: config.name ?? `EVM Chain ${config.chainId}`,
|
|
144
|
+
chainId: config.chainId,
|
|
145
|
+
wormholeChainId: config.wormholeChainId,
|
|
146
|
+
rpcUrl: config.rpcUrl,
|
|
147
|
+
explorerUrl: config.explorerUrl ?? "",
|
|
148
|
+
isEvm: true,
|
|
149
|
+
contracts: {
|
|
150
|
+
hub: config.hubContractAddress,
|
|
151
|
+
vaultFactory: config.vaultFactory,
|
|
152
|
+
vaultImplementation: config.vaultImplementation,
|
|
153
|
+
wormholeCoreBridge: config.wormholeCoreBridge,
|
|
154
|
+
tokenBridge: config.tokenBridge
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
this.provider = new ethers2.JsonRpcProvider(config.rpcUrl);
|
|
158
|
+
this.hubContract = new ethers2.Contract(
|
|
159
|
+
config.hubContractAddress,
|
|
160
|
+
HUB_ABI,
|
|
161
|
+
this.provider
|
|
162
|
+
);
|
|
163
|
+
if (config.vaultFactory) {
|
|
164
|
+
this.factoryContract = new ethers2.Contract(
|
|
165
|
+
config.vaultFactory,
|
|
166
|
+
FACTORY_ABI,
|
|
167
|
+
this.provider
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
if (config.vaultImplementation) {
|
|
171
|
+
this.cachedImplementation = config.vaultImplementation;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
getConfig() {
|
|
175
|
+
return this.config;
|
|
176
|
+
}
|
|
177
|
+
async getNonce(userKeyHash) {
|
|
178
|
+
const nonce = await this.hubContract.getNonce(userKeyHash);
|
|
179
|
+
return BigInt(nonce.toString());
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Get user state from Hub (Issue #9/#10)
|
|
183
|
+
* Returns comprehensive state including last action hash
|
|
184
|
+
*/
|
|
185
|
+
async getUserState(userKeyHash) {
|
|
186
|
+
try {
|
|
187
|
+
const result = await this.hubContract.getUserState(userKeyHash);
|
|
188
|
+
return {
|
|
189
|
+
keyHash: result[0],
|
|
190
|
+
nonce: BigInt(result[1].toString()),
|
|
191
|
+
lastActionHash: result[2]
|
|
192
|
+
};
|
|
193
|
+
} catch (error) {
|
|
194
|
+
const nonce = await this.getNonce(userKeyHash);
|
|
195
|
+
return {
|
|
196
|
+
keyHash: userKeyHash,
|
|
197
|
+
nonce,
|
|
198
|
+
lastActionHash: ethers2.ZeroHash
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Get user's last action hash from Hub (Issue #9/#10)
|
|
204
|
+
* Returns zero hash if user has no actions yet
|
|
205
|
+
*/
|
|
206
|
+
async getUserLastActionHash(userKeyHash) {
|
|
207
|
+
try {
|
|
208
|
+
return await this.hubContract.getUserLastActionHash(userKeyHash);
|
|
209
|
+
} catch (error) {
|
|
210
|
+
return ethers2.ZeroHash;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// ==========================================================================
|
|
214
|
+
// Session Management Methods (Issue #13)
|
|
215
|
+
// ==========================================================================
|
|
216
|
+
/**
|
|
217
|
+
* Register a new session key for temporary authentication
|
|
218
|
+
* Enables native L1 speed for repeat transactions without biometric auth
|
|
219
|
+
*
|
|
220
|
+
* @param params Session registration parameters
|
|
221
|
+
* @param signer Ethereum signer to pay gas
|
|
222
|
+
* @returns Transaction receipt
|
|
223
|
+
*/
|
|
224
|
+
async registerSession(params, signer) {
|
|
225
|
+
const hubWithSigner = this.hubContract.connect(signer);
|
|
226
|
+
const authTuple = {
|
|
227
|
+
authenticatorData: params.signature.authenticatorData,
|
|
228
|
+
clientDataJSON: params.signature.clientDataJSON,
|
|
229
|
+
challengeIndex: params.signature.challengeIndex,
|
|
230
|
+
typeIndex: params.signature.typeIndex,
|
|
231
|
+
r: params.signature.r,
|
|
232
|
+
s: params.signature.s
|
|
233
|
+
};
|
|
234
|
+
const tx = await hubWithSigner.registerSession(
|
|
235
|
+
authTuple,
|
|
236
|
+
params.publicKeyX,
|
|
237
|
+
params.publicKeyY,
|
|
238
|
+
params.sessionKeyHash,
|
|
239
|
+
params.duration,
|
|
240
|
+
params.maxValue,
|
|
241
|
+
params.requireUV
|
|
242
|
+
);
|
|
243
|
+
return await tx.wait();
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Check if a session is currently active (queryable via Wormhole CCQ)
|
|
247
|
+
*
|
|
248
|
+
* @param userKeyHash Hash of the user's Passkey public key
|
|
249
|
+
* @param sessionKeyHash Hash of the session key to check
|
|
250
|
+
* @returns Session validation result
|
|
251
|
+
*/
|
|
252
|
+
async isSessionActive(userKeyHash, sessionKeyHash) {
|
|
253
|
+
const result = await this.hubContract.isSessionActive(userKeyHash, sessionKeyHash);
|
|
254
|
+
return {
|
|
255
|
+
active: result[0],
|
|
256
|
+
expiry: Number(result[1]),
|
|
257
|
+
maxValue: BigInt(result[2].toString()),
|
|
258
|
+
sessionIndex: Number(result[3])
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Revoke a session key immediately
|
|
263
|
+
*
|
|
264
|
+
* @param params Session revocation parameters
|
|
265
|
+
* @param signer Ethereum signer to pay gas
|
|
266
|
+
* @returns Transaction receipt
|
|
267
|
+
*/
|
|
268
|
+
async revokeSession(params, signer) {
|
|
269
|
+
const hubWithSigner = this.hubContract.connect(signer);
|
|
270
|
+
const authTuple = {
|
|
271
|
+
authenticatorData: params.signature.authenticatorData,
|
|
272
|
+
clientDataJSON: params.signature.clientDataJSON,
|
|
273
|
+
challengeIndex: params.signature.challengeIndex,
|
|
274
|
+
typeIndex: params.signature.typeIndex,
|
|
275
|
+
r: params.signature.r,
|
|
276
|
+
s: params.signature.s
|
|
277
|
+
};
|
|
278
|
+
const tx = await hubWithSigner.revokeSession(
|
|
279
|
+
authTuple,
|
|
280
|
+
params.publicKeyX,
|
|
281
|
+
params.publicKeyY,
|
|
282
|
+
params.sessionKeyHash,
|
|
283
|
+
params.requireUV
|
|
284
|
+
);
|
|
285
|
+
return await tx.wait();
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Get all sessions for a user
|
|
289
|
+
*
|
|
290
|
+
* @param userKeyHash Hash of the user's Passkey public key
|
|
291
|
+
* @returns Array of all sessions (active and expired/revoked)
|
|
292
|
+
*/
|
|
293
|
+
async getUserSessions(userKeyHash) {
|
|
294
|
+
const sessions = await this.hubContract.getUserSessions(userKeyHash);
|
|
295
|
+
return sessions.map((s) => ({
|
|
296
|
+
sessionKeyHash: s.sessionKeyHash,
|
|
297
|
+
expiry: Number(s.expiry),
|
|
298
|
+
maxValue: BigInt(s.maxValue.toString()),
|
|
299
|
+
revoked: s.revoked
|
|
300
|
+
}));
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Get the number of sessions for a user
|
|
304
|
+
*
|
|
305
|
+
* @param userKeyHash Hash of the user's Passkey public key
|
|
306
|
+
* @returns Number of sessions
|
|
307
|
+
*/
|
|
308
|
+
async getUserSessionCount(userKeyHash) {
|
|
309
|
+
const count = await this.hubContract.getUserSessionCount(userKeyHash);
|
|
310
|
+
return Number(count);
|
|
311
|
+
}
|
|
312
|
+
async getMessageFee() {
|
|
313
|
+
const fee = await this.hubContract.getMessageFee();
|
|
314
|
+
return BigInt(fee.toString());
|
|
315
|
+
}
|
|
316
|
+
async buildTransferPayload(params) {
|
|
317
|
+
return encodeTransferAction(
|
|
318
|
+
params.token,
|
|
319
|
+
params.recipient,
|
|
320
|
+
params.amount
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
async buildExecutePayload(params) {
|
|
324
|
+
return encodeExecuteAction(
|
|
325
|
+
params.target,
|
|
326
|
+
params.value,
|
|
327
|
+
params.data
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
async buildBridgePayload(params) {
|
|
331
|
+
return encodeBridgeAction(
|
|
332
|
+
params.token,
|
|
333
|
+
params.amount,
|
|
334
|
+
params.destinationChain,
|
|
335
|
+
params.recipient
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
async dispatch(signature, publicKeyX, publicKeyY, targetChain, actionPayload, nonce, signer) {
|
|
339
|
+
const hubWithSigner = this.hubContract.connect(signer);
|
|
340
|
+
const signatureTuple = {
|
|
341
|
+
authenticatorData: signature.authenticatorData,
|
|
342
|
+
clientDataJSON: signature.clientDataJSON,
|
|
343
|
+
challengeIndex: signature.challengeIndex,
|
|
344
|
+
typeIndex: signature.typeIndex,
|
|
345
|
+
r: signature.r,
|
|
346
|
+
s: signature.s
|
|
347
|
+
};
|
|
348
|
+
const messageFee = await this.getMessageFee();
|
|
349
|
+
const tx = await hubWithSigner.dispatch(
|
|
350
|
+
signatureTuple,
|
|
351
|
+
publicKeyX,
|
|
352
|
+
publicKeyY,
|
|
353
|
+
targetChain,
|
|
354
|
+
actionPayload,
|
|
355
|
+
nonce,
|
|
356
|
+
{ value: messageFee }
|
|
357
|
+
);
|
|
358
|
+
const receipt = await tx.wait();
|
|
359
|
+
const dispatchEvent = receipt.logs.find((log) => {
|
|
360
|
+
try {
|
|
361
|
+
const parsed = hubWithSigner.interface.parseLog(log);
|
|
362
|
+
return parsed?.name === "ActionDispatched";
|
|
363
|
+
} catch {
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
let sequence = 0n;
|
|
368
|
+
if (dispatchEvent) {
|
|
369
|
+
const parsed = hubWithSigner.interface.parseLog(dispatchEvent);
|
|
370
|
+
sequence = BigInt(parsed?.args?.sequence?.toString() ?? "0");
|
|
371
|
+
}
|
|
372
|
+
const keyHash = ethers2.keccak256(
|
|
373
|
+
ethers2.AbiCoder.defaultAbiCoder().encode(
|
|
374
|
+
["uint256", "uint256"],
|
|
375
|
+
[publicKeyX, publicKeyY]
|
|
376
|
+
)
|
|
377
|
+
);
|
|
378
|
+
return {
|
|
379
|
+
transactionHash: receipt.hash,
|
|
380
|
+
sequence,
|
|
381
|
+
userKeyHash: keyHash,
|
|
382
|
+
targetChain,
|
|
383
|
+
blockNumber: receipt.blockNumber
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Dispatch an action to the Hub via relayer (gasless)
|
|
388
|
+
* The relayer pays for gas and submits the transaction on behalf of the user
|
|
389
|
+
*/
|
|
390
|
+
async dispatchGasless(signature, publicKeyX, publicKeyY, targetChain, actionPayload, nonce, relayerUrl) {
|
|
391
|
+
const keyHash = ethers2.keccak256(
|
|
392
|
+
ethers2.AbiCoder.defaultAbiCoder().encode(
|
|
393
|
+
["uint256", "uint256"],
|
|
394
|
+
[publicKeyX, publicKeyY]
|
|
395
|
+
)
|
|
396
|
+
);
|
|
397
|
+
const message = ethers2.keccak256(
|
|
398
|
+
ethers2.AbiCoder.defaultAbiCoder().encode(
|
|
399
|
+
["bytes32", "uint16", "bytes", "uint256"],
|
|
400
|
+
[keyHash, targetChain, actionPayload, nonce]
|
|
401
|
+
)
|
|
402
|
+
);
|
|
403
|
+
const request = {
|
|
404
|
+
messageHash: message,
|
|
405
|
+
r: "0x" + signature.r.toString(16).padStart(64, "0"),
|
|
406
|
+
s: "0x" + signature.s.toString(16).padStart(64, "0"),
|
|
407
|
+
publicKeyX: "0x" + publicKeyX.toString(16).padStart(64, "0"),
|
|
408
|
+
publicKeyY: "0x" + publicKeyY.toString(16).padStart(64, "0"),
|
|
409
|
+
targetChain,
|
|
410
|
+
actionPayload,
|
|
411
|
+
nonce: Number(nonce)
|
|
412
|
+
};
|
|
413
|
+
const response = await fetch(`${relayerUrl}/api/v1/submit`, {
|
|
414
|
+
method: "POST",
|
|
415
|
+
headers: {
|
|
416
|
+
"Content-Type": "application/json"
|
|
417
|
+
},
|
|
418
|
+
body: JSON.stringify(request)
|
|
419
|
+
});
|
|
420
|
+
if (!response.ok) {
|
|
421
|
+
const error = await response.json().catch(() => ({ error: response.statusText }));
|
|
422
|
+
throw new Error(`Relayer submission failed: ${error.error || response.statusText}`);
|
|
423
|
+
}
|
|
424
|
+
const result = await response.json();
|
|
425
|
+
if (!result.success) {
|
|
426
|
+
throw new Error(`Relayer submission failed: ${result.error}`);
|
|
427
|
+
}
|
|
428
|
+
return {
|
|
429
|
+
transactionHash: result.txHash,
|
|
430
|
+
sequence: BigInt(result.sequence || "0"),
|
|
431
|
+
userKeyHash: keyHash,
|
|
432
|
+
targetChain
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
async getVaultAddress(userKeyHash) {
|
|
436
|
+
try {
|
|
437
|
+
if (this.factoryContract) {
|
|
438
|
+
const address2 = await this.factoryContract.getVault(userKeyHash);
|
|
439
|
+
if (address2 !== ethers2.ZeroAddress) {
|
|
440
|
+
return address2;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
const address = await this.hubContract.getVaultAddress(userKeyHash);
|
|
444
|
+
if (address === ethers2.ZeroAddress) {
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
return address;
|
|
448
|
+
} catch (error) {
|
|
449
|
+
console.error("Error getting vault address:", error);
|
|
450
|
+
return null;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Compute vault address deterministically without querying the chain
|
|
455
|
+
* Uses CREATE2 with EIP-1167 minimal proxy pattern
|
|
456
|
+
*/
|
|
457
|
+
computeVaultAddress(userKeyHash) {
|
|
458
|
+
const factoryAddress = this.getFactoryAddress();
|
|
459
|
+
const implementationAddress = this.getImplementationAddress();
|
|
460
|
+
if (!factoryAddress || !implementationAddress) {
|
|
461
|
+
throw new Error("Factory and implementation addresses required for address computation");
|
|
462
|
+
}
|
|
463
|
+
const salt = ethers2.keccak256(
|
|
464
|
+
ethers2.solidityPacked(
|
|
465
|
+
["address", "bytes32"],
|
|
466
|
+
[factoryAddress, userKeyHash]
|
|
467
|
+
)
|
|
468
|
+
);
|
|
469
|
+
const initCode = this.buildProxyInitCode(implementationAddress);
|
|
470
|
+
const initCodeHash = ethers2.keccak256(initCode);
|
|
471
|
+
const create2Data = ethers2.solidityPacked(
|
|
472
|
+
["bytes1", "address", "bytes32", "bytes32"],
|
|
473
|
+
["0xff", factoryAddress, salt, initCodeHash]
|
|
474
|
+
);
|
|
475
|
+
const hash = ethers2.keccak256(create2Data);
|
|
476
|
+
return ethers2.getAddress("0x" + hash.slice(26));
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Build EIP-1167 minimal proxy initcode
|
|
480
|
+
*/
|
|
481
|
+
buildProxyInitCode(implementationAddress) {
|
|
482
|
+
const impl = implementationAddress.toLowerCase().replace("0x", "");
|
|
483
|
+
return PROXY_BYTECODE_PREFIX + impl + PROXY_BYTECODE_SUFFIX;
|
|
484
|
+
}
|
|
485
|
+
async vaultExists(userKeyHash) {
|
|
486
|
+
try {
|
|
487
|
+
if (this.factoryContract) {
|
|
488
|
+
return await this.factoryContract.vaultExists(userKeyHash);
|
|
489
|
+
}
|
|
490
|
+
if (this.hubContract.vaultExists) {
|
|
491
|
+
try {
|
|
492
|
+
return await this.hubContract.vaultExists(userKeyHash);
|
|
493
|
+
} catch {
|
|
494
|
+
return false;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return false;
|
|
498
|
+
} catch {
|
|
499
|
+
return false;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
async createVault(userKeyHash, signer) {
|
|
503
|
+
const exists = await this.vaultExists(userKeyHash);
|
|
504
|
+
if (exists) {
|
|
505
|
+
const address = await this.getVaultAddress(userKeyHash);
|
|
506
|
+
if (address) {
|
|
507
|
+
return {
|
|
508
|
+
address,
|
|
509
|
+
transactionHash: "",
|
|
510
|
+
blockNumber: 0,
|
|
511
|
+
gasUsed: 0n,
|
|
512
|
+
alreadyExisted: true
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
let tx;
|
|
517
|
+
if (this.factoryContract) {
|
|
518
|
+
const factoryWithSigner = this.factoryContract.connect(signer);
|
|
519
|
+
tx = await factoryWithSigner.createVault(userKeyHash);
|
|
520
|
+
} else {
|
|
521
|
+
const hubWithSigner = this.hubContract.connect(signer);
|
|
522
|
+
tx = await hubWithSigner.createVault(userKeyHash);
|
|
523
|
+
}
|
|
524
|
+
const receipt = await tx.wait();
|
|
525
|
+
if (!receipt) {
|
|
526
|
+
throw new Error("Transaction failed - no receipt");
|
|
527
|
+
}
|
|
528
|
+
const vaultAddress = await this.getVaultAddress(userKeyHash);
|
|
529
|
+
if (!vaultAddress) {
|
|
530
|
+
throw new Error("Failed to create vault - address not found after creation");
|
|
531
|
+
}
|
|
532
|
+
return {
|
|
533
|
+
address: vaultAddress,
|
|
534
|
+
transactionHash: receipt.hash,
|
|
535
|
+
blockNumber: receipt.blockNumber,
|
|
536
|
+
gasUsed: receipt.gasUsed,
|
|
537
|
+
alreadyExisted: false
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Create a vault with a sponsor wallet paying for gas
|
|
542
|
+
*
|
|
543
|
+
* @param userKeyHash - The user's passkey hash
|
|
544
|
+
* @param sponsorPrivateKey - Private key of the wallet that will pay gas
|
|
545
|
+
* @param rpcUrl - Optional RPC URL to use (defaults to client's RPC)
|
|
546
|
+
* @returns VaultCreationResult with address and transaction details
|
|
547
|
+
*/
|
|
548
|
+
async createVaultSponsored(userKeyHash, sponsorPrivateKey, rpcUrl) {
|
|
549
|
+
const exists = await this.vaultExists(userKeyHash);
|
|
550
|
+
if (exists) {
|
|
551
|
+
const address = await this.getVaultAddress(userKeyHash);
|
|
552
|
+
if (address) {
|
|
553
|
+
return {
|
|
554
|
+
address,
|
|
555
|
+
transactionHash: "",
|
|
556
|
+
blockNumber: 0,
|
|
557
|
+
gasUsed: 0n,
|
|
558
|
+
alreadyExisted: true
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
const provider = rpcUrl ? new ethers2.JsonRpcProvider(rpcUrl) : this.provider;
|
|
563
|
+
const sponsorWallet = new ethers2.Wallet(sponsorPrivateKey, provider);
|
|
564
|
+
const sponsorBalance = await provider.getBalance(sponsorWallet.address);
|
|
565
|
+
const estimatedGas = await this.estimateVaultCreationGas(userKeyHash);
|
|
566
|
+
const feeData = await provider.getFeeData();
|
|
567
|
+
const estimatedCost = estimatedGas * (feeData.gasPrice ?? 1000000000n);
|
|
568
|
+
if (sponsorBalance < estimatedCost) {
|
|
569
|
+
throw new Error(
|
|
570
|
+
`Sponsor wallet has insufficient funds. Balance: ${ethers2.formatEther(sponsorBalance)} ETH, Estimated cost: ${ethers2.formatEther(estimatedCost)} ETH`
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
let tx;
|
|
574
|
+
if (this.factoryContract) {
|
|
575
|
+
const factoryWithSponsor = this.factoryContract.connect(sponsorWallet);
|
|
576
|
+
tx = await factoryWithSponsor.createVault(userKeyHash);
|
|
577
|
+
} else {
|
|
578
|
+
const hubWithSponsor = this.hubContract.connect(sponsorWallet);
|
|
579
|
+
tx = await hubWithSponsor.createVault(userKeyHash);
|
|
580
|
+
}
|
|
581
|
+
const receipt = await tx.wait();
|
|
582
|
+
if (!receipt) {
|
|
583
|
+
throw new Error("Transaction failed - no receipt");
|
|
584
|
+
}
|
|
585
|
+
const vaultAddress = await this.getVaultAddress(userKeyHash);
|
|
586
|
+
if (!vaultAddress) {
|
|
587
|
+
throw new Error("Failed to create vault - address not found after creation");
|
|
588
|
+
}
|
|
589
|
+
return {
|
|
590
|
+
address: vaultAddress,
|
|
591
|
+
transactionHash: receipt.hash,
|
|
592
|
+
blockNumber: receipt.blockNumber,
|
|
593
|
+
gasUsed: receipt.gasUsed,
|
|
594
|
+
alreadyExisted: false,
|
|
595
|
+
sponsoredBy: sponsorWallet.address
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
async estimateVaultCreationGas(userKeyHash) {
|
|
599
|
+
try {
|
|
600
|
+
if (this.factoryContract) {
|
|
601
|
+
return await this.factoryContract.createVault.estimateGas(userKeyHash);
|
|
602
|
+
}
|
|
603
|
+
return await this.hubContract.createVault.estimateGas(userKeyHash);
|
|
604
|
+
} catch (error) {
|
|
605
|
+
console.warn("Gas estimation failed, returning default:", error);
|
|
606
|
+
return 150000n;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
getFactoryAddress() {
|
|
610
|
+
return this.config.contracts.vaultFactory;
|
|
611
|
+
}
|
|
612
|
+
getImplementationAddress() {
|
|
613
|
+
return this.config.contracts.vaultImplementation ?? this.cachedImplementation ?? void 0;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Fetch implementation address from factory contract
|
|
617
|
+
*/
|
|
618
|
+
async fetchImplementationAddress() {
|
|
619
|
+
if (this.cachedImplementation) {
|
|
620
|
+
return this.cachedImplementation;
|
|
621
|
+
}
|
|
622
|
+
if (!this.factoryContract) {
|
|
623
|
+
return null;
|
|
624
|
+
}
|
|
625
|
+
try {
|
|
626
|
+
this.cachedImplementation = await this.factoryContract.implementation();
|
|
627
|
+
return this.cachedImplementation;
|
|
628
|
+
} catch (error) {
|
|
629
|
+
console.error("Error fetching implementation address:", error);
|
|
630
|
+
return null;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Get the provider instance
|
|
635
|
+
*/
|
|
636
|
+
getProvider() {
|
|
637
|
+
return this.provider;
|
|
638
|
+
}
|
|
639
|
+
// ========================================================================
|
|
640
|
+
// Balance Methods (Phase 2)
|
|
641
|
+
// ========================================================================
|
|
642
|
+
/**
|
|
643
|
+
* Get native token balance for an address
|
|
644
|
+
*/
|
|
645
|
+
async getNativeBalance(address) {
|
|
646
|
+
return await this.provider.getBalance(address);
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Get ERC20 token balance for an address
|
|
650
|
+
*/
|
|
651
|
+
async getTokenBalance(tokenAddress, ownerAddress) {
|
|
652
|
+
const contract = new ethers2.Contract(tokenAddress, ERC20_ABI, this.provider);
|
|
653
|
+
return await contract.balanceOf(ownerAddress);
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Get token allowance
|
|
657
|
+
*/
|
|
658
|
+
async getTokenAllowance(tokenAddress, ownerAddress, spenderAddress) {
|
|
659
|
+
const contract = new ethers2.Contract(tokenAddress, ERC20_ABI, this.provider);
|
|
660
|
+
return await contract.allowance(ownerAddress, spenderAddress);
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Estimate gas for a dispatch transaction
|
|
664
|
+
*/
|
|
665
|
+
async estimateDispatchGas(signature, publicKeyX, publicKeyY, targetChain, actionPayload, nonce) {
|
|
666
|
+
const signatureTuple = {
|
|
667
|
+
authenticatorData: signature.authenticatorData,
|
|
668
|
+
clientDataJSON: signature.clientDataJSON,
|
|
669
|
+
challengeIndex: signature.challengeIndex,
|
|
670
|
+
typeIndex: signature.typeIndex,
|
|
671
|
+
r: signature.r,
|
|
672
|
+
s: signature.s
|
|
673
|
+
};
|
|
674
|
+
const messageFee = await this.getMessageFee();
|
|
675
|
+
try {
|
|
676
|
+
const gasEstimate = await this.hubContract.dispatch.estimateGas(
|
|
677
|
+
signatureTuple,
|
|
678
|
+
publicKeyX,
|
|
679
|
+
publicKeyY,
|
|
680
|
+
targetChain,
|
|
681
|
+
actionPayload,
|
|
682
|
+
nonce,
|
|
683
|
+
{ value: messageFee }
|
|
684
|
+
);
|
|
685
|
+
return gasEstimate;
|
|
686
|
+
} catch (error) {
|
|
687
|
+
console.warn("Gas estimation failed, using default:", error);
|
|
688
|
+
return 500000n;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Get current gas price
|
|
693
|
+
*/
|
|
694
|
+
async getGasPrice() {
|
|
695
|
+
const feeData = await this.provider.getFeeData();
|
|
696
|
+
return feeData.gasPrice ?? feeData.maxFeePerGas ?? 0n;
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Get current block number
|
|
700
|
+
*/
|
|
701
|
+
async getBlockNumber() {
|
|
702
|
+
return await this.provider.getBlockNumber();
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Get transaction receipt
|
|
706
|
+
*/
|
|
707
|
+
async getTransactionReceipt(hash) {
|
|
708
|
+
return await this.provider.getTransactionReceipt(hash);
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Wait for transaction confirmation
|
|
712
|
+
*/
|
|
713
|
+
async waitForTransaction(hash, confirmations = 1) {
|
|
714
|
+
return await this.provider.waitForTransaction(hash, confirmations);
|
|
715
|
+
}
|
|
716
|
+
// ==========================================================================
|
|
717
|
+
// Backup Passkey / Multi-Key Identity Methods (Issue #22)
|
|
718
|
+
// ==========================================================================
|
|
719
|
+
/**
|
|
720
|
+
* Get the identity for a given key hash
|
|
721
|
+
* Returns zero hash if key is not registered to any identity
|
|
722
|
+
*
|
|
723
|
+
* @param keyHash Hash of the passkey to look up
|
|
724
|
+
* @returns Identity (first passkey's keyHash) or zero hash
|
|
725
|
+
*/
|
|
726
|
+
async getIdentityForKey(keyHash) {
|
|
727
|
+
try {
|
|
728
|
+
return await this.hubContract.getIdentityForKey(keyHash);
|
|
729
|
+
} catch (error) {
|
|
730
|
+
return ethers2.ZeroHash;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
/**
|
|
734
|
+
* Get all authorized keys for an identity
|
|
735
|
+
*
|
|
736
|
+
* @param identity The identity key hash (first passkey's keyHash)
|
|
737
|
+
* @returns Array of authorized key hashes
|
|
738
|
+
*/
|
|
739
|
+
async getAuthorizedKeys(identity) {
|
|
740
|
+
try {
|
|
741
|
+
return await this.hubContract.getAuthorizedKeys(identity);
|
|
742
|
+
} catch (error) {
|
|
743
|
+
return [];
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Get count of authorized keys for an identity
|
|
748
|
+
*
|
|
749
|
+
* @param identity The identity key hash
|
|
750
|
+
* @returns Number of authorized keys
|
|
751
|
+
*/
|
|
752
|
+
async getAuthorizedKeyCount(identity) {
|
|
753
|
+
try {
|
|
754
|
+
const count = await this.hubContract.getAuthorizedKeyCount(identity);
|
|
755
|
+
return Number(count);
|
|
756
|
+
} catch (error) {
|
|
757
|
+
return 0;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Check if a key is authorized for an identity
|
|
762
|
+
*
|
|
763
|
+
* @param identity The identity key hash
|
|
764
|
+
* @param keyHash The key hash to check
|
|
765
|
+
* @returns Whether the key is authorized
|
|
766
|
+
*/
|
|
767
|
+
async isAuthorizedForIdentity(identity, keyHash) {
|
|
768
|
+
try {
|
|
769
|
+
return await this.hubContract.isAuthorizedForIdentity(identity, keyHash);
|
|
770
|
+
} catch (error) {
|
|
771
|
+
return false;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Check if a key is the root identity key
|
|
776
|
+
*
|
|
777
|
+
* @param keyHash The key hash to check
|
|
778
|
+
* @returns Whether the key is a root identity
|
|
779
|
+
*/
|
|
780
|
+
async isIdentityRootKey(keyHash) {
|
|
781
|
+
try {
|
|
782
|
+
return await this.hubContract.isIdentityRoot(keyHash);
|
|
783
|
+
} catch (error) {
|
|
784
|
+
return false;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Get comprehensive identity state for a key
|
|
789
|
+
*
|
|
790
|
+
* @param keyHash Hash of any key in the identity
|
|
791
|
+
* @returns Identity state including count, max, and root status
|
|
792
|
+
*/
|
|
793
|
+
async getIdentityState(keyHash) {
|
|
794
|
+
try {
|
|
795
|
+
const result = await this.hubContract.getIdentityState(keyHash);
|
|
796
|
+
return {
|
|
797
|
+
identity: result[0],
|
|
798
|
+
keyCount: Number(result[1]),
|
|
799
|
+
maxKeys: Number(result[2]),
|
|
800
|
+
isRoot: result[3]
|
|
801
|
+
};
|
|
802
|
+
} catch (error) {
|
|
803
|
+
return {
|
|
804
|
+
identity: ethers2.ZeroHash,
|
|
805
|
+
keyCount: 0,
|
|
806
|
+
maxKeys: 5,
|
|
807
|
+
isRoot: false
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* Register a new identity with the first passkey
|
|
813
|
+
* This makes the passkey the root identity key
|
|
814
|
+
*
|
|
815
|
+
* @param signature WebAuthn signature
|
|
816
|
+
* @param publicKeyX Passkey public key X coordinate
|
|
817
|
+
* @param publicKeyY Passkey public key Y coordinate
|
|
818
|
+
* @param signer Ethereum signer to pay gas
|
|
819
|
+
* @returns Transaction receipt and identity hash
|
|
820
|
+
*/
|
|
821
|
+
async registerIdentity(signature, publicKeyX, publicKeyY, signer) {
|
|
822
|
+
const hubWithSigner = this.hubContract.connect(signer);
|
|
823
|
+
const authTuple = {
|
|
824
|
+
authenticatorData: signature.authenticatorData,
|
|
825
|
+
clientDataJSON: signature.clientDataJSON,
|
|
826
|
+
challengeIndex: signature.challengeIndex,
|
|
827
|
+
typeIndex: signature.typeIndex,
|
|
828
|
+
r: signature.r,
|
|
829
|
+
s: signature.s
|
|
830
|
+
};
|
|
831
|
+
const tx = await hubWithSigner.registerIdentity(
|
|
832
|
+
authTuple,
|
|
833
|
+
publicKeyX,
|
|
834
|
+
publicKeyY
|
|
835
|
+
);
|
|
836
|
+
const receipt = await tx.wait();
|
|
837
|
+
const keyHash = ethers2.keccak256(
|
|
838
|
+
ethers2.solidityPacked(["uint256", "uint256"], [publicKeyX, publicKeyY])
|
|
839
|
+
);
|
|
840
|
+
return { receipt, identity: keyHash };
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Add a backup passkey to an existing identity
|
|
844
|
+
* Requires WebAuthn signature from an authorized key
|
|
845
|
+
*
|
|
846
|
+
* @param signature WebAuthn signature from existing authorized key
|
|
847
|
+
* @param publicKeyX Existing key's X coordinate
|
|
848
|
+
* @param publicKeyY Existing key's Y coordinate
|
|
849
|
+
* @param newPublicKeyX New backup key's X coordinate
|
|
850
|
+
* @param newPublicKeyY New backup key's Y coordinate
|
|
851
|
+
* @param nonce Current nonce for the signing key
|
|
852
|
+
* @param signer Ethereum signer to pay gas
|
|
853
|
+
* @returns Transaction receipt and sequence number
|
|
854
|
+
*/
|
|
855
|
+
async addBackupKey(signature, publicKeyX, publicKeyY, newPublicKeyX, newPublicKeyY, nonce, signer) {
|
|
856
|
+
const hubWithSigner = this.hubContract.connect(signer);
|
|
857
|
+
const authTuple = {
|
|
858
|
+
authenticatorData: signature.authenticatorData,
|
|
859
|
+
clientDataJSON: signature.clientDataJSON,
|
|
860
|
+
challengeIndex: signature.challengeIndex,
|
|
861
|
+
typeIndex: signature.typeIndex,
|
|
862
|
+
r: signature.r,
|
|
863
|
+
s: signature.s
|
|
864
|
+
};
|
|
865
|
+
const messageFee = await this.getMessageFee();
|
|
866
|
+
const tx = await hubWithSigner.addBackupKey(
|
|
867
|
+
authTuple,
|
|
868
|
+
publicKeyX,
|
|
869
|
+
publicKeyY,
|
|
870
|
+
newPublicKeyX,
|
|
871
|
+
newPublicKeyY,
|
|
872
|
+
nonce,
|
|
873
|
+
{ value: messageFee }
|
|
874
|
+
);
|
|
875
|
+
const receipt = await tx.wait();
|
|
876
|
+
let sequence = 0n;
|
|
877
|
+
for (const log of receipt.logs) {
|
|
878
|
+
try {
|
|
879
|
+
const parsed = this.hubContract.interface.parseLog({
|
|
880
|
+
topics: log.topics,
|
|
881
|
+
data: log.data
|
|
882
|
+
});
|
|
883
|
+
if (parsed?.name === "Dispatched") {
|
|
884
|
+
sequence = BigInt(parsed.args[3]);
|
|
885
|
+
break;
|
|
886
|
+
}
|
|
887
|
+
} catch {
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
return { receipt, sequence };
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Remove a passkey from an identity
|
|
894
|
+
* Cannot remove the last remaining key
|
|
895
|
+
*
|
|
896
|
+
* @param signature WebAuthn signature from an authorized key
|
|
897
|
+
* @param publicKeyX Signing key's X coordinate
|
|
898
|
+
* @param publicKeyY Signing key's Y coordinate
|
|
899
|
+
* @param keyToRemove Hash of the key to remove
|
|
900
|
+
* @param nonce Current nonce for the signing key
|
|
901
|
+
* @param signer Ethereum signer to pay gas
|
|
902
|
+
* @returns Transaction receipt and sequence number
|
|
903
|
+
*/
|
|
904
|
+
async removeKey(signature, publicKeyX, publicKeyY, keyToRemove, nonce, signer) {
|
|
905
|
+
const hubWithSigner = this.hubContract.connect(signer);
|
|
906
|
+
const authTuple = {
|
|
907
|
+
authenticatorData: signature.authenticatorData,
|
|
908
|
+
clientDataJSON: signature.clientDataJSON,
|
|
909
|
+
challengeIndex: signature.challengeIndex,
|
|
910
|
+
typeIndex: signature.typeIndex,
|
|
911
|
+
r: signature.r,
|
|
912
|
+
s: signature.s
|
|
913
|
+
};
|
|
914
|
+
const messageFee = await this.getMessageFee();
|
|
915
|
+
const tx = await hubWithSigner.removeKey(
|
|
916
|
+
authTuple,
|
|
917
|
+
publicKeyX,
|
|
918
|
+
publicKeyY,
|
|
919
|
+
keyToRemove,
|
|
920
|
+
nonce,
|
|
921
|
+
{ value: messageFee }
|
|
922
|
+
);
|
|
923
|
+
const receipt = await tx.wait();
|
|
924
|
+
let sequence = 0n;
|
|
925
|
+
for (const log of receipt.logs) {
|
|
926
|
+
try {
|
|
927
|
+
const parsed = this.hubContract.interface.parseLog({
|
|
928
|
+
topics: log.topics,
|
|
929
|
+
data: log.data
|
|
930
|
+
});
|
|
931
|
+
if (parsed?.name === "Dispatched") {
|
|
932
|
+
sequence = BigInt(parsed.args[3]);
|
|
933
|
+
break;
|
|
934
|
+
}
|
|
935
|
+
} catch {
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
return { receipt, sequence };
|
|
939
|
+
}
|
|
940
|
+
// =========================================================================
|
|
941
|
+
// SOCIAL RECOVERY METHODS (Issue #23)
|
|
942
|
+
// =========================================================================
|
|
943
|
+
/**
|
|
944
|
+
* Setup guardians for an identity
|
|
945
|
+
* @param signature WebAuthn signature from owner
|
|
946
|
+
* @param publicKeyX Owner's public key X coordinate
|
|
947
|
+
* @param publicKeyY Owner's public key Y coordinate
|
|
948
|
+
* @param guardians Array of guardian key hashes
|
|
949
|
+
* @param threshold Required approvals for recovery
|
|
950
|
+
* @param signer Ethers signer for transaction
|
|
951
|
+
*/
|
|
952
|
+
async setupGuardians(signature, publicKeyX, publicKeyY, guardians, threshold, signer) {
|
|
953
|
+
const hubWithSigner = this.hubContract.connect(signer);
|
|
954
|
+
const authTuple = {
|
|
955
|
+
authenticatorData: signature.authenticatorData,
|
|
956
|
+
clientDataJSON: signature.clientDataJSON,
|
|
957
|
+
challengeIndex: signature.challengeIndex,
|
|
958
|
+
typeIndex: signature.typeIndex,
|
|
959
|
+
r: signature.r,
|
|
960
|
+
s: signature.s
|
|
961
|
+
};
|
|
962
|
+
const messageFee = await this.getMessageFee();
|
|
963
|
+
const tx = await hubWithSigner.setupGuardians(
|
|
964
|
+
authTuple,
|
|
965
|
+
publicKeyX,
|
|
966
|
+
publicKeyY,
|
|
967
|
+
guardians,
|
|
968
|
+
threshold,
|
|
969
|
+
{ value: messageFee }
|
|
970
|
+
);
|
|
971
|
+
const receipt = await tx.wait();
|
|
972
|
+
const sequence = this._extractSequenceFromReceipt(receipt);
|
|
973
|
+
return { receipt, sequence };
|
|
974
|
+
}
|
|
975
|
+
/**
|
|
976
|
+
* Add a guardian to an identity
|
|
977
|
+
*/
|
|
978
|
+
async addGuardian(signature, publicKeyX, publicKeyY, guardianKeyHash, signer) {
|
|
979
|
+
const hubWithSigner = this.hubContract.connect(signer);
|
|
980
|
+
const authTuple = {
|
|
981
|
+
authenticatorData: signature.authenticatorData,
|
|
982
|
+
clientDataJSON: signature.clientDataJSON,
|
|
983
|
+
challengeIndex: signature.challengeIndex,
|
|
984
|
+
typeIndex: signature.typeIndex,
|
|
985
|
+
r: signature.r,
|
|
986
|
+
s: signature.s
|
|
987
|
+
};
|
|
988
|
+
const messageFee = await this.getMessageFee();
|
|
989
|
+
const tx = await hubWithSigner.addGuardian(
|
|
990
|
+
authTuple,
|
|
991
|
+
publicKeyX,
|
|
992
|
+
publicKeyY,
|
|
993
|
+
guardianKeyHash,
|
|
994
|
+
{ value: messageFee }
|
|
995
|
+
);
|
|
996
|
+
const receipt = await tx.wait();
|
|
997
|
+
const sequence = this._extractSequenceFromReceipt(receipt);
|
|
998
|
+
return { receipt, sequence };
|
|
999
|
+
}
|
|
1000
|
+
/**
|
|
1001
|
+
* Remove a guardian from an identity
|
|
1002
|
+
*/
|
|
1003
|
+
async removeGuardian(signature, publicKeyX, publicKeyY, guardianKeyHash, signer) {
|
|
1004
|
+
const hubWithSigner = this.hubContract.connect(signer);
|
|
1005
|
+
const authTuple = {
|
|
1006
|
+
authenticatorData: signature.authenticatorData,
|
|
1007
|
+
clientDataJSON: signature.clientDataJSON,
|
|
1008
|
+
challengeIndex: signature.challengeIndex,
|
|
1009
|
+
typeIndex: signature.typeIndex,
|
|
1010
|
+
r: signature.r,
|
|
1011
|
+
s: signature.s
|
|
1012
|
+
};
|
|
1013
|
+
const messageFee = await this.getMessageFee();
|
|
1014
|
+
const tx = await hubWithSigner.removeGuardian(
|
|
1015
|
+
authTuple,
|
|
1016
|
+
publicKeyX,
|
|
1017
|
+
publicKeyY,
|
|
1018
|
+
guardianKeyHash,
|
|
1019
|
+
{ value: messageFee }
|
|
1020
|
+
);
|
|
1021
|
+
const receipt = await tx.wait();
|
|
1022
|
+
const sequence = this._extractSequenceFromReceipt(receipt);
|
|
1023
|
+
return { receipt, sequence };
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* Initiate recovery as a guardian
|
|
1027
|
+
*/
|
|
1028
|
+
async initiateRecovery(signature, publicKeyX, publicKeyY, identityToRecover, newOwnerKeyHash, signer) {
|
|
1029
|
+
const hubWithSigner = this.hubContract.connect(signer);
|
|
1030
|
+
const authTuple = {
|
|
1031
|
+
authenticatorData: signature.authenticatorData,
|
|
1032
|
+
clientDataJSON: signature.clientDataJSON,
|
|
1033
|
+
challengeIndex: signature.challengeIndex,
|
|
1034
|
+
typeIndex: signature.typeIndex,
|
|
1035
|
+
r: signature.r,
|
|
1036
|
+
s: signature.s
|
|
1037
|
+
};
|
|
1038
|
+
const messageFee = await this.getMessageFee();
|
|
1039
|
+
const tx = await hubWithSigner.initiateRecovery(
|
|
1040
|
+
authTuple,
|
|
1041
|
+
publicKeyX,
|
|
1042
|
+
publicKeyY,
|
|
1043
|
+
identityToRecover,
|
|
1044
|
+
newOwnerKeyHash,
|
|
1045
|
+
{ value: messageFee }
|
|
1046
|
+
);
|
|
1047
|
+
const receipt = await tx.wait();
|
|
1048
|
+
const sequence = this._extractSequenceFromReceipt(receipt);
|
|
1049
|
+
return { receipt, sequence };
|
|
1050
|
+
}
|
|
1051
|
+
/**
|
|
1052
|
+
* Approve recovery as a guardian
|
|
1053
|
+
*/
|
|
1054
|
+
async approveRecovery(signature, publicKeyX, publicKeyY, identityToRecover, signer) {
|
|
1055
|
+
const hubWithSigner = this.hubContract.connect(signer);
|
|
1056
|
+
const authTuple = {
|
|
1057
|
+
authenticatorData: signature.authenticatorData,
|
|
1058
|
+
clientDataJSON: signature.clientDataJSON,
|
|
1059
|
+
challengeIndex: signature.challengeIndex,
|
|
1060
|
+
typeIndex: signature.typeIndex,
|
|
1061
|
+
r: signature.r,
|
|
1062
|
+
s: signature.s
|
|
1063
|
+
};
|
|
1064
|
+
const messageFee = await this.getMessageFee();
|
|
1065
|
+
const tx = await hubWithSigner.approveRecovery(
|
|
1066
|
+
authTuple,
|
|
1067
|
+
publicKeyX,
|
|
1068
|
+
publicKeyY,
|
|
1069
|
+
identityToRecover,
|
|
1070
|
+
{ value: messageFee }
|
|
1071
|
+
);
|
|
1072
|
+
const receipt = await tx.wait();
|
|
1073
|
+
const sequence = this._extractSequenceFromReceipt(receipt);
|
|
1074
|
+
return { receipt, sequence };
|
|
1075
|
+
}
|
|
1076
|
+
/**
|
|
1077
|
+
* Execute recovery after timelock (anyone can call)
|
|
1078
|
+
*/
|
|
1079
|
+
async executeRecovery(identityToRecover, newPublicKeyX, newPublicKeyY, signer) {
|
|
1080
|
+
const hubWithSigner = this.hubContract.connect(signer);
|
|
1081
|
+
const messageFee = await this.getMessageFee();
|
|
1082
|
+
const tx = await hubWithSigner.executeRecovery(
|
|
1083
|
+
identityToRecover,
|
|
1084
|
+
newPublicKeyX,
|
|
1085
|
+
newPublicKeyY,
|
|
1086
|
+
{ value: messageFee }
|
|
1087
|
+
);
|
|
1088
|
+
const receipt = await tx.wait();
|
|
1089
|
+
const sequence = this._extractSequenceFromReceipt(receipt);
|
|
1090
|
+
return { receipt, sequence };
|
|
1091
|
+
}
|
|
1092
|
+
/**
|
|
1093
|
+
* Cancel recovery as owner
|
|
1094
|
+
*/
|
|
1095
|
+
async cancelRecovery(signature, publicKeyX, publicKeyY, signer) {
|
|
1096
|
+
const hubWithSigner = this.hubContract.connect(signer);
|
|
1097
|
+
const authTuple = {
|
|
1098
|
+
authenticatorData: signature.authenticatorData,
|
|
1099
|
+
clientDataJSON: signature.clientDataJSON,
|
|
1100
|
+
challengeIndex: signature.challengeIndex,
|
|
1101
|
+
typeIndex: signature.typeIndex,
|
|
1102
|
+
r: signature.r,
|
|
1103
|
+
s: signature.s
|
|
1104
|
+
};
|
|
1105
|
+
const messageFee = await this.getMessageFee();
|
|
1106
|
+
const tx = await hubWithSigner.cancelRecovery(
|
|
1107
|
+
authTuple,
|
|
1108
|
+
publicKeyX,
|
|
1109
|
+
publicKeyY,
|
|
1110
|
+
{ value: messageFee }
|
|
1111
|
+
);
|
|
1112
|
+
const receipt = await tx.wait();
|
|
1113
|
+
const sequence = this._extractSequenceFromReceipt(receipt);
|
|
1114
|
+
return { receipt, sequence };
|
|
1115
|
+
}
|
|
1116
|
+
/**
|
|
1117
|
+
* Get guardians for an identity
|
|
1118
|
+
*/
|
|
1119
|
+
async getGuardians(identityKeyHash) {
|
|
1120
|
+
const result = await this.hubContract.getGuardians(identityKeyHash);
|
|
1121
|
+
return {
|
|
1122
|
+
guardians: result.guardians,
|
|
1123
|
+
threshold: result.threshold,
|
|
1124
|
+
isConfigured: result.isConfigured
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
/**
|
|
1128
|
+
* Get recovery status for an identity
|
|
1129
|
+
*/
|
|
1130
|
+
async getRecoveryStatus(identityKeyHash) {
|
|
1131
|
+
const result = await this.hubContract.getRecoveryStatus(identityKeyHash);
|
|
1132
|
+
return {
|
|
1133
|
+
isActive: result.isActive,
|
|
1134
|
+
newOwnerKeyHash: result.newOwnerKeyHash,
|
|
1135
|
+
initiatedAt: result.initiatedAt,
|
|
1136
|
+
approvalCount: result.approvalCount,
|
|
1137
|
+
threshold: result.threshold,
|
|
1138
|
+
canExecuteAt: result.canExecuteAt,
|
|
1139
|
+
expiresAt: result.expiresAt
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
/**
|
|
1143
|
+
* Check if a guardian has approved recovery
|
|
1144
|
+
*/
|
|
1145
|
+
async hasGuardianApproved(identityKeyHash, guardianKeyHash) {
|
|
1146
|
+
return this.hubContract.hasGuardianApproved(identityKeyHash, guardianKeyHash);
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* Helper to extract sequence from transaction receipt
|
|
1150
|
+
*/
|
|
1151
|
+
_extractSequenceFromReceipt(receipt) {
|
|
1152
|
+
for (const log of receipt.logs) {
|
|
1153
|
+
try {
|
|
1154
|
+
const parsed = this.hubContract.interface.parseLog({
|
|
1155
|
+
topics: log.topics,
|
|
1156
|
+
data: log.data
|
|
1157
|
+
});
|
|
1158
|
+
if (parsed?.name === "Dispatch") {
|
|
1159
|
+
return BigInt(parsed.args.sequence);
|
|
1160
|
+
}
|
|
1161
|
+
} catch {
|
|
1162
|
+
}
|
|
1163
|
+
}
|
|
1164
|
+
return 0n;
|
|
1165
|
+
}
|
|
1166
|
+
};
|
|
1167
|
+
|
|
1168
|
+
// src/chains/evm/EVMHubClientAdapter.ts
|
|
1169
|
+
var EVMHubClientAdapter = class {
|
|
1170
|
+
constructor(evmClient, signer) {
|
|
1171
|
+
this.evmClient = evmClient;
|
|
1172
|
+
this.signer = signer;
|
|
1173
|
+
}
|
|
1174
|
+
/**
|
|
1175
|
+
* Register a session on the Hub
|
|
1176
|
+
*
|
|
1177
|
+
* @param params Registration parameters with Passkey signature
|
|
1178
|
+
* @returns Promise that resolves when registration completes
|
|
1179
|
+
*/
|
|
1180
|
+
async registerSession(params) {
|
|
1181
|
+
await this.evmClient.registerSession(params, this.signer);
|
|
1182
|
+
}
|
|
1183
|
+
/**
|
|
1184
|
+
* Revoke a session on the Hub
|
|
1185
|
+
*
|
|
1186
|
+
* @param params Revocation parameters with Passkey signature
|
|
1187
|
+
* @returns Promise that resolves when revocation completes
|
|
1188
|
+
*/
|
|
1189
|
+
async revokeSession(params) {
|
|
1190
|
+
await this.evmClient.revokeSession(params, this.signer);
|
|
1191
|
+
}
|
|
1192
|
+
/**
|
|
1193
|
+
* Update the signer (e.g., when switching accounts)
|
|
1194
|
+
*
|
|
1195
|
+
* @param signer New Ethereum signer
|
|
1196
|
+
*/
|
|
1197
|
+
updateSigner(signer) {
|
|
1198
|
+
this.signer = signer;
|
|
1199
|
+
}
|
|
1200
|
+
};
|
|
1201
|
+
export {
|
|
1202
|
+
EVMClient,
|
|
1203
|
+
EVMHubClientAdapter
|
|
1204
|
+
};
|
|
1205
|
+
//# sourceMappingURL=index.mjs.map
|