@cavos/kit 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{Cavos-BH2_tOQ2.d.mts → Cavos-x9qFpOvJ.d.mts} +30 -1
- package/dist/{Cavos-BH2_tOQ2.d.ts → Cavos-x9qFpOvJ.d.ts} +30 -1
- package/dist/{chunk-BNGLH3Q3.mjs → chunk-F2J25XSL.mjs} +193 -24
- package/dist/chunk-F2J25XSL.mjs.map +1 -0
- package/dist/index.d.mts +8 -2
- package/dist/index.d.ts +8 -2
- package/dist/index.js +104 -19
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -88
- package/dist/index.mjs.map +1 -1
- package/dist/react/index.d.mts +36 -28
- package/dist/react/index.d.ts +36 -28
- package/dist/react/index.js +549 -100
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +330 -91
- package/dist/react/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-BNGLH3Q3.mjs.map +0 -1
package/dist/react/index.js
CHANGED
|
@@ -252,6 +252,17 @@ var StarknetAdapter = class {
|
|
|
252
252
|
});
|
|
253
253
|
return BigInt(res[0] ?? 0) !== 0n;
|
|
254
254
|
}
|
|
255
|
+
/** True if the account has at least one passkey registered as an approver.
|
|
256
|
+
* Lets the UI decide whether to offer passkey approval before prompting. */
|
|
257
|
+
async hasPasskeyApprover(accountAddress) {
|
|
258
|
+
if (!this.opts.provider) throw new Error("kit/starknet: provider required for reads");
|
|
259
|
+
const res = await this.opts.provider.callContract({
|
|
260
|
+
contractAddress: accountAddress,
|
|
261
|
+
entrypoint: "get_approver_count",
|
|
262
|
+
calldata: []
|
|
263
|
+
});
|
|
264
|
+
return BigInt(res[0] ?? 0) > 0n;
|
|
265
|
+
}
|
|
255
266
|
async getPasskeyNonce(accountAddress) {
|
|
256
267
|
if (!this.opts.provider) throw new Error("kit/starknet: provider required for reads");
|
|
257
268
|
const res = await this.opts.provider.callContract({
|
|
@@ -590,6 +601,11 @@ var SolanaAdapter = class {
|
|
|
590
601
|
return [precompileIx, ix];
|
|
591
602
|
}
|
|
592
603
|
/** Read whether `passkey` is a registered approver. */
|
|
604
|
+
/** True if the account has at least one passkey registered as an approver. */
|
|
605
|
+
async hasPasskeyApprover(account) {
|
|
606
|
+
const approvers = await this.fetchApprovers(new web3_js.PublicKey(account));
|
|
607
|
+
return approvers.length > 0;
|
|
608
|
+
}
|
|
593
609
|
async isApprover(account, passkey) {
|
|
594
610
|
const approvers = await this.fetchApprovers(new web3_js.PublicKey(account));
|
|
595
611
|
const target = Buffer.from(compressedPubkey(passkey)).toString("hex");
|
|
@@ -808,8 +824,8 @@ function u64le(n) {
|
|
|
808
824
|
new DataView(b.buffer, b.byteOffset, 8).setBigUint64(0, BigInt(n), true);
|
|
809
825
|
return b;
|
|
810
826
|
}
|
|
811
|
-
function readU64le(
|
|
812
|
-
return new DataView(
|
|
827
|
+
function readU64le(buf2, offset) {
|
|
828
|
+
return new DataView(buf2.buffer, buf2.byteOffset, buf2.length).getBigUint64(
|
|
813
829
|
offset,
|
|
814
830
|
true
|
|
815
831
|
);
|
|
@@ -1197,6 +1213,39 @@ var WORDLIST = [
|
|
|
1197
1213
|
"beach",
|
|
1198
1214
|
"dusk"
|
|
1199
1215
|
];
|
|
1216
|
+
function base64urlEncode(bytes) {
|
|
1217
|
+
let bin = "";
|
|
1218
|
+
for (const b of bytes) bin += String.fromCharCode(b);
|
|
1219
|
+
const b64 = typeof btoa !== "undefined" ? btoa(bin) : Buffer.from(bytes).toString("base64");
|
|
1220
|
+
return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
1221
|
+
}
|
|
1222
|
+
function derToRs(der) {
|
|
1223
|
+
let i = 0;
|
|
1224
|
+
if (der[i++] !== 48) throw new Error("kit/webauthn: bad DER (no SEQUENCE)");
|
|
1225
|
+
if (der[i] & 128) i += 1 + (der[i] & 127);
|
|
1226
|
+
else i += 1;
|
|
1227
|
+
if (der[i++] !== 2) throw new Error("kit/webauthn: bad DER (no r INTEGER)");
|
|
1228
|
+
const rlen = der[i++];
|
|
1229
|
+
const r = bytesToBigInt(der.subarray(i, i + rlen));
|
|
1230
|
+
i += rlen;
|
|
1231
|
+
if (der[i++] !== 2) throw new Error("kit/webauthn: bad DER (no s INTEGER)");
|
|
1232
|
+
const slen = der[i++];
|
|
1233
|
+
const s = bytesToBigInt(der.subarray(i, i + slen));
|
|
1234
|
+
return { r, s };
|
|
1235
|
+
}
|
|
1236
|
+
function spkiToPublicKey(spki) {
|
|
1237
|
+
const idx = spki.lastIndexOf(4, spki.length - 65);
|
|
1238
|
+
const start = spki.length - 65;
|
|
1239
|
+
const prefix = spki[start];
|
|
1240
|
+
if (prefix !== 4) {
|
|
1241
|
+
if (idx < 0) throw new Error("kit/webauthn: no uncompressed EC point in SPKI");
|
|
1242
|
+
return { x: bytesToBigInt(spki.subarray(idx + 1, idx + 33)), y: bytesToBigInt(spki.subarray(idx + 33, idx + 65)) };
|
|
1243
|
+
}
|
|
1244
|
+
return {
|
|
1245
|
+
x: bytesToBigInt(spki.subarray(start + 1, start + 33)),
|
|
1246
|
+
y: bytesToBigInt(spki.subarray(start + 33, start + 65))
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1200
1249
|
function batchChallenge(leaves) {
|
|
1201
1250
|
const total = leaves.reduce((n, l) => n + l.length, 0);
|
|
1202
1251
|
const cat = new Uint8Array(total);
|
|
@@ -1225,6 +1274,12 @@ function recoverCandidatePublicKeys(r, s, digest) {
|
|
|
1225
1274
|
}
|
|
1226
1275
|
return out;
|
|
1227
1276
|
}
|
|
1277
|
+
function challengeOffsetOf(clientDataJSON, challengeB64) {
|
|
1278
|
+
const text = new TextDecoder().decode(clientDataJSON);
|
|
1279
|
+
const idx = text.indexOf(challengeB64);
|
|
1280
|
+
if (idx < 0) throw new Error("kit/webauthn: challenge not found in clientDataJSON");
|
|
1281
|
+
return idx;
|
|
1282
|
+
}
|
|
1228
1283
|
|
|
1229
1284
|
// src/chains/solana/CavosSolana.ts
|
|
1230
1285
|
var CavosSolana = class _CavosSolana {
|
|
@@ -1239,6 +1294,8 @@ var CavosSolana = class _CavosSolana {
|
|
|
1239
1294
|
this.feePayer = feePayer;
|
|
1240
1295
|
/** Discriminant for the `CavosWallet` union — narrows `execute()` per chain. */
|
|
1241
1296
|
this.chain = "solana";
|
|
1297
|
+
/** True when this connect just created a brand-new account (first sign-up). */
|
|
1298
|
+
this.isNewAccount = false;
|
|
1242
1299
|
}
|
|
1243
1300
|
get publicKey() {
|
|
1244
1301
|
return this.devicePubkey;
|
|
@@ -1289,7 +1346,7 @@ var CavosSolana = class _CavosSolana {
|
|
|
1289
1346
|
}
|
|
1290
1347
|
await registry.register({ userId: identity.userId, address, initialSigner: devicePubkey });
|
|
1291
1348
|
const isSigner = await adapter.isAuthorizedSigner(address, devicePubkey);
|
|
1292
|
-
|
|
1349
|
+
const wallet = new _CavosSolana(
|
|
1293
1350
|
identity,
|
|
1294
1351
|
address,
|
|
1295
1352
|
isSigner ? "ready" : "needs-device-approval",
|
|
@@ -1299,6 +1356,8 @@ var CavosSolana = class _CavosSolana {
|
|
|
1299
1356
|
relayer,
|
|
1300
1357
|
opts.feePayer
|
|
1301
1358
|
);
|
|
1359
|
+
wallet.isNewAccount = !deployed && isSigner;
|
|
1360
|
+
return wallet;
|
|
1302
1361
|
}
|
|
1303
1362
|
/** Authorize an additional device signer (device-signed via precompile). */
|
|
1304
1363
|
async addSigner(pubkey) {
|
|
@@ -1325,6 +1384,16 @@ var CavosSolana = class _CavosSolana {
|
|
|
1325
1384
|
const transactionHash = await this.send(ixs);
|
|
1326
1385
|
return { transactionHash };
|
|
1327
1386
|
}
|
|
1387
|
+
/** True if this account already has a passkey enrolled as an approver, so a
|
|
1388
|
+
* new device can be approved with the passkey instead of the email flow. */
|
|
1389
|
+
async hasPasskey() {
|
|
1390
|
+
return this.adapter.hasPasskeyApprover(this.address);
|
|
1391
|
+
}
|
|
1392
|
+
/** Re-read (from chain) whether THIS device is now an authorized signer.
|
|
1393
|
+
* Used to poll for readiness after a passkey approval before it's indexed. */
|
|
1394
|
+
async isReady() {
|
|
1395
|
+
return this.adapter.isAuthorizedSigner(this.address, this.devicePubkey);
|
|
1396
|
+
}
|
|
1328
1397
|
/**
|
|
1329
1398
|
* From a fresh browser (status `needs-device-approval`), approve adding THIS
|
|
1330
1399
|
* device with the user's synced passkey. Gasless via the relayer — the bundle
|
|
@@ -1612,6 +1681,25 @@ var StellarAdapter = class {
|
|
|
1612
1681
|
]);
|
|
1613
1682
|
}
|
|
1614
1683
|
/** Read whether `passkey` is a registered approver (read-only simulation). */
|
|
1684
|
+
/** True if the account has at least one passkey registered as an approver.
|
|
1685
|
+
* Reads the contract's `approvers` view and checks the list is non-empty. */
|
|
1686
|
+
async hasPasskeyApprover(accountAddress, readSource) {
|
|
1687
|
+
if (!await this.isDeployed(accountAddress)) return false;
|
|
1688
|
+
const { Account: Account3, TransactionBuilder: TransactionBuilder2, BASE_FEE: BASE_FEE2 } = await import('@stellar/stellar-sdk');
|
|
1689
|
+
const src = new Account3(readSource, "0");
|
|
1690
|
+
const op = stellarSdk.Operation.invokeHostFunction({
|
|
1691
|
+
func: invokeFunc(accountAddress, "approvers", []),
|
|
1692
|
+
auth: []
|
|
1693
|
+
});
|
|
1694
|
+
const tx2 = new TransactionBuilder2(src, { fee: BASE_FEE2, networkPassphrase: this.passphrase }).addOperation(op).setTimeout(30).build();
|
|
1695
|
+
const sim = await this.server().simulateTransaction(tx2);
|
|
1696
|
+
if (stellarSdk.rpc.Api.isSimulationError(sim)) {
|
|
1697
|
+
throw new Error(`kit/stellar: approvers simulation failed: ${sim.error}`);
|
|
1698
|
+
}
|
|
1699
|
+
if (!sim.result?.retval) return false;
|
|
1700
|
+
const list = stellarSdk.scValToNative(sim.result.retval);
|
|
1701
|
+
return Array.isArray(list) && list.length > 0;
|
|
1702
|
+
}
|
|
1615
1703
|
async isApprover(accountAddress, passkey, readSource) {
|
|
1616
1704
|
if (!await this.isDeployed(accountAddress)) return false;
|
|
1617
1705
|
const { Account: Account3, TransactionBuilder: TransactionBuilder2, BASE_FEE: BASE_FEE2 } = await import('@stellar/stellar-sdk');
|
|
@@ -1816,6 +1904,8 @@ var CavosStellar = class _CavosStellar {
|
|
|
1816
1904
|
this.sourceKeypair = sourceKeypair;
|
|
1817
1905
|
/** Discriminant for the `CavosWallet` union — narrows `execute()` per chain. */
|
|
1818
1906
|
this.chain = "stellar";
|
|
1907
|
+
/** True when this connect just created a brand-new account (first sign-up). */
|
|
1908
|
+
this.isNewAccount = false;
|
|
1819
1909
|
}
|
|
1820
1910
|
get publicKey() {
|
|
1821
1911
|
return this.devicePubkey;
|
|
@@ -1844,13 +1934,16 @@ var CavosStellar = class _CavosStellar {
|
|
|
1844
1934
|
return build(existing.address, isSigner2 ? "ready" : "needs-device-approval");
|
|
1845
1935
|
}
|
|
1846
1936
|
const address = adapter.computeAddress(addressSeed, devicePubkey);
|
|
1847
|
-
|
|
1937
|
+
const wasDeployed = await adapter.isDeployed(address);
|
|
1938
|
+
if (!wasDeployed) {
|
|
1848
1939
|
const func = adapter.buildDeploy(addressSeed, devicePubkey);
|
|
1849
1940
|
await self.submitHostFunction(func, void 0);
|
|
1850
1941
|
}
|
|
1851
1942
|
await registry.register({ userId: identity.userId, address, initialSigner: devicePubkey });
|
|
1852
1943
|
const isSigner = await adapter.isAuthorizedSigner(address, devicePubkey, readSource);
|
|
1853
|
-
|
|
1944
|
+
const wallet = build(address, isSigner ? "ready" : "needs-device-approval");
|
|
1945
|
+
wallet.isNewAccount = !wasDeployed && isSigner;
|
|
1946
|
+
return wallet;
|
|
1854
1947
|
}
|
|
1855
1948
|
/** Authorize an additional device signer (device-signed via `__check_auth`). */
|
|
1856
1949
|
async addSigner(pubkey) {
|
|
@@ -1878,6 +1971,18 @@ var CavosStellar = class _CavosStellar {
|
|
|
1878
1971
|
const transactionHash = await this.submitHostFunction(func, this.address);
|
|
1879
1972
|
return { transactionHash };
|
|
1880
1973
|
}
|
|
1974
|
+
/** True if this account already has a passkey enrolled as an approver, so a
|
|
1975
|
+
* new device can be approved with the passkey instead of the email flow. */
|
|
1976
|
+
async hasPasskey() {
|
|
1977
|
+
const readSource = await this.resolveSource();
|
|
1978
|
+
return this.adapter.hasPasskeyApprover(this.address, readSource);
|
|
1979
|
+
}
|
|
1980
|
+
/** Re-read (from chain) whether THIS device is now an authorized signer.
|
|
1981
|
+
* Used to poll for readiness after a passkey approval before it's indexed. */
|
|
1982
|
+
async isReady() {
|
|
1983
|
+
const readSource = await this.resolveSource();
|
|
1984
|
+
return this.adapter.isAuthorizedSigner(this.address, this.devicePubkey, readSource);
|
|
1985
|
+
}
|
|
1881
1986
|
/**
|
|
1882
1987
|
* From a fresh browser (status `needs-device-approval`), approve adding THIS
|
|
1883
1988
|
* device using the user's synced passkey. Gasless via the relayer — the call
|
|
@@ -2187,6 +2292,9 @@ var Cavos = class _Cavos {
|
|
|
2187
2292
|
this.chain = "starknet";
|
|
2188
2293
|
/** Request id of the pending device-addition, when status is needs-device-approval. */
|
|
2189
2294
|
this.pendingRequestId = null;
|
|
2295
|
+
/** True when this connect just created & deployed a brand-new account (first
|
|
2296
|
+
* sign-up), so the UI can offer a one-time "secure your account" step. */
|
|
2297
|
+
this.isNewAccount = false;
|
|
2190
2298
|
}
|
|
2191
2299
|
/**
|
|
2192
2300
|
* Unified entry point. Pick a `chain` and an `network` environment; the kit
|
|
@@ -2282,7 +2390,7 @@ var Cavos = class _Cavos {
|
|
|
2282
2390
|
if (existing) {
|
|
2283
2391
|
const account2 = makeAccount(existing.address);
|
|
2284
2392
|
const isSigner2 = await adapter.isAuthorizedSigner(existing.address, devicePubkey);
|
|
2285
|
-
const
|
|
2393
|
+
const cavos2 = new _Cavos(
|
|
2286
2394
|
identity,
|
|
2287
2395
|
existing.address,
|
|
2288
2396
|
isSigner2 ? "ready" : "needs-device-approval",
|
|
@@ -2296,7 +2404,7 @@ var Cavos = class _Cavos {
|
|
|
2296
2404
|
const fresh = dedup && Date.now() - dedup.requestedAt < DEVICE_REQUEST_DEDUP_MS;
|
|
2297
2405
|
try {
|
|
2298
2406
|
if (fresh) {
|
|
2299
|
-
|
|
2407
|
+
cavos2.pendingRequestId = dedup.requestId;
|
|
2300
2408
|
} else {
|
|
2301
2409
|
const { requestId } = await recovery.requestDeviceAddition({
|
|
2302
2410
|
userId: identity.userId,
|
|
@@ -2304,14 +2412,14 @@ var Cavos = class _Cavos {
|
|
|
2304
2412
|
newSigner: devicePubkey,
|
|
2305
2413
|
...identity.email ? { email: identity.email } : {}
|
|
2306
2414
|
});
|
|
2307
|
-
|
|
2415
|
+
cavos2.pendingRequestId = requestId;
|
|
2308
2416
|
lastDeviceRequest.set(identity.userId, { requestId, requestedAt: Date.now() });
|
|
2309
2417
|
}
|
|
2310
2418
|
} catch (e) {
|
|
2311
2419
|
console.warn("[Cavos] requestDeviceAddition failed:", e);
|
|
2312
2420
|
}
|
|
2313
2421
|
}
|
|
2314
|
-
return
|
|
2422
|
+
return cavos2;
|
|
2315
2423
|
}
|
|
2316
2424
|
const address = adapter.computeAddress({ addressSeed, initialSigner: devicePubkey });
|
|
2317
2425
|
const account = makeAccount(address);
|
|
@@ -2342,7 +2450,7 @@ var Cavos = class _Cavos {
|
|
|
2342
2450
|
console.warn("[Cavos] isAuthorizedSigner read failed:", e);
|
|
2343
2451
|
isSigner = !alreadyDeployed;
|
|
2344
2452
|
}
|
|
2345
|
-
|
|
2453
|
+
const cavos = new _Cavos(
|
|
2346
2454
|
identity,
|
|
2347
2455
|
address,
|
|
2348
2456
|
isSigner ? "ready" : "needs-device-approval",
|
|
@@ -2351,6 +2459,8 @@ var Cavos = class _Cavos {
|
|
|
2351
2459
|
devicePubkey,
|
|
2352
2460
|
paymasterConfig
|
|
2353
2461
|
);
|
|
2462
|
+
cavos.isNewAccount = !alreadyDeployed && isSigner;
|
|
2463
|
+
return cavos;
|
|
2354
2464
|
}
|
|
2355
2465
|
/** This device's public key (e.g. to request addition to an existing wallet). */
|
|
2356
2466
|
get publicKey() {
|
|
@@ -2396,8 +2506,24 @@ var Cavos = class _Cavos {
|
|
|
2396
2506
|
const { transactionHash } = await this.execute([
|
|
2397
2507
|
this.adapter.buildAddApprover(this.address, pubkey)
|
|
2398
2508
|
]);
|
|
2509
|
+
try {
|
|
2510
|
+
await this.account.waitForTransaction(transactionHash);
|
|
2511
|
+
} catch (e) {
|
|
2512
|
+
console.warn("[Cavos] add_approver receipt wait failed:", e);
|
|
2513
|
+
}
|
|
2399
2514
|
return { transactionHash };
|
|
2400
2515
|
}
|
|
2516
|
+
/** True if this account already has a passkey enrolled as an approver, so a
|
|
2517
|
+
* new device can be approved with the passkey instead of the email flow. */
|
|
2518
|
+
async hasPasskey() {
|
|
2519
|
+
return this.adapter.hasPasskeyApprover(this.address);
|
|
2520
|
+
}
|
|
2521
|
+
/** Re-read (from chain) whether THIS device is now an authorized signer.
|
|
2522
|
+
* Cheap and side-effect free — used to poll for readiness after a passkey /
|
|
2523
|
+
* device approval submits, before the new signer is indexed. */
|
|
2524
|
+
async isReady() {
|
|
2525
|
+
return this.adapter.isAuthorizedSigner(this.address, this.devicePubkey);
|
|
2526
|
+
}
|
|
2401
2527
|
/**
|
|
2402
2528
|
* From a brand-new browser (status `needs-device-approval`), use the user's
|
|
2403
2529
|
* synced passkey to authorize adding THIS device — no trip back to an already-
|
|
@@ -2714,6 +2840,90 @@ function bytesToChunks(bytes) {
|
|
|
2714
2840
|
for (const b of bytes.subarray(0, 31)) w = w << 8n | BigInt(b);
|
|
2715
2841
|
return w;
|
|
2716
2842
|
}
|
|
2843
|
+
var PasskeySigner = class {
|
|
2844
|
+
constructor(opts = {}) {
|
|
2845
|
+
if (typeof window === "undefined" || !navigator.credentials) {
|
|
2846
|
+
throw new Error("kit/passkey: WebAuthn is only available in a browser");
|
|
2847
|
+
}
|
|
2848
|
+
this.rpId = opts.rpId ?? window.location.hostname;
|
|
2849
|
+
this.rpName = opts.rpName ?? this.rpId;
|
|
2850
|
+
if (isIpAddress(this.rpId)) {
|
|
2851
|
+
throw new Error(
|
|
2852
|
+
`kit/passkey: passkeys can't use an IP address as the domain ("${this.rpId}"). Use http://localhost, a real HTTPS domain, or a tunnel (cloudflared/ngrok) \u2014 or pass an explicit \`rpId\`. (The silent device key works over an IP; passkeys don't.)`
|
|
2853
|
+
);
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2856
|
+
/** True if this platform advertises a usable passkey (platform authenticator). */
|
|
2857
|
+
static async isSupported() {
|
|
2858
|
+
if (typeof window === "undefined" || !window.PublicKeyCredential) return false;
|
|
2859
|
+
try {
|
|
2860
|
+
return await window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
|
|
2861
|
+
} catch {
|
|
2862
|
+
return false;
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
/** Create a new synced passkey and return its P-256 public key. */
|
|
2866
|
+
async enroll(params) {
|
|
2867
|
+
const challenge = crypto.getRandomValues(new Uint8Array(32));
|
|
2868
|
+
const cred = await navigator.credentials.create({
|
|
2869
|
+
publicKey: {
|
|
2870
|
+
challenge: buf(challenge),
|
|
2871
|
+
rp: { id: this.rpId, name: this.rpName },
|
|
2872
|
+
user: {
|
|
2873
|
+
id: buf(userHandle(params.userId)),
|
|
2874
|
+
name: params.userName,
|
|
2875
|
+
displayName: params.displayName ?? params.userName
|
|
2876
|
+
},
|
|
2877
|
+
pubKeyCredParams: [{ type: "public-key", alg: -7 }],
|
|
2878
|
+
// ES256 (P-256)
|
|
2879
|
+
authenticatorSelection: {
|
|
2880
|
+
residentKey: "required",
|
|
2881
|
+
requireResidentKey: true,
|
|
2882
|
+
userVerification: "preferred"
|
|
2883
|
+
},
|
|
2884
|
+
attestation: "none"
|
|
2885
|
+
}
|
|
2886
|
+
});
|
|
2887
|
+
if (!cred) throw new Error("kit/passkey: enrollment cancelled");
|
|
2888
|
+
const response = cred.response;
|
|
2889
|
+
const spki = new Uint8Array(response.getPublicKey());
|
|
2890
|
+
return { publicKey: spkiToPublicKey(spki), credentialId: new Uint8Array(cred.rawId) };
|
|
2891
|
+
}
|
|
2892
|
+
/**
|
|
2893
|
+
* Produce a WebAuthn assertion over `challenge` (a 32-byte value the caller
|
|
2894
|
+
* derives from the signer being added + the on-chain nonce). Uses discoverable
|
|
2895
|
+
* credentials — no `allowCredentials` — so it works on a brand-new browser.
|
|
2896
|
+
*/
|
|
2897
|
+
async assert(challenge) {
|
|
2898
|
+
const cred = await navigator.credentials.get({
|
|
2899
|
+
publicKey: {
|
|
2900
|
+
challenge: buf(challenge),
|
|
2901
|
+
rpId: this.rpId,
|
|
2902
|
+
allowCredentials: [],
|
|
2903
|
+
userVerification: "preferred"
|
|
2904
|
+
}
|
|
2905
|
+
});
|
|
2906
|
+
if (!cred) throw new Error("kit/passkey: assertion cancelled");
|
|
2907
|
+
const response = cred.response;
|
|
2908
|
+
const authenticatorData = new Uint8Array(response.authenticatorData);
|
|
2909
|
+
const clientDataJSON = new Uint8Array(response.clientDataJSON);
|
|
2910
|
+
const { r, s } = derToRs(new Uint8Array(response.signature));
|
|
2911
|
+
const challengeOffset = challengeOffsetOf(clientDataJSON, base64urlEncode(challenge));
|
|
2912
|
+
return { authenticatorData, clientDataJSON, r, s, challengeOffset };
|
|
2913
|
+
}
|
|
2914
|
+
};
|
|
2915
|
+
function isIpAddress(host) {
|
|
2916
|
+
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(host)) return true;
|
|
2917
|
+
if (host.includes(":")) return true;
|
|
2918
|
+
return false;
|
|
2919
|
+
}
|
|
2920
|
+
function userHandle(userId) {
|
|
2921
|
+
const bytes = new TextEncoder().encode(userId);
|
|
2922
|
+
return bytes.length <= 64 ? bytes : sha256.sha256(bytes);
|
|
2923
|
+
}
|
|
2924
|
+
function buf(bytes) {
|
|
2925
|
+
return bytes.slice();
|
|
2926
|
+
}
|
|
2717
2927
|
var cavosLogoBase64 = "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";
|
|
2718
2928
|
var STYLE_ID = "cavos-modal-styles-v2";
|
|
2719
2929
|
function injectStyles() {
|
|
@@ -2817,6 +3027,17 @@ var EmailIcon = () => /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "16", heig
|
|
|
2817
3027
|
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "2", y: "4", width: "20", height: "16", rx: "2" }),
|
|
2818
3028
|
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "m22 7-8.97 5.7a1.94 1.94 0 01-2.06 0L2 7" })
|
|
2819
3029
|
] });
|
|
3030
|
+
var FingerprintIcon = ({ size = 22, color = "currentColor" }) => /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "1.7", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
3031
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M2 12C2 6.5 6.5 2 12 2a10 10 0 0 1 8 4" }),
|
|
3032
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M5 19.5C5.5 18 6 15 6 12c0-.7.12-1.37.34-2" }),
|
|
3033
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M17.29 21.02c.12-.6.43-2.3.5-3.02" }),
|
|
3034
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 10a2 2 0 0 0-2 2c0 1.02-.1 2.51-.26 4" }),
|
|
3035
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8.65 22c.21-.66.45-1.32.57-2" }),
|
|
3036
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M14 13.12c0 2.38 0 6.38-1 8.88" }),
|
|
3037
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M2 16h.01" }),
|
|
3038
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21.8 16c.2-2 .131-5.354 0-6" }),
|
|
3039
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9 6.8a6 6 0 0 1 9 5.2c0 .47 0 1.17-.02 2" })
|
|
3040
|
+
] });
|
|
2820
3041
|
var CavosLogo = ({ invert }) => /* @__PURE__ */ jsxRuntime.jsx("img", { src: `data:image/png;base64,${cavosLogoBase64}`, alt: "Cavos", style: { width: "auto", height: "16px", objectFit: "contain", opacity: 0.55, flexShrink: 0, filter: invert ? "invert(1)" : "none" } });
|
|
2821
3042
|
function Spinner({ size = 16, color = "#888" }) {
|
|
2822
3043
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -2967,7 +3188,11 @@ function CavosAuthModal({
|
|
|
2967
3188
|
authError,
|
|
2968
3189
|
clearAuthError,
|
|
2969
3190
|
resendDeviceApproval,
|
|
2970
|
-
recover
|
|
3191
|
+
recover,
|
|
3192
|
+
passkeySupported,
|
|
3193
|
+
enrollPasskeyDefault,
|
|
3194
|
+
approveDeviceWithPasskey,
|
|
3195
|
+
setupRecovery
|
|
2971
3196
|
} = useCavos();
|
|
2972
3197
|
const isMobile = useIsMobile();
|
|
2973
3198
|
const isLight = theme !== "dark";
|
|
@@ -2995,7 +3220,12 @@ function CavosAuthModal({
|
|
|
2995
3220
|
const [deviceResendBusy, setDeviceResendBusy] = react.useState(false);
|
|
2996
3221
|
const [recoverCode, setRecoverCode] = react.useState("");
|
|
2997
3222
|
const [recoverBusy, setRecoverBusy] = react.useState(false);
|
|
3223
|
+
const [pkBusy, setPkBusy] = react.useState(false);
|
|
3224
|
+
const [pkError, setPkError] = react.useState("");
|
|
3225
|
+
const [savedRecoveryCode, setSavedRecoveryCode] = react.useState("");
|
|
3226
|
+
const [copied, setCopied] = react.useState(false);
|
|
2998
3227
|
const doneHandledRef = react.useRef(false);
|
|
3228
|
+
const secureHandledRef = react.useRef(false);
|
|
2999
3229
|
const closeTimerRef = react.useRef(null);
|
|
3000
3230
|
const otpBoxRefs = react.useRef([null, null, null, null, null, null]);
|
|
3001
3231
|
const setOtpDigit = (i, raw) => {
|
|
@@ -3056,6 +3286,7 @@ function CavosAuthModal({
|
|
|
3056
3286
|
setOtpCode("");
|
|
3057
3287
|
setError("");
|
|
3058
3288
|
doneHandledRef.current = false;
|
|
3289
|
+
secureHandledRef.current = false;
|
|
3059
3290
|
}, 1600);
|
|
3060
3291
|
}, [onClose]);
|
|
3061
3292
|
react.useEffect(() => {
|
|
@@ -3066,13 +3297,26 @@ function CavosAuthModal({
|
|
|
3066
3297
|
doneHandledRef.current = false;
|
|
3067
3298
|
}
|
|
3068
3299
|
if (isAuthenticated && address && walletStatus.isReady) {
|
|
3069
|
-
|
|
3300
|
+
if (walletStatus.isNewAccount && !secureHandledRef.current) {
|
|
3301
|
+
if (screen !== "secure-account" && screen !== "recovery-code" && !doneHandledRef.current) {
|
|
3302
|
+
setScreen("secure-account");
|
|
3303
|
+
}
|
|
3304
|
+
} else {
|
|
3305
|
+
triggerDone(address);
|
|
3306
|
+
}
|
|
3070
3307
|
}
|
|
3071
|
-
if (walletStatus.
|
|
3072
|
-
|
|
3073
|
-
|
|
3308
|
+
if (walletStatus.needsDeviceApproval && // A deploy in progress owns the screen; never fight the deploying branch
|
|
3309
|
+
// above (both flags true would oscillate deploying ↔ approval → loop).
|
|
3310
|
+
!walletStatus.isDeploying && screen !== "recover" && screen !== "device-approval" && screen !== "passkey-approval") {
|
|
3311
|
+
if (walletStatus.hasPasskey && passkeySupported) {
|
|
3312
|
+
setScreen("passkey-approval");
|
|
3313
|
+
doneHandledRef.current = false;
|
|
3314
|
+
} else if (walletStatus.awaitingApproval) {
|
|
3315
|
+
setScreen("device-approval");
|
|
3316
|
+
doneHandledRef.current = false;
|
|
3317
|
+
}
|
|
3074
3318
|
}
|
|
3075
|
-
}, [open, isAuthenticated, address, walletStatus.isReady, walletStatus.isDeploying, walletStatus.awaitingApproval, screen, triggerDone]);
|
|
3319
|
+
}, [open, isAuthenticated, address, walletStatus.isReady, walletStatus.isDeploying, walletStatus.awaitingApproval, walletStatus.needsDeviceApproval, walletStatus.hasPasskey, walletStatus.isNewAccount, passkeySupported, screen, triggerDone]);
|
|
3076
3320
|
react.useEffect(() => () => {
|
|
3077
3321
|
if (closeTimerRef.current) clearTimeout(closeTimerRef.current);
|
|
3078
3322
|
}, []);
|
|
@@ -3082,9 +3326,61 @@ function CavosAuthModal({
|
|
|
3082
3326
|
setEmail("");
|
|
3083
3327
|
setOtpCode("");
|
|
3084
3328
|
setError("");
|
|
3329
|
+
setPkError("");
|
|
3330
|
+
setSavedRecoveryCode("");
|
|
3331
|
+
setCopied(false);
|
|
3085
3332
|
doneHandledRef.current = false;
|
|
3333
|
+
secureHandledRef.current = false;
|
|
3086
3334
|
onClose();
|
|
3087
3335
|
};
|
|
3336
|
+
const finishSecureStep = () => {
|
|
3337
|
+
secureHandledRef.current = true;
|
|
3338
|
+
if (address) triggerDone(address);
|
|
3339
|
+
};
|
|
3340
|
+
const handleSetupPasskey = async () => {
|
|
3341
|
+
setPkBusy(true);
|
|
3342
|
+
setPkError("");
|
|
3343
|
+
try {
|
|
3344
|
+
await enrollPasskeyDefault();
|
|
3345
|
+
finishSecureStep();
|
|
3346
|
+
} catch (e) {
|
|
3347
|
+
setPkError(e instanceof Error ? e.message : "We couldn't set up your passkey. Try again.");
|
|
3348
|
+
} finally {
|
|
3349
|
+
setPkBusy(false);
|
|
3350
|
+
}
|
|
3351
|
+
};
|
|
3352
|
+
const handleSaveRecovery = async () => {
|
|
3353
|
+
setPkBusy(true);
|
|
3354
|
+
setPkError("");
|
|
3355
|
+
try {
|
|
3356
|
+
const code = await setupRecovery();
|
|
3357
|
+
setSavedRecoveryCode(code);
|
|
3358
|
+
setScreen("recovery-code");
|
|
3359
|
+
} catch (e) {
|
|
3360
|
+
setPkError(e instanceof Error ? e.message : "We couldn't create your recovery phrase. Try again.");
|
|
3361
|
+
} finally {
|
|
3362
|
+
setPkBusy(false);
|
|
3363
|
+
}
|
|
3364
|
+
};
|
|
3365
|
+
const handleCopyRecovery = async () => {
|
|
3366
|
+
try {
|
|
3367
|
+
await navigator.clipboard.writeText(savedRecoveryCode);
|
|
3368
|
+
setCopied(true);
|
|
3369
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
3370
|
+
} catch {
|
|
3371
|
+
}
|
|
3372
|
+
};
|
|
3373
|
+
const handlePasskeyApprove = async () => {
|
|
3374
|
+
setPkBusy(true);
|
|
3375
|
+
setPkError("");
|
|
3376
|
+
try {
|
|
3377
|
+
await approveDeviceWithPasskey();
|
|
3378
|
+
setPkBusy(false);
|
|
3379
|
+
} catch (e) {
|
|
3380
|
+
setPkError(e instanceof Error ? e.message : "We couldn't verify your passkey. Try again or use email.");
|
|
3381
|
+
setPkBusy(false);
|
|
3382
|
+
}
|
|
3383
|
+
};
|
|
3088
3384
|
const handleOAuth = async (provider) => {
|
|
3089
3385
|
setError("");
|
|
3090
3386
|
setBusy(true);
|
|
@@ -3175,6 +3471,85 @@ function CavosAuthModal({
|
|
|
3175
3471
|
setDeviceResendBusy(false);
|
|
3176
3472
|
}
|
|
3177
3473
|
};
|
|
3474
|
+
if (screen === "secure-account") {
|
|
3475
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { style: overlay, "data-cavos-theme": theme, role: "dialog", "aria-modal": true, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { ...card, position: "relative" }, children: [
|
|
3476
|
+
isMobile && /* @__PURE__ */ jsxRuntime.jsx("div", { style: handle }),
|
|
3477
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: isMobile ? "28px 24px 28px" : "44px 24px 28px", textAlign: "center" }, children: [
|
|
3478
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { width: 48, height: 48, borderRadius: "50%", margin: "0 auto 16px", background: isLight ? "rgba(0,0,0,0.04)" : "rgba(255,255,255,0.06)", border: `1px solid ${isLight ? "rgba(0,0,0,0.08)" : "rgba(255,255,255,0.1)"}`, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: textColor, strokeWidth: "1.7", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
3479
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" }),
|
|
3480
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9 12l2 2 4-4" })
|
|
3481
|
+
] }) }),
|
|
3482
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { style: { margin: "0 0 8px", fontSize: "17px", fontWeight: 600, color: textColor, letterSpacing: "-0.02em" }, children: "Keep your account safe" }),
|
|
3483
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { style: { margin: "0 0 24px", fontSize: "13px", color: subTextColor, lineHeight: 1.55 }, children: "Set up a passkey so you can sign in on a new phone or computer in one tap. It takes a few seconds." }),
|
|
3484
|
+
pkError && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { background: errBg, border: `1px solid ${errBorder}`, borderRadius: "10px", padding: "9px 13px", fontSize: "13px", color: errColor, marginBottom: "14px", textAlign: "left" }, children: pkError }),
|
|
3485
|
+
passkeySupported ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
3486
|
+
/* @__PURE__ */ jsxRuntime.jsxs("button", { className: "cavos-primary-btn", style: { width: "100%", padding: "13px", borderRadius: "12px", border: "none", background: primaryColor, color: "#fff", fontSize: "14px", fontWeight: 600, cursor: pkBusy ? "default" : "pointer", fontFamily: "inherit", display: "flex", alignItems: "center", justifyContent: "center", gap: "9px", opacity: pkBusy ? 0.65 : 1, transition: "opacity 0.15s, transform 0.1s" }, onClick: handleSetupPasskey, disabled: pkBusy, children: [
|
|
3487
|
+
pkBusy ? /* @__PURE__ */ jsxRuntime.jsx(Spinner, { size: 16, color: "#fff" }) : /* @__PURE__ */ jsxRuntime.jsx(FingerprintIcon, { size: 18, color: "#fff" }),
|
|
3488
|
+
pkBusy ? "Setting up\u2026" : "Set up a passkey"
|
|
3489
|
+
] }),
|
|
3490
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { style: { margin: "10px 0 0", fontSize: "12px", color: subTextColor, lineHeight: 1.5 }, children: "Uses Face ID, Touch ID, or your device PIN." }),
|
|
3491
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: "12px", margin: "18px 0" }, children: [
|
|
3492
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "cavos-divider-line", style: { background: isLight ? "rgba(0,0,0,0.08)" : "rgba(255,255,255,0.1)" } }),
|
|
3493
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "11px", color: subTextColor, textTransform: "uppercase", letterSpacing: "0.06em" }, children: "or" }),
|
|
3494
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "cavos-divider-line", style: { background: isLight ? "rgba(0,0,0,0.08)" : "rgba(255,255,255,0.1)" } })
|
|
3495
|
+
] }),
|
|
3496
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { className: "cavos-submit-btn", style: { width: "100%", padding: "12px", borderRadius: "12px", border: inputBorder, background: "transparent", color: textColor, fontSize: "14px", fontWeight: 500, cursor: pkBusy ? "default" : "pointer", fontFamily: "inherit", transition: "background 0.15s", opacity: pkBusy ? 0.6 : 1 }, onClick: handleSaveRecovery, disabled: pkBusy, children: "Save a recovery phrase instead" })
|
|
3497
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs("button", { className: "cavos-primary-btn", style: { width: "100%", padding: "13px", borderRadius: "12px", border: "none", background: primaryColor, color: "#fff", fontSize: "14px", fontWeight: 600, cursor: pkBusy ? "default" : "pointer", fontFamily: "inherit", display: "flex", alignItems: "center", justifyContent: "center", gap: "9px", opacity: pkBusy ? 0.65 : 1, transition: "opacity 0.15s" }, onClick: handleSaveRecovery, disabled: pkBusy, children: [
|
|
3498
|
+
pkBusy && /* @__PURE__ */ jsxRuntime.jsx(Spinner, { size: 16, color: "#fff" }),
|
|
3499
|
+
pkBusy ? "Setting up\u2026" : "Save a recovery phrase"
|
|
3500
|
+
] }),
|
|
3501
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { className: "cavos-sub-btn", style: { background: "none", border: "none", cursor: pkBusy ? "default" : "pointer", fontSize: "13px", color: subTextColor, width: "100%", textAlign: "center", padding: "18px 0 0", fontFamily: "inherit", transition: "color 0.15s", opacity: pkBusy ? 0.6 : 1 }, onClick: finishSecureStep, disabled: pkBusy, children: "Skip for now" })
|
|
3502
|
+
] }),
|
|
3503
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: footer, children: [
|
|
3504
|
+
/* @__PURE__ */ jsxRuntime.jsx(CavosLogo, { invert: !isLight }),
|
|
3505
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Secured by Cavos" })
|
|
3506
|
+
] })
|
|
3507
|
+
] }) });
|
|
3508
|
+
}
|
|
3509
|
+
if (screen === "recovery-code") {
|
|
3510
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { style: overlay, "data-cavos-theme": theme, role: "dialog", "aria-modal": true, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { ...card, position: "relative" }, children: [
|
|
3511
|
+
isMobile && /* @__PURE__ */ jsxRuntime.jsx("div", { style: handle }),
|
|
3512
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: isMobile ? "28px 24px 28px" : "44px 24px 28px", textAlign: "center" }, children: [
|
|
3513
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { style: { margin: "0 0 8px", fontSize: "17px", fontWeight: 600, color: textColor, letterSpacing: "-0.02em" }, children: "Save your recovery phrase" }),
|
|
3514
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { style: { margin: "0 0 18px", fontSize: "13px", color: subTextColor, lineHeight: 1.55 }, children: "Write this down and keep it somewhere safe. It's the only way to get back in if you lose your devices, and we can't recover it for you." }),
|
|
3515
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { background: isLight ? "rgba(0,0,0,0.03)" : "rgba(255,255,255,0.05)", border: `1px solid ${isLight ? "rgba(0,0,0,0.08)" : "rgba(255,255,255,0.1)"}`, borderRadius: "12px", padding: "16px", marginBottom: "12px", fontSize: "14px", fontWeight: 500, color: textColor, wordBreak: "break-word", lineHeight: 1.6, fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", textAlign: "center" }, children: savedRecoveryCode }),
|
|
3516
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { className: "cavos-provider", style: { ...pBtn, marginBottom: "10px" }, onClick: handleCopyRecovery, children: /* @__PURE__ */ jsxRuntime.jsx("span", { children: copied ? "Copied \u2713" : "Copy to clipboard" }) }),
|
|
3517
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { className: "cavos-primary-btn", style: { width: "100%", padding: "12px", borderRadius: "12px", border: "none", background: primaryColor, color: "#fff", fontSize: "14px", fontWeight: 500, cursor: "pointer", fontFamily: "inherit", transition: "opacity 0.15s" }, onClick: finishSecureStep, children: "I've saved it" })
|
|
3518
|
+
] }),
|
|
3519
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: footer, children: [
|
|
3520
|
+
/* @__PURE__ */ jsxRuntime.jsx(CavosLogo, { invert: !isLight }),
|
|
3521
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Secured by Cavos" })
|
|
3522
|
+
] })
|
|
3523
|
+
] }) });
|
|
3524
|
+
}
|
|
3525
|
+
if (screen === "passkey-approval") {
|
|
3526
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { style: overlay, "data-cavos-theme": theme, role: "dialog", "aria-modal": true, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { ...card, position: "relative" }, children: [
|
|
3527
|
+
isMobile && /* @__PURE__ */ jsxRuntime.jsx("div", { style: handle }),
|
|
3528
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { className: "cavos-close", style: close, onClick: handleClose, "aria-label": "Close", children: /* @__PURE__ */ jsxRuntime.jsx(CloseX, {}) }),
|
|
3529
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: isMobile ? "28px 24px 32px" : "48px 24px 32px", textAlign: "center" }, children: [
|
|
3530
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { width: 48, height: 48, borderRadius: "50%", margin: "0 auto 16px", background: isLight ? "rgba(0,0,0,0.04)" : "rgba(255,255,255,0.06)", border: `1px solid ${isLight ? "rgba(0,0,0,0.08)" : "rgba(255,255,255,0.1)"}`, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsxRuntime.jsx(FingerprintIcon, { size: 24, color: textColor }) }),
|
|
3531
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { style: { margin: "0 0 8px", fontSize: "17px", fontWeight: 600, color: textColor, letterSpacing: "-0.02em" }, children: "Verify it's you" }),
|
|
3532
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { style: { margin: "0 0 20px", fontSize: "13px", color: subTextColor, lineHeight: 1.55 }, children: "Confirm with Face ID, Touch ID, or your device PIN to add this device to your account." }),
|
|
3533
|
+
pkError && /* @__PURE__ */ jsxRuntime.jsx("div", { style: { background: errBg, border: `1px solid ${errBorder}`, borderRadius: "10px", padding: "9px 13px", fontSize: "13px", color: errColor, marginBottom: "14px" }, children: pkError }),
|
|
3534
|
+
/* @__PURE__ */ jsxRuntime.jsxs("button", { className: "cavos-primary-btn", style: { width: "100%", padding: "12px", borderRadius: "12px", border: "none", background: primaryColor, color: "#fff", fontSize: "14px", fontWeight: 500, cursor: pkBusy ? "default" : "pointer", fontFamily: "inherit", display: "flex", alignItems: "center", justifyContent: "center", gap: "8px", opacity: pkBusy ? 0.65 : 1 }, onClick: handlePasskeyApprove, disabled: pkBusy, children: [
|
|
3535
|
+
pkBusy && /* @__PURE__ */ jsxRuntime.jsx(Spinner, { size: 15, color: "#fff" }),
|
|
3536
|
+
pkBusy ? "Verifying\u2026" : "Continue with passkey"
|
|
3537
|
+
] }),
|
|
3538
|
+
walletStatus.awaitingApproval && /* @__PURE__ */ jsxRuntime.jsx("button", { className: "cavos-sub-btn", style: { background: "none", border: "none", cursor: "pointer", fontSize: "13px", color: subTextColor, width: "100%", textAlign: "center", padding: "16px 0 0", fontFamily: "inherit", transition: "color 0.15s" }, onClick: () => {
|
|
3539
|
+
setScreen("device-approval");
|
|
3540
|
+
setPkError("");
|
|
3541
|
+
}, children: "Approve by email instead" }),
|
|
3542
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { className: "cavos-sub-btn", style: { background: "none", border: "none", cursor: "pointer", fontSize: "13px", color: subTextColor, width: "100%", textAlign: "center", padding: `${walletStatus.awaitingApproval ? "10px" : "16px"} 0 0`, fontFamily: "inherit", transition: "color 0.15s" }, onClick: () => {
|
|
3543
|
+
setScreen("recover");
|
|
3544
|
+
setPkError("");
|
|
3545
|
+
}, children: "Use a recovery phrase" })
|
|
3546
|
+
] }),
|
|
3547
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: footer, children: [
|
|
3548
|
+
/* @__PURE__ */ jsxRuntime.jsx(CavosLogo, { invert: !isLight }),
|
|
3549
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Secured by Cavos" })
|
|
3550
|
+
] })
|
|
3551
|
+
] }) });
|
|
3552
|
+
}
|
|
3178
3553
|
if (screen === "device-approval") {
|
|
3179
3554
|
const expired = walletStatus.awaitingApproval && !walletStatus.pendingRequestId;
|
|
3180
3555
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { style: overlay, "data-cavos-theme": theme, role: "dialog", "aria-modal": true, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { ...card, position: "relative" }, children: [
|
|
@@ -3234,7 +3609,7 @@ function CavosAuthModal({
|
|
|
3234
3609
|
}, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { ...card, position: "relative" }, children: [
|
|
3235
3610
|
isMobile && /* @__PURE__ */ jsxRuntime.jsx("div", { style: handle }),
|
|
3236
3611
|
/* @__PURE__ */ jsxRuntime.jsx("button", { className: "cavos-close", style: { ...close, left: "16px", right: "auto" }, onClick: () => {
|
|
3237
|
-
setScreen("device-approval");
|
|
3612
|
+
setScreen(walletStatus.hasPasskey && passkeySupported ? "passkey-approval" : "device-approval");
|
|
3238
3613
|
setError("");
|
|
3239
3614
|
setRecoverCode("");
|
|
3240
3615
|
}, "aria-label": "Back", children: /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M19 12H5M12 5l-7 7 7 7" }) }) }),
|
|
@@ -3473,23 +3848,36 @@ var INITIAL_STATUS = {
|
|
|
3473
3848
|
isReady: false,
|
|
3474
3849
|
needsDeviceApproval: false,
|
|
3475
3850
|
awaitingApproval: false,
|
|
3476
|
-
pendingRequestId: null
|
|
3851
|
+
pendingRequestId: null,
|
|
3852
|
+
hasPasskey: false,
|
|
3853
|
+
isNewAccount: false
|
|
3477
3854
|
};
|
|
3478
3855
|
function CavosProvider({ config, modal, children }) {
|
|
3479
3856
|
const [auth] = react.useState(
|
|
3480
3857
|
() => new CavosAuth({ appId: config.appId, backendUrl: config.authBackendUrl })
|
|
3481
3858
|
);
|
|
3482
|
-
const [
|
|
3859
|
+
const [wallet, setWallet] = react.useState(null);
|
|
3483
3860
|
const [identity, setIdentity] = react.useState(null);
|
|
3484
3861
|
const [walletStatus, setWalletStatus] = react.useState(INITIAL_STATUS);
|
|
3485
3862
|
const [isLoading, setIsLoading] = react.useState(false);
|
|
3486
3863
|
const [authError, setAuthError] = react.useState(null);
|
|
3487
3864
|
const [modalOpen, setModalOpen] = react.useState(false);
|
|
3865
|
+
const [passkeySupported, setPasskeySupported] = react.useState(false);
|
|
3488
3866
|
const [branding, setBranding] = react.useState({});
|
|
3867
|
+
react.useEffect(() => {
|
|
3868
|
+
let cancelled = false;
|
|
3869
|
+
PasskeySigner.isSupported().then((ok) => {
|
|
3870
|
+
if (!cancelled) setPasskeySupported(ok);
|
|
3871
|
+
}).catch(() => {
|
|
3872
|
+
});
|
|
3873
|
+
return () => {
|
|
3874
|
+
cancelled = true;
|
|
3875
|
+
};
|
|
3876
|
+
}, []);
|
|
3489
3877
|
const configRef = react.useRef(config);
|
|
3490
3878
|
react.useEffect(() => {
|
|
3491
3879
|
configRef.current = config;
|
|
3492
|
-
}
|
|
3880
|
+
});
|
|
3493
3881
|
react.useEffect(() => {
|
|
3494
3882
|
if (!config.appId || typeof window === "undefined") return;
|
|
3495
3883
|
const base = config.authBackendUrl ?? "https://cavos.xyz";
|
|
@@ -3503,41 +3891,52 @@ function CavosProvider({ config, modal, children }) {
|
|
|
3503
3891
|
const closeModal = react.useCallback(() => setModalOpen(false), []);
|
|
3504
3892
|
const clearAuthError = react.useCallback(() => setAuthError(null), []);
|
|
3505
3893
|
const resendDeviceApproval = react.useCallback(async () => {
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3894
|
+
const cfg = configRef.current;
|
|
3895
|
+
if (!identity || !wallet || wallet.chain !== "starknet" || !wallet.pendingRequestId) return;
|
|
3896
|
+
const backendUrl = cfg.authBackendUrl ?? "https://cavos.xyz";
|
|
3897
|
+
if (!cfg.appId) return;
|
|
3898
|
+
const recovery = new HttpRecoveryClient({ baseUrl: backendUrl, appId: cfg.appId });
|
|
3510
3899
|
await recovery.requestDeviceAddition({
|
|
3511
3900
|
userId: identity.userId,
|
|
3512
|
-
accountAddress:
|
|
3513
|
-
newSigner:
|
|
3901
|
+
accountAddress: wallet.address,
|
|
3902
|
+
newSigner: wallet.publicKey,
|
|
3514
3903
|
...identity.email ? { email: identity.email } : {}
|
|
3515
3904
|
});
|
|
3516
|
-
}, [identity,
|
|
3517
|
-
const connect = react.useCallback(async (id) => {
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3905
|
+
}, [identity, wallet]);
|
|
3906
|
+
const connect = react.useCallback(async (id, opts) => {
|
|
3907
|
+
const cfg = configRef.current;
|
|
3908
|
+
if (!opts?.silent) setWalletStatus({ ...INITIAL_STATUS, isDeploying: true });
|
|
3909
|
+
const w = await Cavos.connect({
|
|
3910
|
+
chain: cfg.chain ?? "starknet",
|
|
3911
|
+
network: cfg.network,
|
|
3522
3912
|
identity: id,
|
|
3523
|
-
appSalt:
|
|
3524
|
-
...
|
|
3525
|
-
...
|
|
3526
|
-
...
|
|
3527
|
-
...
|
|
3913
|
+
appSalt: cfg.appSalt,
|
|
3914
|
+
...cfg.paymasterApiKey ? { paymasterApiKey: cfg.paymasterApiKey } : {},
|
|
3915
|
+
...cfg.appId ? { appId: cfg.appId } : {},
|
|
3916
|
+
...cfg.authBackendUrl ? { backendUrl: cfg.authBackendUrl } : {},
|
|
3917
|
+
...cfg.rpcUrl ? { rpcUrl: cfg.rpcUrl } : {}
|
|
3528
3918
|
});
|
|
3529
|
-
|
|
3919
|
+
setWallet(w);
|
|
3530
3920
|
setIdentity(id);
|
|
3531
|
-
const pendingRequestId =
|
|
3921
|
+
const pendingRequestId = w.chain === "starknet" ? w.pendingRequestId : null;
|
|
3922
|
+
let hasPasskey = false;
|
|
3923
|
+
if (w.status === "needs-device-approval") {
|
|
3924
|
+
try {
|
|
3925
|
+
hasPasskey = await w.hasPasskey();
|
|
3926
|
+
} catch {
|
|
3927
|
+
}
|
|
3928
|
+
}
|
|
3532
3929
|
setWalletStatus({
|
|
3533
3930
|
isDeploying: false,
|
|
3534
|
-
isReady:
|
|
3535
|
-
needsDeviceApproval:
|
|
3536
|
-
awaitingApproval:
|
|
3537
|
-
pendingRequestId
|
|
3931
|
+
isReady: w.status === "ready",
|
|
3932
|
+
needsDeviceApproval: w.status === "needs-device-approval",
|
|
3933
|
+
awaitingApproval: w.status === "needs-device-approval" && !!pendingRequestId,
|
|
3934
|
+
pendingRequestId,
|
|
3935
|
+
hasPasskey,
|
|
3936
|
+
isNewAccount: w.isNewAccount
|
|
3538
3937
|
});
|
|
3539
|
-
modal?.onSuccess?.(
|
|
3540
|
-
return
|
|
3938
|
+
modal?.onSuccess?.(w.address);
|
|
3939
|
+
return w;
|
|
3541
3940
|
}, [modal]);
|
|
3542
3941
|
react.useEffect(() => {
|
|
3543
3942
|
if (typeof window === "undefined") return;
|
|
@@ -3589,75 +3988,122 @@ function CavosProvider({ config, modal, children }) {
|
|
|
3589
3988
|
await connect(id);
|
|
3590
3989
|
}, [auth, connect]);
|
|
3591
3990
|
const execute = react.useCallback(async (calls) => {
|
|
3592
|
-
if (!
|
|
3593
|
-
if (
|
|
3991
|
+
if (!wallet) throw new Error("Not logged in");
|
|
3992
|
+
if (wallet.chain !== "starknet") {
|
|
3594
3993
|
throw new Error(
|
|
3595
|
-
"kit: useCavos().execute(calls) is Starknet-only. On Solana use the `wallet` handle: wallet.execute(amount, dest)."
|
|
3994
|
+
"kit: useCavos().execute(calls) is Starknet-only. On Solana/Stellar use the `wallet` handle: wallet.execute(amount, dest)."
|
|
3596
3995
|
);
|
|
3597
3996
|
}
|
|
3598
|
-
return
|
|
3599
|
-
}, [
|
|
3997
|
+
return wallet.execute(calls);
|
|
3998
|
+
}, [wallet]);
|
|
3600
3999
|
const addSigner = react.useCallback(
|
|
3601
4000
|
async (pubkey) => {
|
|
3602
|
-
if (!
|
|
3603
|
-
if (
|
|
3604
|
-
throw new Error("kit: addSigner via useCavos() is Starknet-only; use the `wallet` handle on
|
|
4001
|
+
if (!wallet) throw new Error("Not logged in");
|
|
4002
|
+
if (wallet.chain !== "starknet") {
|
|
4003
|
+
throw new Error("kit: addSigner via useCavos() is Starknet-only; use the `wallet` handle on other chains.");
|
|
3605
4004
|
}
|
|
3606
|
-
return
|
|
4005
|
+
return wallet.addSigner(pubkey);
|
|
3607
4006
|
},
|
|
3608
|
-
[
|
|
4007
|
+
[wallet]
|
|
3609
4008
|
);
|
|
3610
4009
|
const enrollPasskey = react.useCallback(
|
|
3611
4010
|
async (passkey, params) => {
|
|
3612
|
-
if (!
|
|
3613
|
-
return
|
|
4011
|
+
if (!wallet) throw new Error("Not logged in");
|
|
4012
|
+
return wallet.enrollPasskey(passkey, params);
|
|
3614
4013
|
},
|
|
3615
|
-
[
|
|
4014
|
+
[wallet]
|
|
3616
4015
|
);
|
|
3617
|
-
const
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
4016
|
+
const rpName = branding.appName ?? modal?.appName ?? "Cavos";
|
|
4017
|
+
const enrollPasskeyDefault = react.useCallback(async () => {
|
|
4018
|
+
if (!wallet || !identity) throw new Error("Not logged in");
|
|
4019
|
+
if (wallet.status !== "ready") throw new Error("kit: no ready device to enroll a passkey on");
|
|
4020
|
+
const passkey = new PasskeySigner({ rpName });
|
|
4021
|
+
await wallet.enrollPasskey(passkey, {
|
|
4022
|
+
userId: identity.userId,
|
|
4023
|
+
userName: identity.email ?? identity.userId,
|
|
4024
|
+
...identity.email ? { displayName: identity.email } : {}
|
|
4025
|
+
});
|
|
4026
|
+
}, [wallet, identity, rpName]);
|
|
4027
|
+
const approveDeviceWithPasskey = react.useCallback(async () => {
|
|
4028
|
+
if (!wallet || !identity) throw new Error("Not logged in");
|
|
4029
|
+
if (wallet.status !== "needs-device-approval") {
|
|
4030
|
+
await connect(identity);
|
|
4031
|
+
return;
|
|
4032
|
+
}
|
|
4033
|
+
const passkey = new PasskeySigner({ rpName });
|
|
4034
|
+
if (wallet.chain === "starknet") {
|
|
4035
|
+
await wallet.approveThisDeviceWithPasskey({ passkey });
|
|
4036
|
+
} else {
|
|
4037
|
+
await wallet.approveThisDeviceWithPasskey(passkey);
|
|
4038
|
+
}
|
|
4039
|
+
setWalletStatus((s) => ({ ...s, isDeploying: true, needsDeviceApproval: false, awaitingApproval: false }));
|
|
4040
|
+
const deadline = Date.now() + 6e4;
|
|
4041
|
+
for (; ; ) {
|
|
4042
|
+
let ready = false;
|
|
4043
|
+
try {
|
|
4044
|
+
ready = await wallet.isReady();
|
|
4045
|
+
} catch {
|
|
3622
4046
|
}
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
4047
|
+
if (ready) break;
|
|
4048
|
+
if (Date.now() > deadline) {
|
|
4049
|
+
setWalletStatus((s) => ({ ...s, isDeploying: false, needsDeviceApproval: true }));
|
|
4050
|
+
throw new Error(
|
|
4051
|
+
"Your device is being added, but it's taking longer than usual. Please try again in a moment."
|
|
4052
|
+
);
|
|
4053
|
+
}
|
|
4054
|
+
await new Promise((r) => setTimeout(r, 3e3));
|
|
4055
|
+
}
|
|
4056
|
+
await connect(identity, { silent: true });
|
|
4057
|
+
}, [wallet, identity, rpName, connect]);
|
|
3628
4058
|
const setupRecovery = react.useCallback(async () => {
|
|
3629
|
-
if (!
|
|
4059
|
+
if (!wallet) throw new Error("Not logged in");
|
|
3630
4060
|
const code = generateRecoveryCode();
|
|
3631
|
-
await
|
|
4061
|
+
await wallet.setupRecovery(code);
|
|
3632
4062
|
return code;
|
|
3633
|
-
}, [
|
|
4063
|
+
}, [wallet]);
|
|
3634
4064
|
const recover = react.useCallback(async (code) => {
|
|
3635
4065
|
if (!identity) throw new Error("Sign in first so we know which account to recover.");
|
|
4066
|
+
const cfg = configRef.current;
|
|
3636
4067
|
setAuthError(null);
|
|
3637
4068
|
setWalletStatus({ ...INITIAL_STATUS, isDeploying: true });
|
|
3638
4069
|
try {
|
|
3639
|
-
const chain =
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
4070
|
+
const chain = cfg.chain ?? "starknet";
|
|
4071
|
+
let w;
|
|
4072
|
+
if (chain === "solana") {
|
|
4073
|
+
w = await CavosSolana.recover({
|
|
4074
|
+
code,
|
|
4075
|
+
identity,
|
|
4076
|
+
network: cfg.network === "mainnet" ? "solana-mainnet" : "solana-devnet",
|
|
4077
|
+
appSalt: cfg.appSalt,
|
|
4078
|
+
...cfg.appId ? { appId: cfg.appId } : {},
|
|
4079
|
+
...cfg.authBackendUrl ? { backendUrl: cfg.authBackendUrl } : {},
|
|
4080
|
+
...cfg.rpcUrl ? { rpcUrl: cfg.rpcUrl } : {}
|
|
4081
|
+
});
|
|
4082
|
+
} else if (chain === "stellar") {
|
|
4083
|
+
w = await CavosStellar.recover({
|
|
4084
|
+
code,
|
|
4085
|
+
identity,
|
|
4086
|
+
network: cfg.network === "mainnet" ? "stellar-mainnet" : "stellar-testnet",
|
|
4087
|
+
appSalt: cfg.appSalt,
|
|
4088
|
+
...cfg.appId ? { appId: cfg.appId } : {},
|
|
4089
|
+
...cfg.authBackendUrl ? { backendUrl: cfg.authBackendUrl } : {},
|
|
4090
|
+
...cfg.rpcUrl ? { rpcUrl: cfg.rpcUrl } : {}
|
|
4091
|
+
});
|
|
4092
|
+
} else {
|
|
4093
|
+
w = await Cavos.recover({
|
|
4094
|
+
code,
|
|
4095
|
+
identity,
|
|
4096
|
+
network: cfg.network,
|
|
4097
|
+
appSalt: cfg.appSalt,
|
|
4098
|
+
paymasterApiKey: cfg.paymasterApiKey ?? "",
|
|
4099
|
+
...cfg.appId ? { appId: cfg.appId } : {},
|
|
4100
|
+
...cfg.authBackendUrl ? { backendUrl: cfg.authBackendUrl } : {},
|
|
4101
|
+
...cfg.rpcUrl ? { rpcUrl: cfg.rpcUrl } : {}
|
|
4102
|
+
});
|
|
4103
|
+
}
|
|
4104
|
+
setWallet(w);
|
|
3659
4105
|
setWalletStatus({ ...INITIAL_STATUS, isReady: true });
|
|
3660
|
-
modal?.onSuccess?.(
|
|
4106
|
+
modal?.onSuccess?.(w.address);
|
|
3661
4107
|
} catch (e) {
|
|
3662
4108
|
const msg = e instanceof Error ? e.message : "Recovery failed. Check your code and try again.";
|
|
3663
4109
|
setAuthError(msg);
|
|
@@ -3667,9 +4113,10 @@ function CavosProvider({ config, modal, children }) {
|
|
|
3667
4113
|
}, [identity, modal]);
|
|
3668
4114
|
react.useEffect(() => {
|
|
3669
4115
|
if (!walletStatus.awaitingApproval || !walletStatus.pendingRequestId || !identity) return;
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
const
|
|
4116
|
+
const cfg = configRef.current;
|
|
4117
|
+
if (!cfg.appId) return;
|
|
4118
|
+
const backendUrl = cfg.authBackendUrl ?? "https://cavos.xyz";
|
|
4119
|
+
const recovery = new HttpRecoveryClient({ baseUrl: backendUrl, appId: cfg.appId });
|
|
3673
4120
|
const requestId = walletStatus.pendingRequestId;
|
|
3674
4121
|
let cancelled = false;
|
|
3675
4122
|
const tick = async () => {
|
|
@@ -3693,7 +4140,7 @@ function CavosProvider({ config, modal, children }) {
|
|
|
3693
4140
|
};
|
|
3694
4141
|
}, [walletStatus.awaitingApproval, walletStatus.pendingRequestId, identity, connect]);
|
|
3695
4142
|
const logout = react.useCallback(() => {
|
|
3696
|
-
|
|
4143
|
+
setWallet(null);
|
|
3697
4144
|
setIdentity(null);
|
|
3698
4145
|
setWalletStatus(INITIAL_STATUS);
|
|
3699
4146
|
setAuthError(null);
|
|
@@ -3701,11 +4148,11 @@ function CavosProvider({ config, modal, children }) {
|
|
|
3701
4148
|
const value = {
|
|
3702
4149
|
openModal,
|
|
3703
4150
|
closeModal,
|
|
3704
|
-
isAuthenticated: !!
|
|
4151
|
+
isAuthenticated: !!wallet,
|
|
3705
4152
|
user: identity ? { userId: identity.userId, email: identity.email, provider: identity.provider } : null,
|
|
3706
4153
|
chain: config.chain ?? "starknet",
|
|
3707
|
-
wallet
|
|
3708
|
-
address:
|
|
4154
|
+
wallet,
|
|
4155
|
+
address: wallet?.address ?? null,
|
|
3709
4156
|
walletStatus,
|
|
3710
4157
|
isLoading,
|
|
3711
4158
|
authError,
|
|
@@ -3718,7 +4165,9 @@ function CavosProvider({ config, modal, children }) {
|
|
|
3718
4165
|
execute,
|
|
3719
4166
|
addSigner,
|
|
3720
4167
|
enrollPasskey,
|
|
3721
|
-
|
|
4168
|
+
passkeySupported,
|
|
4169
|
+
enrollPasskeyDefault,
|
|
4170
|
+
approveDeviceWithPasskey,
|
|
3722
4171
|
resendDeviceApproval,
|
|
3723
4172
|
setupRecovery,
|
|
3724
4173
|
recover,
|