@vaultkeepr/smart-account 0.1.1 → 0.2.0
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/README.md +42 -0
- package/dist/index.d.mts +194 -95
- package/dist/index.d.ts +194 -95
- package/dist/index.js +333 -281
- package/dist/index.mjs +325 -273
- package/package.json +21 -6
- package/src/index.ts +16 -14
package/dist/index.mjs
CHANGED
|
@@ -162,252 +162,102 @@ function clearSmartAccountCache() {
|
|
|
162
162
|
_cachedKey = null;
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
// src/
|
|
166
|
-
import { getHiddenWalletFromPassword, getHiddenWalletLegacy } from "@vaultkeepr/core";
|
|
167
|
-
async function getOwnerFromPassword(password, secretKey) {
|
|
168
|
-
return getHiddenWalletFromPassword(password, secretKey);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// src/cidRegistry.ts
|
|
165
|
+
// src/deriveIdentity.ts
|
|
172
166
|
import {
|
|
173
167
|
createPublicClient as createPublicClient2,
|
|
174
|
-
|
|
175
|
-
|
|
168
|
+
http as http2,
|
|
169
|
+
getAddress,
|
|
170
|
+
isAddressEqual,
|
|
171
|
+
recoverMessageAddress,
|
|
172
|
+
zeroAddress
|
|
176
173
|
} from "viem";
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
{
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
outputs: []
|
|
185
|
-
},
|
|
186
|
-
{
|
|
187
|
-
name: "deleteCid",
|
|
188
|
-
type: "function",
|
|
189
|
-
stateMutability: "nonpayable",
|
|
190
|
-
inputs: [],
|
|
191
|
-
outputs: []
|
|
192
|
-
},
|
|
193
|
-
{
|
|
194
|
-
name: "getCid",
|
|
195
|
-
type: "function",
|
|
196
|
-
stateMutability: "view",
|
|
197
|
-
inputs: [{ name: "account", type: "address" }],
|
|
198
|
-
outputs: [{ name: "", type: "string" }]
|
|
199
|
-
},
|
|
200
|
-
{
|
|
201
|
-
name: "hasCid",
|
|
202
|
-
type: "function",
|
|
203
|
-
stateMutability: "view",
|
|
204
|
-
inputs: [{ name: "account", type: "address" }],
|
|
205
|
-
outputs: [{ name: "", type: "bool" }]
|
|
206
|
-
},
|
|
207
|
-
{
|
|
208
|
-
name: "getUpdatedAt",
|
|
209
|
-
type: "function",
|
|
210
|
-
stateMutability: "view",
|
|
211
|
-
inputs: [{ name: "account", type: "address" }],
|
|
212
|
-
outputs: [{ name: "", type: "uint256" }]
|
|
213
|
-
},
|
|
214
|
-
{
|
|
215
|
-
name: "nextAllowedUpdate",
|
|
216
|
-
type: "function",
|
|
217
|
-
stateMutability: "view",
|
|
218
|
-
inputs: [{ name: "account", type: "address" }],
|
|
219
|
-
outputs: [{ name: "", type: "uint256" }]
|
|
220
|
-
},
|
|
221
|
-
{
|
|
222
|
-
name: "cooldownSeconds",
|
|
223
|
-
type: "function",
|
|
224
|
-
stateMutability: "view",
|
|
225
|
-
inputs: [],
|
|
226
|
-
outputs: [{ name: "", type: "uint256" }]
|
|
227
|
-
}
|
|
228
|
-
];
|
|
229
|
-
function getCidRegistryAddress() {
|
|
230
|
-
let addr = "";
|
|
231
|
-
try {
|
|
232
|
-
addr = process.env.CID_REGISTRY_ADDRESS || "";
|
|
233
|
-
} catch {
|
|
234
|
-
}
|
|
235
|
-
if (addr) return addr;
|
|
236
|
-
try {
|
|
237
|
-
addr = process.env.NEXT_PUBLIC_CID_REGISTRY_ADDRESS || "";
|
|
238
|
-
} catch {
|
|
239
|
-
}
|
|
240
|
-
if (addr) return addr;
|
|
241
|
-
try {
|
|
242
|
-
addr = process.env.EXPO_PUBLIC_CID_REGISTRY_ADDRESS || "";
|
|
243
|
-
} catch {
|
|
244
|
-
}
|
|
245
|
-
if (addr) return addr;
|
|
246
|
-
return "0x0000000000000000000000000000000000000000";
|
|
174
|
+
async function deriveIdentity(ownerAccount, mode) {
|
|
175
|
+
const aaAddress = await computeSmartAccountAddress(ownerAccount);
|
|
176
|
+
return {
|
|
177
|
+
ownerAddress: ownerAccount.address,
|
|
178
|
+
aaAddress,
|
|
179
|
+
mode
|
|
180
|
+
};
|
|
247
181
|
}
|
|
248
|
-
|
|
249
|
-
|
|
182
|
+
var MAX_SKEW_MS = 5 * 6e4;
|
|
183
|
+
var UNSIGNABLE_OWNER = "verifyAAAuth: owner stub cannot sign";
|
|
184
|
+
function addressOnlyAccount(address) {
|
|
185
|
+
return {
|
|
186
|
+
address,
|
|
187
|
+
type: "local",
|
|
188
|
+
publicKey: "0x",
|
|
189
|
+
source: "custom",
|
|
190
|
+
signMessage: async () => {
|
|
191
|
+
throw new Error(UNSIGNABLE_OWNER);
|
|
192
|
+
},
|
|
193
|
+
signTypedData: async () => {
|
|
194
|
+
throw new Error(UNSIGNABLE_OWNER);
|
|
195
|
+
},
|
|
196
|
+
signTransaction: async () => {
|
|
197
|
+
throw new Error(UNSIGNABLE_OWNER);
|
|
198
|
+
}
|
|
199
|
+
};
|
|
250
200
|
}
|
|
251
|
-
async function
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
}
|
|
201
|
+
async function verifyAAAuth(params) {
|
|
202
|
+
const rejected = {
|
|
203
|
+
ok: false,
|
|
204
|
+
ownerAddress: zeroAddress,
|
|
205
|
+
aaAddress: zeroAddress
|
|
206
|
+
};
|
|
258
207
|
try {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
208
|
+
if (!Number.isFinite(params.timestamp) || Math.abs(Date.now() - params.timestamp) > MAX_SKEW_MS) {
|
|
209
|
+
return rejected;
|
|
210
|
+
}
|
|
211
|
+
const signature = params.signature;
|
|
212
|
+
let ownerAddress;
|
|
213
|
+
if (params.claimedOwner) {
|
|
214
|
+
ownerAddress = getAddress(params.claimedOwner);
|
|
215
|
+
const publicClient = createPublicClient2({
|
|
216
|
+
chain: TARGET_CHAIN,
|
|
217
|
+
transport: http2()
|
|
218
|
+
});
|
|
219
|
+
const valid = await publicClient.verifyMessage({
|
|
220
|
+
address: ownerAddress,
|
|
221
|
+
message: params.message,
|
|
222
|
+
signature
|
|
223
|
+
});
|
|
224
|
+
if (!valid) {
|
|
225
|
+
return {
|
|
226
|
+
ok: false,
|
|
227
|
+
ownerAddress,
|
|
228
|
+
aaAddress: params.claimedAA ? getAddress(params.claimedAA) : zeroAddress
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
} else {
|
|
232
|
+
ownerAddress = await recoverMessageAddress({
|
|
233
|
+
message: params.message,
|
|
234
|
+
signature
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
const claimedAA = params.claimedAA ? getAddress(params.claimedAA) : null;
|
|
238
|
+
const aaAddress = await computeSmartAccountAddress(
|
|
239
|
+
addressOnlyAccount(ownerAddress)
|
|
281
240
|
);
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
return
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
async function readCidOnChain(smartAccountAddress) {
|
|
289
|
-
if (!isCidRegistryConfigured()) return null;
|
|
290
|
-
try {
|
|
291
|
-
const publicClient = createPublicClient2({
|
|
292
|
-
chain: TARGET_CHAIN,
|
|
293
|
-
transport: http2()
|
|
294
|
-
});
|
|
295
|
-
const cid = await publicClient.readContract({
|
|
296
|
-
address: getCidRegistryAddress(),
|
|
297
|
-
abi: CID_REGISTRY_ABI,
|
|
298
|
-
functionName: "getCid",
|
|
299
|
-
args: [smartAccountAddress]
|
|
300
|
-
});
|
|
301
|
-
return cid && cid.length > 0 ? cid : null;
|
|
302
|
-
} catch (error) {
|
|
303
|
-
logger2.error("[CidRegistry] Erreur lecture:", error instanceof Error ? error.message : error);
|
|
304
|
-
return null;
|
|
241
|
+
if (claimedAA && !isAddressEqual(claimedAA, aaAddress)) {
|
|
242
|
+
return { ok: false, ownerAddress, aaAddress };
|
|
243
|
+
}
|
|
244
|
+
return { ok: true, ownerAddress, aaAddress };
|
|
245
|
+
} catch {
|
|
246
|
+
return rejected;
|
|
305
247
|
}
|
|
306
248
|
}
|
|
307
249
|
|
|
308
|
-
// src/
|
|
309
|
-
import {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
var _debounceTimer = null;
|
|
314
|
-
var _lastPublishedCid = null;
|
|
315
|
-
var _lastPublishTime = 0;
|
|
316
|
-
var _isPublishing = false;
|
|
317
|
-
function scheduleCidOnChainSync(smartAccount, cid, debounceMs = DEFAULT_DEBOUNCE_MS) {
|
|
318
|
-
if (!isCidRegistryConfigured()) return;
|
|
319
|
-
if (cid === _lastPublishedCid) {
|
|
320
|
-
logger3.debug("[OnChainSync] CID identique, skip");
|
|
321
|
-
return;
|
|
322
|
-
}
|
|
323
|
-
const sa = smartAccount ?? _pendingSmartAccount;
|
|
324
|
-
if (!sa) {
|
|
325
|
-
logger3.warn("[OnChainSync] Pas de Smart Account disponible, skip");
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
_pendingCid = cid;
|
|
329
|
-
_pendingSmartAccount = sa;
|
|
330
|
-
if (_debounceTimer) {
|
|
331
|
-
clearTimeout(_debounceTimer);
|
|
332
|
-
}
|
|
333
|
-
_debounceTimer = setTimeout(() => {
|
|
334
|
-
_executeSync();
|
|
335
|
-
}, debounceMs);
|
|
336
|
-
logger3.debug(
|
|
337
|
-
`[OnChainSync] CID planifi\xE9 dans ${(debounceMs / 1e3).toFixed(0)}s : ${cid.slice(0, 20)}...`
|
|
338
|
-
);
|
|
339
|
-
}
|
|
340
|
-
async function flushCidOnChainSync() {
|
|
341
|
-
logger3.debug(
|
|
342
|
-
"[OnChainSync] flush demand\xE9 \u2014 pending:",
|
|
343
|
-
_pendingCid?.slice(0, 20),
|
|
344
|
-
"SA:",
|
|
345
|
-
!!_pendingSmartAccount
|
|
346
|
-
);
|
|
347
|
-
if (!_pendingCid || !_pendingSmartAccount) {
|
|
348
|
-
logger3.debug("[OnChainSync] Rien \xE0 flush");
|
|
349
|
-
return true;
|
|
350
|
-
}
|
|
351
|
-
if (_debounceTimer) {
|
|
352
|
-
clearTimeout(_debounceTimer);
|
|
353
|
-
_debounceTimer = null;
|
|
354
|
-
}
|
|
355
|
-
logger3.debug("[OnChainSync] Flush imm\xE9diat...");
|
|
356
|
-
return _executeSync();
|
|
357
|
-
}
|
|
358
|
-
function getOnChainSyncStatus() {
|
|
359
|
-
return {
|
|
360
|
-
hasPending: _pendingCid !== null && _pendingCid !== _lastPublishedCid,
|
|
361
|
-
pendingCid: _pendingCid,
|
|
362
|
-
lastPublishedCid: _lastPublishedCid,
|
|
363
|
-
lastPublishTime: _lastPublishTime,
|
|
364
|
-
isPublishing: _isPublishing
|
|
365
|
-
};
|
|
366
|
-
}
|
|
367
|
-
function resetOnChainSync() {
|
|
368
|
-
if (_debounceTimer) {
|
|
369
|
-
clearTimeout(_debounceTimer);
|
|
370
|
-
_debounceTimer = null;
|
|
371
|
-
}
|
|
372
|
-
_pendingCid = null;
|
|
373
|
-
_pendingSmartAccount = null;
|
|
374
|
-
_lastPublishedCid = null;
|
|
375
|
-
_lastPublishTime = 0;
|
|
376
|
-
_isPublishing = false;
|
|
377
|
-
}
|
|
378
|
-
async function _executeSync() {
|
|
379
|
-
if (!_pendingCid || !_pendingSmartAccount || _isPublishing) return false;
|
|
380
|
-
if (_pendingCid === _lastPublishedCid) {
|
|
381
|
-
_pendingCid = null;
|
|
382
|
-
return true;
|
|
383
|
-
}
|
|
384
|
-
_isPublishing = true;
|
|
385
|
-
const cid = _pendingCid;
|
|
386
|
-
const sa = _pendingSmartAccount;
|
|
387
|
-
try {
|
|
388
|
-
logger3.debug(`[OnChainSync] Publication on-chain: ${cid.slice(0, 20)}...`);
|
|
389
|
-
const txHash = await publishCidOnChain(sa, cid);
|
|
390
|
-
if (txHash) {
|
|
391
|
-
_lastPublishedCid = cid;
|
|
392
|
-
_lastPublishTime = Date.now();
|
|
393
|
-
_pendingCid = null;
|
|
394
|
-
logger3.debug(
|
|
395
|
-
`[OnChainSync] \u2705 CID publi\xE9 on-chain : ${cid.slice(0, 20)}...`
|
|
396
|
-
);
|
|
397
|
-
return true;
|
|
398
|
-
}
|
|
399
|
-
logger3.warn("[OnChainSync] Publication \xE9chou\xE9e (null txHash)");
|
|
400
|
-
return false;
|
|
401
|
-
} catch (error) {
|
|
402
|
-
logger3.error("[OnChainSync] Erreur publication:", error instanceof Error ? error.message : error);
|
|
403
|
-
return false;
|
|
404
|
-
} finally {
|
|
405
|
-
_isPublishing = false;
|
|
250
|
+
// src/owner.ts
|
|
251
|
+
import { getHiddenWalletFromPassword, getHiddenWalletFromSecretKey, getHiddenWalletLegacy } from "@vaultkeepr/core";
|
|
252
|
+
async function getOwnerFromPassword(password, secretKey) {
|
|
253
|
+
if (secretKey) {
|
|
254
|
+
return getHiddenWalletFromSecretKey(secretKey);
|
|
406
255
|
}
|
|
256
|
+
return getHiddenWalletFromPassword(password);
|
|
407
257
|
}
|
|
408
258
|
|
|
409
259
|
// src/identity.ts
|
|
410
|
-
import { logger as
|
|
260
|
+
import { logger as logger2, redactAddress as redactAddress2 } from "@vaultkeepr/logger";
|
|
411
261
|
var _state = {
|
|
412
262
|
owner: null,
|
|
413
263
|
smartAccount: null,
|
|
@@ -433,7 +283,15 @@ async function initIdentityFromPassword(password, secretKey) {
|
|
|
433
283
|
mode: "password"
|
|
434
284
|
});
|
|
435
285
|
} catch (e) {
|
|
436
|
-
|
|
286
|
+
logger2.warn("[Identity] SA init failed (non-blocking):", e.message);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
let aaAddress = sa?.address ?? null;
|
|
290
|
+
if (!aaAddress && isSmartAccountConfigured()) {
|
|
291
|
+
try {
|
|
292
|
+
aaAddress = await computeSmartAccountAddress(owner);
|
|
293
|
+
} catch (e) {
|
|
294
|
+
logger2.warn("[Identity] AA derivation failed (non-blocking):", e.message);
|
|
437
295
|
}
|
|
438
296
|
}
|
|
439
297
|
_state = {
|
|
@@ -443,12 +301,13 @@ async function initIdentityFromPassword(password, secretKey) {
|
|
|
443
301
|
initialized: true
|
|
444
302
|
};
|
|
445
303
|
_notify();
|
|
446
|
-
|
|
304
|
+
logger2.debug(
|
|
447
305
|
`[Identity] Connect\xE9 (password) \u2014 adresse: ${redactAddress2(owner.address)}` + (sa ? ` | SA: ${redactAddress2(sa.address)}` : "") + (secretKey ? ` | SK: \u2713` : " | no secretKey")
|
|
448
306
|
);
|
|
449
307
|
return {
|
|
450
308
|
address: owner.address,
|
|
451
|
-
smartAccountAddress: sa?.address ?? null
|
|
309
|
+
smartAccountAddress: sa?.address ?? null,
|
|
310
|
+
aaAddress
|
|
452
311
|
};
|
|
453
312
|
}
|
|
454
313
|
async function initIdentityFromSigner(signer, mode = "passkey") {
|
|
@@ -457,7 +316,15 @@ async function initIdentityFromSigner(signer, mode = "passkey") {
|
|
|
457
316
|
try {
|
|
458
317
|
sa = await createVaultSmartAccount({ ownerAccount: signer, mode });
|
|
459
318
|
} catch (e) {
|
|
460
|
-
|
|
319
|
+
logger2.warn("[Identity] SA init failed (non-blocking):", e.message);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
let aaAddress = sa?.address ?? null;
|
|
323
|
+
if (!aaAddress && isSmartAccountConfigured()) {
|
|
324
|
+
try {
|
|
325
|
+
aaAddress = await computeSmartAccountAddress(signer);
|
|
326
|
+
} catch (e) {
|
|
327
|
+
logger2.warn("[Identity] AA derivation failed (non-blocking):", e.message);
|
|
461
328
|
}
|
|
462
329
|
}
|
|
463
330
|
_state = {
|
|
@@ -467,12 +334,13 @@ async function initIdentityFromSigner(signer, mode = "passkey") {
|
|
|
467
334
|
initialized: true
|
|
468
335
|
};
|
|
469
336
|
_notify();
|
|
470
|
-
|
|
337
|
+
logger2.info(
|
|
471
338
|
`[Identity] Connected (${mode}) \u2014 address: ${signer.address}` + (sa ? ` | SA: ${sa.address}` : "")
|
|
472
339
|
);
|
|
473
340
|
return {
|
|
474
341
|
address: signer.address,
|
|
475
|
-
smartAccountAddress: sa?.address ?? null
|
|
342
|
+
smartAccountAddress: sa?.address ?? null,
|
|
343
|
+
aaAddress
|
|
476
344
|
};
|
|
477
345
|
}
|
|
478
346
|
function clearIdentity() {
|
|
@@ -484,7 +352,7 @@ function clearIdentity() {
|
|
|
484
352
|
};
|
|
485
353
|
clearSmartAccountCache();
|
|
486
354
|
_notify();
|
|
487
|
-
|
|
355
|
+
logger2.info("[Identity] Disconnected");
|
|
488
356
|
}
|
|
489
357
|
function getIdentityAddress() {
|
|
490
358
|
return _state.owner?.address ?? null;
|
|
@@ -543,24 +411,10 @@ function useIdentity() {
|
|
|
543
411
|
const disconnect = useCallback(() => {
|
|
544
412
|
clearIdentity();
|
|
545
413
|
}, []);
|
|
546
|
-
const
|
|
547
|
-
(cid) => {
|
|
548
|
-
const sa = getIdentitySmartAccount();
|
|
549
|
-
if (!sa) return;
|
|
550
|
-
scheduleCidOnChainSync(sa, cid);
|
|
551
|
-
},
|
|
552
|
-
[]
|
|
553
|
-
);
|
|
554
|
-
const cleanupOnLock = useCallback(async () => {
|
|
555
|
-
await flushCidOnChainSync().catch(() => {
|
|
556
|
-
});
|
|
414
|
+
const cleanupOnLock = useCallback(() => {
|
|
557
415
|
clearSmartAccountCache();
|
|
558
|
-
resetOnChainSync();
|
|
559
416
|
clearIdentity();
|
|
560
|
-
|
|
561
|
-
const flushOnChainSync = useCallback(async () => {
|
|
562
|
-
await flushCidOnChainSync().catch(() => {
|
|
563
|
-
});
|
|
417
|
+
return Promise.resolve();
|
|
564
418
|
}, []);
|
|
565
419
|
return {
|
|
566
420
|
address: state.owner?.address ?? null,
|
|
@@ -569,9 +423,7 @@ function useIdentity() {
|
|
|
569
423
|
smartAccountAddress: state.smartAccount?.address ?? null,
|
|
570
424
|
signMessage,
|
|
571
425
|
disconnect,
|
|
572
|
-
|
|
573
|
-
cleanupOnLock,
|
|
574
|
-
flushOnChainSync
|
|
426
|
+
cleanupOnLock
|
|
575
427
|
};
|
|
576
428
|
}
|
|
577
429
|
|
|
@@ -579,13 +431,12 @@ function useIdentity() {
|
|
|
579
431
|
import { bytesToHex } from "viem";
|
|
580
432
|
function generateSecretKey() {
|
|
581
433
|
const bytes = new Uint8Array(32);
|
|
582
|
-
if (typeof globalThis.crypto?.getRandomValues
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
bytes[i] = Math.floor(Math.random() * 256);
|
|
587
|
-
}
|
|
434
|
+
if (typeof globalThis.crypto?.getRandomValues !== "function") {
|
|
435
|
+
throw new Error(
|
|
436
|
+
"[SmartAccount] CSPRNG indisponible: crypto.getRandomValues est requis pour generer une cle secrete. Un fallback Math.random produirait une cle devinable (CWE-338), refuse."
|
|
437
|
+
);
|
|
588
438
|
}
|
|
439
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
589
440
|
return bytesToHex(bytes).slice(2);
|
|
590
441
|
}
|
|
591
442
|
function isValidSecretKey(key) {
|
|
@@ -598,13 +449,214 @@ function formatSecretKeyForDisplay(key) {
|
|
|
598
449
|
function parseSecretKeyInput(input) {
|
|
599
450
|
return input.replace(/[-\s]/g, "").toLowerCase();
|
|
600
451
|
}
|
|
452
|
+
|
|
453
|
+
// src/keyPassage.ts
|
|
454
|
+
function identityState(input) {
|
|
455
|
+
if (input.hasSecretKey) return { kind: "ready" };
|
|
456
|
+
if (input.pointer === "moved") return { kind: "pair_required" };
|
|
457
|
+
if (input.pointer === "active") return { kind: "pass_to_key" };
|
|
458
|
+
return { kind: "join_or_create" };
|
|
459
|
+
}
|
|
460
|
+
function canWritePointer(state) {
|
|
461
|
+
return state.kind === "ready" || state.kind === "pass_to_key";
|
|
462
|
+
}
|
|
463
|
+
function sameAddress(a, b) {
|
|
464
|
+
return a.trim().toLowerCase() === b.trim().toLowerCase();
|
|
465
|
+
}
|
|
466
|
+
async function runPassage(io, opts) {
|
|
467
|
+
if (opts.hasSecretKey) return { status: "already_keyed" };
|
|
468
|
+
const legacy = await io.readLegacyPointer();
|
|
469
|
+
if (legacy.moved) {
|
|
470
|
+
const pending = await io.peekPendingKey();
|
|
471
|
+
if (pending && legacy.movedTo && sameAddress(await io.smartAccountOf(pending), legacy.movedTo)) {
|
|
472
|
+
await io.activatePendingKey();
|
|
473
|
+
return { status: "moved", smartAccount: legacy.movedTo };
|
|
474
|
+
}
|
|
475
|
+
if (pending) await io.discardPendingKey();
|
|
476
|
+
return { status: "already_moved" };
|
|
477
|
+
}
|
|
478
|
+
if (!legacy.cid) return { status: "nothing_to_pass" };
|
|
479
|
+
const secretKey = await io.getOrCreatePendingKey();
|
|
480
|
+
const smartAccount = await io.smartAccountOf(secretKey);
|
|
481
|
+
await io.writeSmartAccountPointer(smartAccount, legacy.cid);
|
|
482
|
+
await io.markLegacyMoved(smartAccount);
|
|
483
|
+
await io.activatePendingKey();
|
|
484
|
+
return { status: "moved", smartAccount };
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// src/passageClient.ts
|
|
488
|
+
var PASSAGE_ENABLED = false;
|
|
489
|
+
var DELEGATION_TTL_SECONDS = 300;
|
|
490
|
+
async function postJson(env, path, body) {
|
|
491
|
+
const res = await env.fetch(`${env.apiBase}${path}`, {
|
|
492
|
+
method: "POST",
|
|
493
|
+
headers: { "Content-Type": "application/json" },
|
|
494
|
+
body: JSON.stringify(body)
|
|
495
|
+
});
|
|
496
|
+
if (!res.ok) throw new Error(`passage ${path} failed (${res.status})`);
|
|
497
|
+
return res.json();
|
|
498
|
+
}
|
|
499
|
+
function createPassageIO(env) {
|
|
500
|
+
const legacyAddress = env.legacyAddress.toLowerCase();
|
|
501
|
+
const delegation2 = async (sign) => {
|
|
502
|
+
const sessionId = env.newSessionId();
|
|
503
|
+
const expiryTimestamp = Math.floor(env.now() / 1e3) + DELEGATION_TTL_SECONDS;
|
|
504
|
+
const delegationSignature = await sign(env.formatDelegation(sessionId, expiryTimestamp));
|
|
505
|
+
return { sessionId, expiryTimestamp, delegationSignature };
|
|
506
|
+
};
|
|
507
|
+
return {
|
|
508
|
+
async readLegacyPointer() {
|
|
509
|
+
const d = await delegation2((m) => env.signLegacy(m));
|
|
510
|
+
const query = [
|
|
511
|
+
`wallet=${encodeURIComponent(legacyAddress)}`,
|
|
512
|
+
`delegationSignature=${encodeURIComponent(d.delegationSignature)}`,
|
|
513
|
+
`sessionId=${encodeURIComponent(d.sessionId)}`,
|
|
514
|
+
`expiryTimestamp=${d.expiryTimestamp}`
|
|
515
|
+
].join("&");
|
|
516
|
+
const res = await env.fetch(`${env.apiBase}/api/vault-cid?${query}`);
|
|
517
|
+
if (!res.ok) throw new Error(`passage read failed (${res.status})`);
|
|
518
|
+
const json = await res.json();
|
|
519
|
+
return { cid: json.cid ?? null, moved: !!json.moved, movedTo: json.movedTo ?? void 0 };
|
|
520
|
+
},
|
|
521
|
+
peekPendingKey: () => env.pending.read(),
|
|
522
|
+
async getOrCreatePendingKey() {
|
|
523
|
+
const existing = await env.pending.read();
|
|
524
|
+
if (existing) return existing;
|
|
525
|
+
const secretKey = env.newSecretKey();
|
|
526
|
+
await env.pending.write(secretKey);
|
|
527
|
+
return secretKey;
|
|
528
|
+
},
|
|
529
|
+
smartAccountOf: (secretKey) => env.smartAccountOf(secretKey),
|
|
530
|
+
async writeSmartAccountPointer(smartAccount, cid) {
|
|
531
|
+
const pendingKey = await env.pending.read();
|
|
532
|
+
if (!pendingKey) throw new Error("passage: no pending key");
|
|
533
|
+
const d = await delegation2((m) => env.signWithKey(pendingKey, m));
|
|
534
|
+
await postJson(env, "/api/vault-cid", {
|
|
535
|
+
walletAddress: smartAccount,
|
|
536
|
+
cid,
|
|
537
|
+
delegationSignature: d.delegationSignature,
|
|
538
|
+
sessionId: d.sessionId,
|
|
539
|
+
expiryTimestamp: d.expiryTimestamp
|
|
540
|
+
});
|
|
541
|
+
},
|
|
542
|
+
async markLegacyMoved(smartAccount) {
|
|
543
|
+
const d = await delegation2((m) => env.signLegacy(m));
|
|
544
|
+
await postJson(env, "/api/vault-cid/move", {
|
|
545
|
+
walletAddress: legacyAddress,
|
|
546
|
+
targetSmartAccount: smartAccount,
|
|
547
|
+
delegationSignature: d.delegationSignature,
|
|
548
|
+
sessionId: d.sessionId,
|
|
549
|
+
expiryTimestamp: d.expiryTimestamp
|
|
550
|
+
});
|
|
551
|
+
},
|
|
552
|
+
async activatePendingKey() {
|
|
553
|
+
const pendingKey = await env.pending.read();
|
|
554
|
+
if (!pendingKey) throw new Error("passage: no pending key");
|
|
555
|
+
await env.activate(pendingKey);
|
|
556
|
+
await env.pending.clear();
|
|
557
|
+
},
|
|
558
|
+
discardPendingKey: () => env.pending.clear()
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
async function readPassageState(env, hasSecretKey) {
|
|
562
|
+
const legacy = await createPassageIO(env).readLegacyPointer();
|
|
563
|
+
const pointer = legacy.moved ? "moved" : legacy.cid ? "active" : "none";
|
|
564
|
+
return identityState({ hasSecretKey, pointer });
|
|
565
|
+
}
|
|
566
|
+
function runPassageWith(env, hasSecretKey) {
|
|
567
|
+
return runPassage(createPassageIO(env), { hasSecretKey });
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// src/keyedMigration.ts
|
|
571
|
+
var KEYED_MIGRATION_ENABLED = false;
|
|
572
|
+
var DELEGATION_TTL_SECONDS2 = 300;
|
|
573
|
+
async function delegation(env) {
|
|
574
|
+
const sessionId = env.newSessionId();
|
|
575
|
+
const expiryTimestamp = Math.floor(env.now() / 1e3) + DELEGATION_TTL_SECONDS2;
|
|
576
|
+
const delegationSignature = await env.signWithKey(env.formatDelegation(sessionId, expiryTimestamp));
|
|
577
|
+
return { sessionId, expiryTimestamp, delegationSignature };
|
|
578
|
+
}
|
|
579
|
+
async function readPointer(env, wallet) {
|
|
580
|
+
const d = await delegation(env);
|
|
581
|
+
const query = [
|
|
582
|
+
`wallet=${encodeURIComponent(wallet)}`,
|
|
583
|
+
`delegationSignature=${encodeURIComponent(d.delegationSignature)}`,
|
|
584
|
+
`sessionId=${encodeURIComponent(d.sessionId)}`,
|
|
585
|
+
`expiryTimestamp=${d.expiryTimestamp}`
|
|
586
|
+
].join("&");
|
|
587
|
+
const res = await env.fetch(`${env.apiBase}/api/vault-cid?${query}`);
|
|
588
|
+
if (!res.ok) throw new Error(`keyed migration: read failed (${res.status})`);
|
|
589
|
+
const json = await res.json();
|
|
590
|
+
return { cid: json.cid ?? null, moved: !!json.moved, movedTo: json.movedTo ?? void 0 };
|
|
591
|
+
}
|
|
592
|
+
async function copyPointer(env, smartAccount, cid) {
|
|
593
|
+
const d = await delegation(env);
|
|
594
|
+
const res = await env.fetch(`${env.apiBase}/api/vault-cid`, {
|
|
595
|
+
method: "POST",
|
|
596
|
+
headers: { "Content-Type": "application/json" },
|
|
597
|
+
body: JSON.stringify({
|
|
598
|
+
walletAddress: smartAccount,
|
|
599
|
+
cid,
|
|
600
|
+
delegationSignature: d.delegationSignature,
|
|
601
|
+
sessionId: d.sessionId,
|
|
602
|
+
expiryTimestamp: d.expiryTimestamp
|
|
603
|
+
})
|
|
604
|
+
});
|
|
605
|
+
if (!res.ok) throw new Error(`keyed migration: SA copy failed (${res.status})`);
|
|
606
|
+
}
|
|
607
|
+
async function markMoved(env, owner, smartAccount) {
|
|
608
|
+
const d = await delegation(env);
|
|
609
|
+
const res = await env.fetch(`${env.apiBase}/api/vault-cid/move`, {
|
|
610
|
+
method: "POST",
|
|
611
|
+
headers: { "Content-Type": "application/json" },
|
|
612
|
+
body: JSON.stringify({
|
|
613
|
+
walletAddress: owner,
|
|
614
|
+
targetSmartAccount: smartAccount,
|
|
615
|
+
delegationSignature: d.delegationSignature,
|
|
616
|
+
sessionId: d.sessionId,
|
|
617
|
+
expiryTimestamp: d.expiryTimestamp
|
|
618
|
+
})
|
|
619
|
+
});
|
|
620
|
+
if (!res.ok) throw new Error(`keyed migration: move failed (${res.status})`);
|
|
621
|
+
}
|
|
622
|
+
async function reconcileAfterMove(env, owner, smartAccount) {
|
|
623
|
+
const frozen = await readPointer(env, owner);
|
|
624
|
+
if (!frozen.cid) throw new Error("keyed migration: legacy pointer empty after the move");
|
|
625
|
+
const copied = await readPointer(env, smartAccount);
|
|
626
|
+
if (copied.cid === frozen.cid) return;
|
|
627
|
+
await copyPointer(env, smartAccount, frozen.cid);
|
|
628
|
+
const check = await readPointer(env, smartAccount);
|
|
629
|
+
if (check.cid !== frozen.cid) throw new Error("keyed migration: SA copy differs after the move");
|
|
630
|
+
}
|
|
631
|
+
async function migrateKeyedPointer(env) {
|
|
632
|
+
const owner = env.ownerAddress.toLowerCase();
|
|
633
|
+
const smartAccount = env.smartAccount.toLowerCase();
|
|
634
|
+
const legacy = await readPointer(env, owner);
|
|
635
|
+
if (legacy.moved) {
|
|
636
|
+
if ((legacy.movedTo ?? "").toLowerCase() !== smartAccount) {
|
|
637
|
+
throw new Error("keyed migration: legacy pointer moved to another smart account");
|
|
638
|
+
}
|
|
639
|
+
await reconcileAfterMove(env, owner, smartAccount);
|
|
640
|
+
return "already_moved";
|
|
641
|
+
}
|
|
642
|
+
if (!legacy.cid) return "no_pointer";
|
|
643
|
+
const target = await readPointer(env, smartAccount);
|
|
644
|
+
if (target.cid !== null && target.cid !== legacy.cid) return "conflict";
|
|
645
|
+
if (target.cid === null) await copyPointer(env, smartAccount, legacy.cid);
|
|
646
|
+
await markMoved(env, owner, smartAccount);
|
|
647
|
+
await reconcileAfterMove(env, owner, smartAccount);
|
|
648
|
+
return "moved";
|
|
649
|
+
}
|
|
601
650
|
export {
|
|
602
|
-
|
|
651
|
+
KEYED_MIGRATION_ENABLED,
|
|
652
|
+
PASSAGE_ENABLED,
|
|
653
|
+
canWritePointer,
|
|
603
654
|
clearIdentity,
|
|
604
655
|
clearSmartAccountCache,
|
|
605
656
|
computeSmartAccountAddress,
|
|
657
|
+
createPassageIO,
|
|
606
658
|
createVaultSmartAccount,
|
|
607
|
-
|
|
659
|
+
deriveIdentity,
|
|
608
660
|
formatSecretKeyForDisplay,
|
|
609
661
|
generateSecretKey,
|
|
610
662
|
getCachedSmartAccountAddress,
|
|
@@ -614,22 +666,22 @@ export {
|
|
|
614
666
|
getIdentityOwner,
|
|
615
667
|
getIdentitySmartAccount,
|
|
616
668
|
getIdentitySnapshot,
|
|
617
|
-
getOnChainSyncStatus,
|
|
618
669
|
getOwnerFromPassword,
|
|
619
670
|
getPimlicoApiKey,
|
|
620
671
|
getTargetChain,
|
|
672
|
+
identityState,
|
|
621
673
|
initIdentityFromPassword,
|
|
622
674
|
initIdentityFromSigner,
|
|
623
|
-
isCidRegistryConfigured,
|
|
624
675
|
isIdentityConnected,
|
|
625
676
|
isSmartAccountConfigured,
|
|
626
677
|
isValidSecretKey,
|
|
678
|
+
migrateKeyedPointer,
|
|
627
679
|
parseSecretKeyInput,
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
scheduleCidOnChainSync,
|
|
680
|
+
readPassageState,
|
|
681
|
+
runPassage,
|
|
682
|
+
runPassageWith,
|
|
632
683
|
signMessageWithIdentity,
|
|
633
684
|
subscribeIdentity,
|
|
634
|
-
useIdentity
|
|
685
|
+
useIdentity,
|
|
686
|
+
verifyAAAuth
|
|
635
687
|
};
|