@cavos/kit 0.0.3 → 0.0.5
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-D20EtgOK.d.mts} +312 -182
- package/dist/{Cavos-BH2_tOQ2.d.ts → Cavos-D20EtgOK.d.ts} +312 -182
- package/dist/{chunk-BNGLH3Q3.mjs → chunk-M5BGBODC.mjs} +1660 -582
- package/dist/chunk-M5BGBODC.mjs.map +1 -0
- package/dist/index.d.mts +293 -89
- package/dist/index.d.ts +293 -89
- package/dist/index.js +1601 -593
- 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 +42 -29
- package/dist/react/index.d.ts +42 -29
- package/dist/react/index.js +2002 -655
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +353 -92
- 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
|
@@ -172,7 +172,7 @@ var CAVOS_PAYMASTER_URL = {
|
|
|
172
172
|
mainnet: "https://paymaster.cavos.xyz"
|
|
173
173
|
};
|
|
174
174
|
var DEVICE_ACCOUNT_CLASS_HASH = {
|
|
175
|
-
sepolia: "
|
|
175
|
+
sepolia: "0x765f000b7021577aa0d6c206fb3d8ac73571686b3e76b9dc9b6d59a372e8c2c",
|
|
176
176
|
mainnet: "0x1840aded59e8a0d2b440a134cb9079a7fc11b06c77f58ed189ab436a034ca6a"
|
|
177
177
|
};
|
|
178
178
|
|
|
@@ -182,21 +182,34 @@ var StarknetAdapter = class {
|
|
|
182
182
|
this.opts = opts;
|
|
183
183
|
this.chain = "starknet";
|
|
184
184
|
}
|
|
185
|
-
|
|
185
|
+
/**
|
|
186
|
+
* Deterministic address = f(addressSeed) ONLY. The device pubkey is NOT
|
|
187
|
+
* part of the derivation — anti-squatting is the integrator's responsibility
|
|
188
|
+
* (keep `appSalt` secret; deploy on first login). This makes the address
|
|
189
|
+
* recomputable by the user from (userId, appSalt) alone, even after losing
|
|
190
|
+
* every device.
|
|
191
|
+
*
|
|
192
|
+
* `initialSigner` in `ComputeAddressParams` is IGNORED on Starknet (kept in
|
|
193
|
+
* the shared type for Solana/Stellar, which still include it).
|
|
194
|
+
*/
|
|
195
|
+
computeAddress({ addressSeed, salt }) {
|
|
186
196
|
return starknet.hash.calculateContractAddressFromHash(
|
|
187
197
|
starknet.num.toHex(salt ?? addressSeed),
|
|
188
198
|
this.opts.classHash,
|
|
189
|
-
this.constructorCalldata(addressSeed
|
|
199
|
+
this.constructorCalldata(addressSeed),
|
|
190
200
|
0
|
|
191
201
|
// deployerAddress 0 => deterministic counterfactual address
|
|
192
202
|
);
|
|
193
203
|
}
|
|
194
|
-
/**
|
|
195
|
-
*
|
|
196
|
-
*
|
|
204
|
+
/**
|
|
205
|
+
* UDC deploy call. The constructor takes ONLY the seed — no device pubkey —
|
|
206
|
+
* so the account is born with no signers. The caller MUST follow up with
|
|
207
|
+
* `buildInitialize` in the same multicall (or a separate tx) to register the
|
|
208
|
+
* first device signer; otherwise the account is unusable.
|
|
209
|
+
*/
|
|
197
210
|
buildDeploy(params) {
|
|
198
211
|
const salt = params.salt ?? params.addressSeed;
|
|
199
|
-
const calldata = this.constructorCalldata(params.addressSeed
|
|
212
|
+
const calldata = this.constructorCalldata(params.addressSeed);
|
|
200
213
|
return [
|
|
201
214
|
{
|
|
202
215
|
contractAddress: UDC_ADDRESS,
|
|
@@ -212,9 +225,22 @@ var StarknetAdapter = class {
|
|
|
212
225
|
}
|
|
213
226
|
];
|
|
214
227
|
}
|
|
215
|
-
/** Constructor calldata: [address_seed
|
|
216
|
-
constructorCalldata(addressSeed
|
|
217
|
-
return [starknet.num.toHex(addressSeed)
|
|
228
|
+
/** Constructor calldata: [address_seed]. Device pubkey is registered post-deploy via initialize. */
|
|
229
|
+
constructorCalldata(addressSeed) {
|
|
230
|
+
return [starknet.num.toHex(addressSeed)];
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* `initialize` call: registers the first device signer. Callable only while
|
|
234
|
+
* the account has no signers (one-shot). In production this is bundled with
|
|
235
|
+
* the UDC deploy in a single sponsored multicall — see `connectStarknet`.
|
|
236
|
+
* Anti-squatting is NOT enforced on-chain.
|
|
237
|
+
*/
|
|
238
|
+
buildInitialize(accountAddress, devicePubkey) {
|
|
239
|
+
return {
|
|
240
|
+
contractAddress: accountAddress,
|
|
241
|
+
entrypoint: "initialize",
|
|
242
|
+
calldata: pubkeyCalldata(devicePubkey)
|
|
243
|
+
};
|
|
218
244
|
}
|
|
219
245
|
buildAddSigner(accountAddress, signer) {
|
|
220
246
|
return { contractAddress: accountAddress, entrypoint: "add_signer", calldata: pubkeyCalldata(signer) };
|
|
@@ -252,6 +278,17 @@ var StarknetAdapter = class {
|
|
|
252
278
|
});
|
|
253
279
|
return BigInt(res[0] ?? 0) !== 0n;
|
|
254
280
|
}
|
|
281
|
+
/** True if the account has at least one passkey registered as an approver.
|
|
282
|
+
* Lets the UI decide whether to offer passkey approval before prompting. */
|
|
283
|
+
async hasPasskeyApprover(accountAddress) {
|
|
284
|
+
if (!this.opts.provider) throw new Error("kit/starknet: provider required for reads");
|
|
285
|
+
const res = await this.opts.provider.callContract({
|
|
286
|
+
contractAddress: accountAddress,
|
|
287
|
+
entrypoint: "get_approver_count",
|
|
288
|
+
calldata: []
|
|
289
|
+
});
|
|
290
|
+
return BigInt(res[0] ?? 0) > 0n;
|
|
291
|
+
}
|
|
255
292
|
async getPasskeyNonce(accountAddress) {
|
|
256
293
|
if (!this.opts.provider) throw new Error("kit/starknet: provider required for reads");
|
|
257
294
|
const res = await this.opts.provider.callContract({
|
|
@@ -439,26 +476,30 @@ var SolanaAdapter = class {
|
|
|
439
476
|
this.chain = "solana";
|
|
440
477
|
this.programId = new web3_js.PublicKey(opts.programId ?? DEVICE_ACCOUNT_PROGRAM_ID);
|
|
441
478
|
}
|
|
442
|
-
/**
|
|
443
|
-
|
|
444
|
-
|
|
479
|
+
/**
|
|
480
|
+
* Deterministic account address: PDA of [ACCOUNT_SEED, addressSeed] — the
|
|
481
|
+
* device pubkey is NOT part of the seeds, so the address is recomputable
|
|
482
|
+
* from (userId, appSalt) alone. Anti-squatting is the integrator's
|
|
483
|
+
* responsibility (keep `appSalt` secret; deploy on first login).
|
|
484
|
+
*/
|
|
485
|
+
computeAddress(addressSeed) {
|
|
486
|
+
return this.pda(addressSeed).toBase58();
|
|
445
487
|
}
|
|
446
|
-
pda(addressSeed
|
|
488
|
+
pda(addressSeed) {
|
|
447
489
|
const [pda] = web3_js.PublicKey.findProgramAddressSync(
|
|
448
|
-
[
|
|
449
|
-
Buffer.from(ACCOUNT_SEED),
|
|
450
|
-
Buffer.from(addressSeed),
|
|
451
|
-
Buffer.from(initialCompressed.slice(1, 33))
|
|
452
|
-
// x-coordinate
|
|
453
|
-
],
|
|
490
|
+
[Buffer.from(ACCOUNT_SEED), Buffer.from(addressSeed)],
|
|
454
491
|
this.programId
|
|
455
492
|
);
|
|
456
493
|
return pda;
|
|
457
494
|
}
|
|
458
|
-
/**
|
|
495
|
+
/**
|
|
496
|
+
* `initialize` instruction: creates the account PDA and registers the first
|
|
497
|
+
* device signer. No attestation is required — anti-squatting is NOT enforced
|
|
498
|
+
* on-chain.
|
|
499
|
+
*/
|
|
459
500
|
buildInitialize(addressSeed, payer, initialSigner) {
|
|
460
501
|
const initialCompressed = compressedPubkey(initialSigner);
|
|
461
|
-
const account = this.pda(addressSeed
|
|
502
|
+
const account = this.pda(addressSeed);
|
|
462
503
|
const data = Buffer.concat([
|
|
463
504
|
anchorDiscriminator("initialize"),
|
|
464
505
|
Buffer.from(addressSeed),
|
|
@@ -466,7 +507,7 @@ var SolanaAdapter = class {
|
|
|
466
507
|
Buffer.from(initialCompressed)
|
|
467
508
|
// [u8;33]
|
|
468
509
|
]);
|
|
469
|
-
|
|
510
|
+
const programIx = new web3_js.TransactionInstruction({
|
|
470
511
|
programId: this.programId,
|
|
471
512
|
keys: [
|
|
472
513
|
{ pubkey: account, isSigner: false, isWritable: true },
|
|
@@ -475,6 +516,7 @@ var SolanaAdapter = class {
|
|
|
475
516
|
],
|
|
476
517
|
data
|
|
477
518
|
});
|
|
519
|
+
return [programIx];
|
|
478
520
|
}
|
|
479
521
|
/** `[precompile, add_signer]` bundle, authorized by an existing device signer. */
|
|
480
522
|
async buildAddSigner(account, newSigner) {
|
|
@@ -590,6 +632,11 @@ var SolanaAdapter = class {
|
|
|
590
632
|
return [precompileIx, ix];
|
|
591
633
|
}
|
|
592
634
|
/** Read whether `passkey` is a registered approver. */
|
|
635
|
+
/** True if the account has at least one passkey registered as an approver. */
|
|
636
|
+
async hasPasskeyApprover(account) {
|
|
637
|
+
const approvers = await this.fetchApprovers(new web3_js.PublicKey(account));
|
|
638
|
+
return approvers.length > 0;
|
|
639
|
+
}
|
|
593
640
|
async isApprover(account, passkey) {
|
|
594
641
|
const approvers = await this.fetchApprovers(new web3_js.PublicKey(account));
|
|
595
642
|
const target = Buffer.from(compressedPubkey(passkey)).toString("hex");
|
|
@@ -808,8 +855,8 @@ function u64le(n) {
|
|
|
808
855
|
new DataView(b.buffer, b.byteOffset, 8).setBigUint64(0, BigInt(n), true);
|
|
809
856
|
return b;
|
|
810
857
|
}
|
|
811
|
-
function readU64le(
|
|
812
|
-
return new DataView(
|
|
858
|
+
function readU64le(buf3, offset) {
|
|
859
|
+
return new DataView(buf3.buffer, buf3.byteOffset, buf3.length).getBigUint64(
|
|
813
860
|
offset,
|
|
814
861
|
true
|
|
815
862
|
);
|
|
@@ -868,11 +915,11 @@ var SolanaRelayer = class {
|
|
|
868
915
|
async send(instructions) {
|
|
869
916
|
const feePayer = await this.getFeePayer();
|
|
870
917
|
const { blockhash } = await this.opts.connection.getLatestBlockhash("confirmed");
|
|
871
|
-
const
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
const serialized =
|
|
918
|
+
const tx3 = new web3_js.Transaction();
|
|
919
|
+
tx3.feePayer = feePayer;
|
|
920
|
+
tx3.recentBlockhash = blockhash;
|
|
921
|
+
tx3.add(...instructions);
|
|
922
|
+
const serialized = tx3.serialize({ requireAllSignatures: false, verifySignatures: false }).toString("base64");
|
|
876
923
|
const res = await fetch(`${this.opts.baseUrl}/api/solana/relay`, {
|
|
877
924
|
method: "POST",
|
|
878
925
|
headers: { "Content-Type": "application/json" },
|
|
@@ -1197,6 +1244,39 @@ var WORDLIST = [
|
|
|
1197
1244
|
"beach",
|
|
1198
1245
|
"dusk"
|
|
1199
1246
|
];
|
|
1247
|
+
function base64urlEncode(bytes) {
|
|
1248
|
+
let bin = "";
|
|
1249
|
+
for (const b of bytes) bin += String.fromCharCode(b);
|
|
1250
|
+
const b64 = typeof btoa !== "undefined" ? btoa(bin) : Buffer.from(bytes).toString("base64");
|
|
1251
|
+
return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
1252
|
+
}
|
|
1253
|
+
function derToRs(der) {
|
|
1254
|
+
let i = 0;
|
|
1255
|
+
if (der[i++] !== 48) throw new Error("kit/webauthn: bad DER (no SEQUENCE)");
|
|
1256
|
+
if (der[i] & 128) i += 1 + (der[i] & 127);
|
|
1257
|
+
else i += 1;
|
|
1258
|
+
if (der[i++] !== 2) throw new Error("kit/webauthn: bad DER (no r INTEGER)");
|
|
1259
|
+
const rlen = der[i++];
|
|
1260
|
+
const r = bytesToBigInt(der.subarray(i, i + rlen));
|
|
1261
|
+
i += rlen;
|
|
1262
|
+
if (der[i++] !== 2) throw new Error("kit/webauthn: bad DER (no s INTEGER)");
|
|
1263
|
+
const slen = der[i++];
|
|
1264
|
+
const s = bytesToBigInt(der.subarray(i, i + slen));
|
|
1265
|
+
return { r, s };
|
|
1266
|
+
}
|
|
1267
|
+
function spkiToPublicKey(spki) {
|
|
1268
|
+
const idx = spki.lastIndexOf(4, spki.length - 65);
|
|
1269
|
+
const start = spki.length - 65;
|
|
1270
|
+
const prefix = spki[start];
|
|
1271
|
+
if (prefix !== 4) {
|
|
1272
|
+
if (idx < 0) throw new Error("kit/webauthn: no uncompressed EC point in SPKI");
|
|
1273
|
+
return { x: bytesToBigInt(spki.subarray(idx + 1, idx + 33)), y: bytesToBigInt(spki.subarray(idx + 33, idx + 65)) };
|
|
1274
|
+
}
|
|
1275
|
+
return {
|
|
1276
|
+
x: bytesToBigInt(spki.subarray(start + 1, start + 33)),
|
|
1277
|
+
y: bytesToBigInt(spki.subarray(start + 33, start + 65))
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1200
1280
|
function batchChallenge(leaves) {
|
|
1201
1281
|
const total = leaves.reduce((n, l) => n + l.length, 0);
|
|
1202
1282
|
const cat = new Uint8Array(total);
|
|
@@ -1225,6 +1305,12 @@ function recoverCandidatePublicKeys(r, s, digest) {
|
|
|
1225
1305
|
}
|
|
1226
1306
|
return out;
|
|
1227
1307
|
}
|
|
1308
|
+
function challengeOffsetOf(clientDataJSON, challengeB64) {
|
|
1309
|
+
const text = new TextDecoder().decode(clientDataJSON);
|
|
1310
|
+
const idx = text.indexOf(challengeB64);
|
|
1311
|
+
if (idx < 0) throw new Error("kit/webauthn: challenge not found in clientDataJSON");
|
|
1312
|
+
return idx;
|
|
1313
|
+
}
|
|
1228
1314
|
|
|
1229
1315
|
// src/chains/solana/CavosSolana.ts
|
|
1230
1316
|
var CavosSolana = class _CavosSolana {
|
|
@@ -1239,6 +1325,8 @@ var CavosSolana = class _CavosSolana {
|
|
|
1239
1325
|
this.feePayer = feePayer;
|
|
1240
1326
|
/** Discriminant for the `CavosWallet` union — narrows `execute()` per chain. */
|
|
1241
1327
|
this.chain = "solana";
|
|
1328
|
+
/** True when this connect just created a brand-new account (first sign-up). */
|
|
1329
|
+
this.isNewAccount = false;
|
|
1242
1330
|
}
|
|
1243
1331
|
get publicKey() {
|
|
1244
1332
|
return this.devicePubkey;
|
|
@@ -1273,23 +1361,23 @@ var CavosSolana = class _CavosSolana {
|
|
|
1273
1361
|
opts.feePayer
|
|
1274
1362
|
);
|
|
1275
1363
|
}
|
|
1276
|
-
const address = adapter.computeAddress(addressSeed
|
|
1364
|
+
const address = adapter.computeAddress(addressSeed);
|
|
1277
1365
|
const deployed = await connection.getAccountInfo(new web3_js.PublicKey(address)) !== null;
|
|
1278
1366
|
if (!deployed) {
|
|
1279
1367
|
if (relayer) {
|
|
1280
1368
|
const payer = await relayer.getFeePayer();
|
|
1281
|
-
const
|
|
1282
|
-
await relayer.send(
|
|
1369
|
+
const ixs = adapter.buildInitialize(addressSeed, payer.toBase58(), devicePubkey);
|
|
1370
|
+
await relayer.send(ixs);
|
|
1283
1371
|
} else if (opts.feePayer) {
|
|
1284
|
-
const
|
|
1285
|
-
await web3_js.sendAndConfirmTransaction(connection, new web3_js.Transaction().add(
|
|
1372
|
+
const ixs = adapter.buildInitialize(addressSeed, opts.feePayer.publicKey.toBase58(), devicePubkey);
|
|
1373
|
+
await web3_js.sendAndConfirmTransaction(connection, new web3_js.Transaction().add(...ixs), [opts.feePayer]);
|
|
1286
1374
|
} else {
|
|
1287
1375
|
throw new Error("kit/solana: a relayer (appId) or feePayer is required to initialize a new account");
|
|
1288
1376
|
}
|
|
1289
1377
|
}
|
|
1290
1378
|
await registry.register({ userId: identity.userId, address, initialSigner: devicePubkey });
|
|
1291
1379
|
const isSigner = await adapter.isAuthorizedSigner(address, devicePubkey);
|
|
1292
|
-
|
|
1380
|
+
const wallet = new _CavosSolana(
|
|
1293
1381
|
identity,
|
|
1294
1382
|
address,
|
|
1295
1383
|
isSigner ? "ready" : "needs-device-approval",
|
|
@@ -1299,6 +1387,8 @@ var CavosSolana = class _CavosSolana {
|
|
|
1299
1387
|
relayer,
|
|
1300
1388
|
opts.feePayer
|
|
1301
1389
|
);
|
|
1390
|
+
wallet.isNewAccount = !deployed && isSigner;
|
|
1391
|
+
return wallet;
|
|
1302
1392
|
}
|
|
1303
1393
|
/** Authorize an additional device signer (device-signed via precompile). */
|
|
1304
1394
|
async addSigner(pubkey) {
|
|
@@ -1325,6 +1415,16 @@ var CavosSolana = class _CavosSolana {
|
|
|
1325
1415
|
const transactionHash = await this.send(ixs);
|
|
1326
1416
|
return { transactionHash };
|
|
1327
1417
|
}
|
|
1418
|
+
/** True if this account already has a passkey enrolled as an approver, so a
|
|
1419
|
+
* new device can be approved with the passkey instead of the email flow. */
|
|
1420
|
+
async hasPasskey() {
|
|
1421
|
+
return this.adapter.hasPasskeyApprover(this.address);
|
|
1422
|
+
}
|
|
1423
|
+
/** Re-read (from chain) whether THIS device is now an authorized signer.
|
|
1424
|
+
* Used to poll for readiness after a passkey approval before it's indexed. */
|
|
1425
|
+
async isReady() {
|
|
1426
|
+
return this.adapter.isAuthorizedSigner(this.address, this.devicePubkey);
|
|
1427
|
+
}
|
|
1328
1428
|
/**
|
|
1329
1429
|
* From a fresh browser (status `needs-device-approval`), approve adding THIS
|
|
1330
1430
|
* device with the user's synced passkey. Gasless via the relayer — the bundle
|
|
@@ -1369,12 +1469,12 @@ var CavosSolana = class _CavosSolana {
|
|
|
1369
1469
|
return { transactionHash: await this.send(ixs) };
|
|
1370
1470
|
}
|
|
1371
1471
|
/** Move `amount` lamports out of the account to `destination` (device-signed). */
|
|
1372
|
-
async execute(amount, destination) {
|
|
1472
|
+
async execute(amount, destination, opts) {
|
|
1373
1473
|
if (this.status !== "ready") {
|
|
1374
1474
|
throw new Error("kit/solana: this device is not yet an authorized signer of the wallet");
|
|
1375
1475
|
}
|
|
1376
1476
|
const ixs = await this.adapter.buildExecuteTransfer(this.address, destination, amount);
|
|
1377
|
-
return this.send(ixs);
|
|
1477
|
+
return this.send(ixs, opts);
|
|
1378
1478
|
}
|
|
1379
1479
|
/**
|
|
1380
1480
|
* Run arbitrary CPI `instructions` with the account PDA as signer (device-
|
|
@@ -1384,14 +1484,16 @@ var CavosSolana = class _CavosSolana {
|
|
|
1384
1484
|
*
|
|
1385
1485
|
* What the relayer will sponsor is constrained by the app's Solana program
|
|
1386
1486
|
* allowlist (configured in the dashboard) — programs outside the allowlist are
|
|
1387
|
-
* rejected before co-signing.
|
|
1487
|
+
* rejected before co-signing. Pass `{ sponsored: false }` to bypass the relayer
|
|
1488
|
+
* and pay the fee from a configured `feePayer` (e.g. for allowlisted programs
|
|
1489
|
+
* the relayer rejects, or to test the device signature end-to-end).
|
|
1388
1490
|
*/
|
|
1389
|
-
async executeInstructions(instructions) {
|
|
1491
|
+
async executeInstructions(instructions, opts) {
|
|
1390
1492
|
if (this.status !== "ready") {
|
|
1391
1493
|
throw new Error("kit/solana: this device is not yet an authorized signer of the wallet");
|
|
1392
1494
|
}
|
|
1393
1495
|
const ixs = await this.adapter.buildExecute(this.address, instructions);
|
|
1394
|
-
return this.send(ixs);
|
|
1496
|
+
return this.send(ixs, opts);
|
|
1395
1497
|
}
|
|
1396
1498
|
/**
|
|
1397
1499
|
* Register the backup signer derived from `code` as an authorized signer of this
|
|
@@ -1470,300 +1572,974 @@ var CavosSolana = class _CavosSolana {
|
|
|
1470
1572
|
opts.feePayer
|
|
1471
1573
|
);
|
|
1472
1574
|
}
|
|
1473
|
-
|
|
1474
|
-
|
|
1575
|
+
/**
|
|
1576
|
+
* Submit a built instruction bundle. Sponsored by default (relayer pays the
|
|
1577
|
+
* fee); pass `{ sponsored: false }` to self-fund via the configured `feePayer`.
|
|
1578
|
+
* The device signature is embedded inside the secp256r1 precompile instruction,
|
|
1579
|
+
* NOT applied as a Solana tx signature — so switching only changes who pays,
|
|
1580
|
+
* never the signing identity.
|
|
1581
|
+
*/
|
|
1582
|
+
async send(ixs, opts) {
|
|
1583
|
+
const sponsored = opts?.sponsored !== false;
|
|
1584
|
+
if (sponsored && this.relayer) return this.relayer.send(ixs);
|
|
1475
1585
|
if (this.feePayer) {
|
|
1476
1586
|
return web3_js.sendAndConfirmTransaction(this.connection, new web3_js.Transaction().add(...ixs), [this.feePayer]);
|
|
1477
1587
|
}
|
|
1478
|
-
throw new Error(
|
|
1588
|
+
throw new Error(
|
|
1589
|
+
`kit/solana: cannot ${sponsored ? "sponsor" : "self-fund"} \u2014 no ${sponsored ? "relayer" : "feePayer"} configured`
|
|
1590
|
+
);
|
|
1479
1591
|
}
|
|
1480
1592
|
};
|
|
1481
1593
|
var defaultRegistry = new InMemoryWalletRegistry();
|
|
1482
1594
|
|
|
1483
1595
|
// src/chains/stellar/constants.ts
|
|
1484
|
-
var FACTORY_CONTRACT_ID = {
|
|
1485
|
-
// Re-deployed 2026-07-01 with the passkey-approval device-account wasm (batched
|
|
1486
|
-
// multi-chain challenge). The factory pins the wasm hash immutably, so a new
|
|
1487
|
-
// wasm needs a new factory → new account addresses; testnet has no prod wallets.
|
|
1488
|
-
"stellar-testnet": "CBCJIODXIEBOXXD66KCUCF7ZDYJARKI4ZIVQOVWPULOBH5XGNCDP6W3I",
|
|
1489
|
-
// Set once the factory is deployed to mainnet (its address differs — network id
|
|
1490
|
-
// is part of contract-address derivation).
|
|
1491
|
-
"stellar-mainnet": ""
|
|
1492
|
-
};
|
|
1493
1596
|
var STELLAR_NETWORKS = {
|
|
1494
1597
|
"stellar-testnet": {
|
|
1495
|
-
rpcUrl: "https://soroban-testnet.stellar.org",
|
|
1496
1598
|
passphrase: "Test SDF Network ; September 2015"
|
|
1497
1599
|
},
|
|
1498
1600
|
"stellar-mainnet": {
|
|
1499
|
-
rpcUrl: "https://soroban-rpc.mainnet.stellar.gateway.fm",
|
|
1500
1601
|
passphrase: "Public Global Stellar Network ; September 2015"
|
|
1501
1602
|
}
|
|
1502
1603
|
};
|
|
1503
|
-
var
|
|
1504
|
-
"stellar-testnet": "
|
|
1505
|
-
"stellar-mainnet": "
|
|
1604
|
+
var HORIZON_URL = {
|
|
1605
|
+
"stellar-testnet": "https://horizon-testnet.stellar.org",
|
|
1606
|
+
"stellar-mainnet": "https://horizon.stellar.org"
|
|
1506
1607
|
};
|
|
1507
1608
|
|
|
1609
|
+
// node_modules/@noble/ciphers/esm/utils.js
|
|
1610
|
+
function isBytes(a) {
|
|
1611
|
+
return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
|
|
1612
|
+
}
|
|
1613
|
+
function abytes(b, ...lengths) {
|
|
1614
|
+
if (!isBytes(b))
|
|
1615
|
+
throw new Error("Uint8Array expected");
|
|
1616
|
+
if (lengths.length > 0 && !lengths.includes(b.length))
|
|
1617
|
+
throw new Error("Uint8Array expected of length " + lengths + ", got length=" + b.length);
|
|
1618
|
+
}
|
|
1619
|
+
function aexists(instance, checkFinished = true) {
|
|
1620
|
+
if (instance.destroyed)
|
|
1621
|
+
throw new Error("Hash instance has been destroyed");
|
|
1622
|
+
if (checkFinished && instance.finished)
|
|
1623
|
+
throw new Error("Hash#digest() has already been called");
|
|
1624
|
+
}
|
|
1625
|
+
function aoutput(out, instance) {
|
|
1626
|
+
abytes(out);
|
|
1627
|
+
const min = instance.outputLen;
|
|
1628
|
+
if (out.length < min) {
|
|
1629
|
+
throw new Error("digestInto() expects output buffer of length at least " + min);
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
function u8(arr) {
|
|
1633
|
+
return new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);
|
|
1634
|
+
}
|
|
1635
|
+
function u32(arr) {
|
|
1636
|
+
return new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
|
|
1637
|
+
}
|
|
1638
|
+
function clean(...arrays) {
|
|
1639
|
+
for (let i = 0; i < arrays.length; i++) {
|
|
1640
|
+
arrays[i].fill(0);
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
function createView(arr) {
|
|
1644
|
+
return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
|
|
1645
|
+
}
|
|
1646
|
+
var isLE = /* @__PURE__ */ (() => new Uint8Array(new Uint32Array([287454020]).buffer)[0] === 68)();
|
|
1647
|
+
function utf8ToBytes(str) {
|
|
1648
|
+
if (typeof str !== "string")
|
|
1649
|
+
throw new Error("string expected");
|
|
1650
|
+
return new Uint8Array(new TextEncoder().encode(str));
|
|
1651
|
+
}
|
|
1652
|
+
function toBytes(data) {
|
|
1653
|
+
if (typeof data === "string")
|
|
1654
|
+
data = utf8ToBytes(data);
|
|
1655
|
+
else if (isBytes(data))
|
|
1656
|
+
data = copyBytes(data);
|
|
1657
|
+
else
|
|
1658
|
+
throw new Error("Uint8Array expected, got " + typeof data);
|
|
1659
|
+
return data;
|
|
1660
|
+
}
|
|
1661
|
+
function equalBytes(a, b) {
|
|
1662
|
+
if (a.length !== b.length)
|
|
1663
|
+
return false;
|
|
1664
|
+
let diff = 0;
|
|
1665
|
+
for (let i = 0; i < a.length; i++)
|
|
1666
|
+
diff |= a[i] ^ b[i];
|
|
1667
|
+
return diff === 0;
|
|
1668
|
+
}
|
|
1669
|
+
var wrapCipher = /* @__NO_SIDE_EFFECTS__ */ (params, constructor) => {
|
|
1670
|
+
function wrappedCipher(key, ...args) {
|
|
1671
|
+
abytes(key);
|
|
1672
|
+
if (!isLE)
|
|
1673
|
+
throw new Error("Non little-endian hardware is not yet supported");
|
|
1674
|
+
if (params.nonceLength !== void 0) {
|
|
1675
|
+
const nonce = args[0];
|
|
1676
|
+
if (!nonce)
|
|
1677
|
+
throw new Error("nonce / iv required");
|
|
1678
|
+
if (params.varSizeNonce)
|
|
1679
|
+
abytes(nonce);
|
|
1680
|
+
else
|
|
1681
|
+
abytes(nonce, params.nonceLength);
|
|
1682
|
+
}
|
|
1683
|
+
const tagl = params.tagLength;
|
|
1684
|
+
if (tagl && args[1] !== void 0) {
|
|
1685
|
+
abytes(args[1]);
|
|
1686
|
+
}
|
|
1687
|
+
const cipher = constructor(key, ...args);
|
|
1688
|
+
const checkOutput = (fnLength, output) => {
|
|
1689
|
+
if (output !== void 0) {
|
|
1690
|
+
if (fnLength !== 2)
|
|
1691
|
+
throw new Error("cipher output not supported");
|
|
1692
|
+
abytes(output);
|
|
1693
|
+
}
|
|
1694
|
+
};
|
|
1695
|
+
let called = false;
|
|
1696
|
+
const wrCipher = {
|
|
1697
|
+
encrypt(data, output) {
|
|
1698
|
+
if (called)
|
|
1699
|
+
throw new Error("cannot encrypt() twice with same key + nonce");
|
|
1700
|
+
called = true;
|
|
1701
|
+
abytes(data);
|
|
1702
|
+
checkOutput(cipher.encrypt.length, output);
|
|
1703
|
+
return cipher.encrypt(data, output);
|
|
1704
|
+
},
|
|
1705
|
+
decrypt(data, output) {
|
|
1706
|
+
abytes(data);
|
|
1707
|
+
if (tagl && data.length < tagl)
|
|
1708
|
+
throw new Error("invalid ciphertext length: smaller than tagLength=" + tagl);
|
|
1709
|
+
checkOutput(cipher.decrypt.length, output);
|
|
1710
|
+
return cipher.decrypt(data, output);
|
|
1711
|
+
}
|
|
1712
|
+
};
|
|
1713
|
+
return wrCipher;
|
|
1714
|
+
}
|
|
1715
|
+
Object.assign(wrappedCipher, params);
|
|
1716
|
+
return wrappedCipher;
|
|
1717
|
+
};
|
|
1718
|
+
function getOutput(expectedLength, out, onlyAligned = true) {
|
|
1719
|
+
if (out === void 0)
|
|
1720
|
+
return new Uint8Array(expectedLength);
|
|
1721
|
+
if (out.length !== expectedLength)
|
|
1722
|
+
throw new Error("invalid output length, expected " + expectedLength + ", got: " + out.length);
|
|
1723
|
+
if (onlyAligned && !isAligned32(out))
|
|
1724
|
+
throw new Error("invalid output, must be aligned");
|
|
1725
|
+
return out;
|
|
1726
|
+
}
|
|
1727
|
+
function setBigUint64(view, byteOffset, value, isLE2) {
|
|
1728
|
+
if (typeof view.setBigUint64 === "function")
|
|
1729
|
+
return view.setBigUint64(byteOffset, value, isLE2);
|
|
1730
|
+
const _32n = BigInt(32);
|
|
1731
|
+
const _u32_max = BigInt(4294967295);
|
|
1732
|
+
const wh = Number(value >> _32n & _u32_max);
|
|
1733
|
+
const wl = Number(value & _u32_max);
|
|
1734
|
+
const h = 0;
|
|
1735
|
+
const l = 4;
|
|
1736
|
+
view.setUint32(byteOffset + h, wh, isLE2);
|
|
1737
|
+
view.setUint32(byteOffset + l, wl, isLE2);
|
|
1738
|
+
}
|
|
1739
|
+
function u64Lengths(dataLength, aadLength, isLE2) {
|
|
1740
|
+
const num5 = new Uint8Array(16);
|
|
1741
|
+
const view = createView(num5);
|
|
1742
|
+
setBigUint64(view, 0, BigInt(aadLength), isLE2);
|
|
1743
|
+
setBigUint64(view, 8, BigInt(dataLength), isLE2);
|
|
1744
|
+
return num5;
|
|
1745
|
+
}
|
|
1746
|
+
function isAligned32(bytes) {
|
|
1747
|
+
return bytes.byteOffset % 4 === 0;
|
|
1748
|
+
}
|
|
1749
|
+
function copyBytes(bytes) {
|
|
1750
|
+
return Uint8Array.from(bytes);
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
// node_modules/@noble/ciphers/esm/_polyval.js
|
|
1754
|
+
var BLOCK_SIZE = 16;
|
|
1755
|
+
var ZEROS16 = /* @__PURE__ */ new Uint8Array(16);
|
|
1756
|
+
var ZEROS32 = u32(ZEROS16);
|
|
1757
|
+
var POLY = 225;
|
|
1758
|
+
var mul2 = (s0, s1, s2, s3) => {
|
|
1759
|
+
const hiBit = s3 & 1;
|
|
1760
|
+
return {
|
|
1761
|
+
s3: s2 << 31 | s3 >>> 1,
|
|
1762
|
+
s2: s1 << 31 | s2 >>> 1,
|
|
1763
|
+
s1: s0 << 31 | s1 >>> 1,
|
|
1764
|
+
s0: s0 >>> 1 ^ POLY << 24 & -(hiBit & 1)
|
|
1765
|
+
// reduce % poly
|
|
1766
|
+
};
|
|
1767
|
+
};
|
|
1768
|
+
var swapLE = (n) => (n >>> 0 & 255) << 24 | (n >>> 8 & 255) << 16 | (n >>> 16 & 255) << 8 | n >>> 24 & 255 | 0;
|
|
1769
|
+
function _toGHASHKey(k) {
|
|
1770
|
+
k.reverse();
|
|
1771
|
+
const hiBit = k[15] & 1;
|
|
1772
|
+
let carry = 0;
|
|
1773
|
+
for (let i = 0; i < k.length; i++) {
|
|
1774
|
+
const t = k[i];
|
|
1775
|
+
k[i] = t >>> 1 | carry;
|
|
1776
|
+
carry = (t & 1) << 7;
|
|
1777
|
+
}
|
|
1778
|
+
k[0] ^= -hiBit & 225;
|
|
1779
|
+
return k;
|
|
1780
|
+
}
|
|
1781
|
+
var estimateWindow = (bytes) => {
|
|
1782
|
+
if (bytes > 64 * 1024)
|
|
1783
|
+
return 8;
|
|
1784
|
+
if (bytes > 1024)
|
|
1785
|
+
return 4;
|
|
1786
|
+
return 2;
|
|
1787
|
+
};
|
|
1788
|
+
var GHASH = class {
|
|
1789
|
+
// We select bits per window adaptively based on expectedLength
|
|
1790
|
+
constructor(key, expectedLength) {
|
|
1791
|
+
this.blockLen = BLOCK_SIZE;
|
|
1792
|
+
this.outputLen = BLOCK_SIZE;
|
|
1793
|
+
this.s0 = 0;
|
|
1794
|
+
this.s1 = 0;
|
|
1795
|
+
this.s2 = 0;
|
|
1796
|
+
this.s3 = 0;
|
|
1797
|
+
this.finished = false;
|
|
1798
|
+
key = toBytes(key);
|
|
1799
|
+
abytes(key, 16);
|
|
1800
|
+
const kView = createView(key);
|
|
1801
|
+
let k0 = kView.getUint32(0, false);
|
|
1802
|
+
let k1 = kView.getUint32(4, false);
|
|
1803
|
+
let k2 = kView.getUint32(8, false);
|
|
1804
|
+
let k3 = kView.getUint32(12, false);
|
|
1805
|
+
const doubles = [];
|
|
1806
|
+
for (let i = 0; i < 128; i++) {
|
|
1807
|
+
doubles.push({ s0: swapLE(k0), s1: swapLE(k1), s2: swapLE(k2), s3: swapLE(k3) });
|
|
1808
|
+
({ s0: k0, s1: k1, s2: k2, s3: k3 } = mul2(k0, k1, k2, k3));
|
|
1809
|
+
}
|
|
1810
|
+
const W = estimateWindow(expectedLength || 1024);
|
|
1811
|
+
if (![1, 2, 4, 8].includes(W))
|
|
1812
|
+
throw new Error("ghash: invalid window size, expected 2, 4 or 8");
|
|
1813
|
+
this.W = W;
|
|
1814
|
+
const bits = 128;
|
|
1815
|
+
const windows = bits / W;
|
|
1816
|
+
const windowSize = this.windowSize = 2 ** W;
|
|
1817
|
+
const items = [];
|
|
1818
|
+
for (let w = 0; w < windows; w++) {
|
|
1819
|
+
for (let byte = 0; byte < windowSize; byte++) {
|
|
1820
|
+
let s0 = 0, s1 = 0, s2 = 0, s3 = 0;
|
|
1821
|
+
for (let j = 0; j < W; j++) {
|
|
1822
|
+
const bit = byte >>> W - j - 1 & 1;
|
|
1823
|
+
if (!bit)
|
|
1824
|
+
continue;
|
|
1825
|
+
const { s0: d0, s1: d1, s2: d2, s3: d3 } = doubles[W * w + j];
|
|
1826
|
+
s0 ^= d0, s1 ^= d1, s2 ^= d2, s3 ^= d3;
|
|
1827
|
+
}
|
|
1828
|
+
items.push({ s0, s1, s2, s3 });
|
|
1829
|
+
}
|
|
1830
|
+
}
|
|
1831
|
+
this.t = items;
|
|
1832
|
+
}
|
|
1833
|
+
_updateBlock(s0, s1, s2, s3) {
|
|
1834
|
+
s0 ^= this.s0, s1 ^= this.s1, s2 ^= this.s2, s3 ^= this.s3;
|
|
1835
|
+
const { W, t, windowSize } = this;
|
|
1836
|
+
let o0 = 0, o1 = 0, o2 = 0, o3 = 0;
|
|
1837
|
+
const mask = (1 << W) - 1;
|
|
1838
|
+
let w = 0;
|
|
1839
|
+
for (const num5 of [s0, s1, s2, s3]) {
|
|
1840
|
+
for (let bytePos = 0; bytePos < 4; bytePos++) {
|
|
1841
|
+
const byte = num5 >>> 8 * bytePos & 255;
|
|
1842
|
+
for (let bitPos = 8 / W - 1; bitPos >= 0; bitPos--) {
|
|
1843
|
+
const bit = byte >>> W * bitPos & mask;
|
|
1844
|
+
const { s0: e0, s1: e1, s2: e2, s3: e3 } = t[w * windowSize + bit];
|
|
1845
|
+
o0 ^= e0, o1 ^= e1, o2 ^= e2, o3 ^= e3;
|
|
1846
|
+
w += 1;
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
this.s0 = o0;
|
|
1851
|
+
this.s1 = o1;
|
|
1852
|
+
this.s2 = o2;
|
|
1853
|
+
this.s3 = o3;
|
|
1854
|
+
}
|
|
1855
|
+
update(data) {
|
|
1856
|
+
aexists(this);
|
|
1857
|
+
data = toBytes(data);
|
|
1858
|
+
abytes(data);
|
|
1859
|
+
const b32 = u32(data);
|
|
1860
|
+
const blocks = Math.floor(data.length / BLOCK_SIZE);
|
|
1861
|
+
const left = data.length % BLOCK_SIZE;
|
|
1862
|
+
for (let i = 0; i < blocks; i++) {
|
|
1863
|
+
this._updateBlock(b32[i * 4 + 0], b32[i * 4 + 1], b32[i * 4 + 2], b32[i * 4 + 3]);
|
|
1864
|
+
}
|
|
1865
|
+
if (left) {
|
|
1866
|
+
ZEROS16.set(data.subarray(blocks * BLOCK_SIZE));
|
|
1867
|
+
this._updateBlock(ZEROS32[0], ZEROS32[1], ZEROS32[2], ZEROS32[3]);
|
|
1868
|
+
clean(ZEROS32);
|
|
1869
|
+
}
|
|
1870
|
+
return this;
|
|
1871
|
+
}
|
|
1872
|
+
destroy() {
|
|
1873
|
+
const { t } = this;
|
|
1874
|
+
for (const elm of t) {
|
|
1875
|
+
elm.s0 = 0, elm.s1 = 0, elm.s2 = 0, elm.s3 = 0;
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
digestInto(out) {
|
|
1879
|
+
aexists(this);
|
|
1880
|
+
aoutput(out, this);
|
|
1881
|
+
this.finished = true;
|
|
1882
|
+
const { s0, s1, s2, s3 } = this;
|
|
1883
|
+
const o32 = u32(out);
|
|
1884
|
+
o32[0] = s0;
|
|
1885
|
+
o32[1] = s1;
|
|
1886
|
+
o32[2] = s2;
|
|
1887
|
+
o32[3] = s3;
|
|
1888
|
+
return out;
|
|
1889
|
+
}
|
|
1890
|
+
digest() {
|
|
1891
|
+
const res = new Uint8Array(BLOCK_SIZE);
|
|
1892
|
+
this.digestInto(res);
|
|
1893
|
+
this.destroy();
|
|
1894
|
+
return res;
|
|
1895
|
+
}
|
|
1896
|
+
};
|
|
1897
|
+
var Polyval = class extends GHASH {
|
|
1898
|
+
constructor(key, expectedLength) {
|
|
1899
|
+
key = toBytes(key);
|
|
1900
|
+
abytes(key);
|
|
1901
|
+
const ghKey = _toGHASHKey(copyBytes(key));
|
|
1902
|
+
super(ghKey, expectedLength);
|
|
1903
|
+
clean(ghKey);
|
|
1904
|
+
}
|
|
1905
|
+
update(data) {
|
|
1906
|
+
data = toBytes(data);
|
|
1907
|
+
aexists(this);
|
|
1908
|
+
const b32 = u32(data);
|
|
1909
|
+
const left = data.length % BLOCK_SIZE;
|
|
1910
|
+
const blocks = Math.floor(data.length / BLOCK_SIZE);
|
|
1911
|
+
for (let i = 0; i < blocks; i++) {
|
|
1912
|
+
this._updateBlock(swapLE(b32[i * 4 + 3]), swapLE(b32[i * 4 + 2]), swapLE(b32[i * 4 + 1]), swapLE(b32[i * 4 + 0]));
|
|
1913
|
+
}
|
|
1914
|
+
if (left) {
|
|
1915
|
+
ZEROS16.set(data.subarray(blocks * BLOCK_SIZE));
|
|
1916
|
+
this._updateBlock(swapLE(ZEROS32[3]), swapLE(ZEROS32[2]), swapLE(ZEROS32[1]), swapLE(ZEROS32[0]));
|
|
1917
|
+
clean(ZEROS32);
|
|
1918
|
+
}
|
|
1919
|
+
return this;
|
|
1920
|
+
}
|
|
1921
|
+
digestInto(out) {
|
|
1922
|
+
aexists(this);
|
|
1923
|
+
aoutput(out, this);
|
|
1924
|
+
this.finished = true;
|
|
1925
|
+
const { s0, s1, s2, s3 } = this;
|
|
1926
|
+
const o32 = u32(out);
|
|
1927
|
+
o32[0] = s0;
|
|
1928
|
+
o32[1] = s1;
|
|
1929
|
+
o32[2] = s2;
|
|
1930
|
+
o32[3] = s3;
|
|
1931
|
+
return out.reverse();
|
|
1932
|
+
}
|
|
1933
|
+
};
|
|
1934
|
+
function wrapConstructorWithKey(hashCons) {
|
|
1935
|
+
const hashC = (msg, key) => hashCons(key, msg.length).update(toBytes(msg)).digest();
|
|
1936
|
+
const tmp = hashCons(new Uint8Array(16), 0);
|
|
1937
|
+
hashC.outputLen = tmp.outputLen;
|
|
1938
|
+
hashC.blockLen = tmp.blockLen;
|
|
1939
|
+
hashC.create = (key, expectedLength) => hashCons(key, expectedLength);
|
|
1940
|
+
return hashC;
|
|
1941
|
+
}
|
|
1942
|
+
var ghash = wrapConstructorWithKey((key, expectedLength) => new GHASH(key, expectedLength));
|
|
1943
|
+
wrapConstructorWithKey((key, expectedLength) => new Polyval(key, expectedLength));
|
|
1944
|
+
|
|
1945
|
+
// node_modules/@noble/ciphers/esm/aes.js
|
|
1946
|
+
var BLOCK_SIZE2 = 16;
|
|
1947
|
+
var BLOCK_SIZE32 = 4;
|
|
1948
|
+
var EMPTY_BLOCK = /* @__PURE__ */ new Uint8Array(BLOCK_SIZE2);
|
|
1949
|
+
var POLY2 = 283;
|
|
1950
|
+
function mul22(n) {
|
|
1951
|
+
return n << 1 ^ POLY2 & -(n >> 7);
|
|
1952
|
+
}
|
|
1953
|
+
function mul(a, b) {
|
|
1954
|
+
let res = 0;
|
|
1955
|
+
for (; b > 0; b >>= 1) {
|
|
1956
|
+
res ^= a & -(b & 1);
|
|
1957
|
+
a = mul22(a);
|
|
1958
|
+
}
|
|
1959
|
+
return res;
|
|
1960
|
+
}
|
|
1961
|
+
var sbox = /* @__PURE__ */ (() => {
|
|
1962
|
+
const t = new Uint8Array(256);
|
|
1963
|
+
for (let i = 0, x = 1; i < 256; i++, x ^= mul22(x))
|
|
1964
|
+
t[i] = x;
|
|
1965
|
+
const box = new Uint8Array(256);
|
|
1966
|
+
box[0] = 99;
|
|
1967
|
+
for (let i = 0; i < 255; i++) {
|
|
1968
|
+
let x = t[255 - i];
|
|
1969
|
+
x |= x << 8;
|
|
1970
|
+
box[t[i]] = (x ^ x >> 4 ^ x >> 5 ^ x >> 6 ^ x >> 7 ^ 99) & 255;
|
|
1971
|
+
}
|
|
1972
|
+
clean(t);
|
|
1973
|
+
return box;
|
|
1974
|
+
})();
|
|
1975
|
+
var rotr32_8 = (n) => n << 24 | n >>> 8;
|
|
1976
|
+
var rotl32_8 = (n) => n << 8 | n >>> 24;
|
|
1977
|
+
function genTtable(sbox2, fn) {
|
|
1978
|
+
if (sbox2.length !== 256)
|
|
1979
|
+
throw new Error("Wrong sbox length");
|
|
1980
|
+
const T0 = new Uint32Array(256).map((_, j) => fn(sbox2[j]));
|
|
1981
|
+
const T1 = T0.map(rotl32_8);
|
|
1982
|
+
const T2 = T1.map(rotl32_8);
|
|
1983
|
+
const T3 = T2.map(rotl32_8);
|
|
1984
|
+
const T01 = new Uint32Array(256 * 256);
|
|
1985
|
+
const T23 = new Uint32Array(256 * 256);
|
|
1986
|
+
const sbox22 = new Uint16Array(256 * 256);
|
|
1987
|
+
for (let i = 0; i < 256; i++) {
|
|
1988
|
+
for (let j = 0; j < 256; j++) {
|
|
1989
|
+
const idx = i * 256 + j;
|
|
1990
|
+
T01[idx] = T0[i] ^ T1[j];
|
|
1991
|
+
T23[idx] = T2[i] ^ T3[j];
|
|
1992
|
+
sbox22[idx] = sbox2[i] << 8 | sbox2[j];
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
return { sbox: sbox2, sbox2: sbox22, T0, T1, T2, T3, T01, T23 };
|
|
1996
|
+
}
|
|
1997
|
+
var tableEncoding = /* @__PURE__ */ genTtable(sbox, (s) => mul(s, 3) << 24 | s << 16 | s << 8 | mul(s, 2));
|
|
1998
|
+
var xPowers = /* @__PURE__ */ (() => {
|
|
1999
|
+
const p = new Uint8Array(16);
|
|
2000
|
+
for (let i = 0, x = 1; i < 16; i++, x = mul22(x))
|
|
2001
|
+
p[i] = x;
|
|
2002
|
+
return p;
|
|
2003
|
+
})();
|
|
2004
|
+
function expandKeyLE(key) {
|
|
2005
|
+
abytes(key);
|
|
2006
|
+
const len = key.length;
|
|
2007
|
+
if (![16, 24, 32].includes(len))
|
|
2008
|
+
throw new Error("aes: invalid key size, should be 16, 24 or 32, got " + len);
|
|
2009
|
+
const { sbox2 } = tableEncoding;
|
|
2010
|
+
const toClean = [];
|
|
2011
|
+
if (!isAligned32(key))
|
|
2012
|
+
toClean.push(key = copyBytes(key));
|
|
2013
|
+
const k32 = u32(key);
|
|
2014
|
+
const Nk = k32.length;
|
|
2015
|
+
const subByte = (n) => applySbox(sbox2, n, n, n, n);
|
|
2016
|
+
const xk = new Uint32Array(len + 28);
|
|
2017
|
+
xk.set(k32);
|
|
2018
|
+
for (let i = Nk; i < xk.length; i++) {
|
|
2019
|
+
let t = xk[i - 1];
|
|
2020
|
+
if (i % Nk === 0)
|
|
2021
|
+
t = subByte(rotr32_8(t)) ^ xPowers[i / Nk - 1];
|
|
2022
|
+
else if (Nk > 6 && i % Nk === 4)
|
|
2023
|
+
t = subByte(t);
|
|
2024
|
+
xk[i] = xk[i - Nk] ^ t;
|
|
2025
|
+
}
|
|
2026
|
+
clean(...toClean);
|
|
2027
|
+
return xk;
|
|
2028
|
+
}
|
|
2029
|
+
function apply0123(T01, T23, s0, s1, s2, s3) {
|
|
2030
|
+
return T01[s0 << 8 & 65280 | s1 >>> 8 & 255] ^ T23[s2 >>> 8 & 65280 | s3 >>> 24 & 255];
|
|
2031
|
+
}
|
|
2032
|
+
function applySbox(sbox2, s0, s1, s2, s3) {
|
|
2033
|
+
return sbox2[s0 & 255 | s1 & 65280] | sbox2[s2 >>> 16 & 255 | s3 >>> 16 & 65280] << 16;
|
|
2034
|
+
}
|
|
2035
|
+
function encrypt(xk, s0, s1, s2, s3) {
|
|
2036
|
+
const { sbox2, T01, T23 } = tableEncoding;
|
|
2037
|
+
let k = 0;
|
|
2038
|
+
s0 ^= xk[k++], s1 ^= xk[k++], s2 ^= xk[k++], s3 ^= xk[k++];
|
|
2039
|
+
const rounds = xk.length / 4 - 2;
|
|
2040
|
+
for (let i = 0; i < rounds; i++) {
|
|
2041
|
+
const t02 = xk[k++] ^ apply0123(T01, T23, s0, s1, s2, s3);
|
|
2042
|
+
const t12 = xk[k++] ^ apply0123(T01, T23, s1, s2, s3, s0);
|
|
2043
|
+
const t22 = xk[k++] ^ apply0123(T01, T23, s2, s3, s0, s1);
|
|
2044
|
+
const t32 = xk[k++] ^ apply0123(T01, T23, s3, s0, s1, s2);
|
|
2045
|
+
s0 = t02, s1 = t12, s2 = t22, s3 = t32;
|
|
2046
|
+
}
|
|
2047
|
+
const t0 = xk[k++] ^ applySbox(sbox2, s0, s1, s2, s3);
|
|
2048
|
+
const t1 = xk[k++] ^ applySbox(sbox2, s1, s2, s3, s0);
|
|
2049
|
+
const t2 = xk[k++] ^ applySbox(sbox2, s2, s3, s0, s1);
|
|
2050
|
+
const t3 = xk[k++] ^ applySbox(sbox2, s3, s0, s1, s2);
|
|
2051
|
+
return { s0: t0, s1: t1, s2: t2, s3: t3 };
|
|
2052
|
+
}
|
|
2053
|
+
function ctr32(xk, isLE2, nonce, src, dst) {
|
|
2054
|
+
abytes(nonce, BLOCK_SIZE2);
|
|
2055
|
+
abytes(src);
|
|
2056
|
+
dst = getOutput(src.length, dst);
|
|
2057
|
+
const ctr = nonce;
|
|
2058
|
+
const c32 = u32(ctr);
|
|
2059
|
+
const view = createView(ctr);
|
|
2060
|
+
const src32 = u32(src);
|
|
2061
|
+
const dst32 = u32(dst);
|
|
2062
|
+
const ctrPos = 12;
|
|
2063
|
+
const srcLen = src.length;
|
|
2064
|
+
let ctrNum = view.getUint32(ctrPos, isLE2);
|
|
2065
|
+
let { s0, s1, s2, s3 } = encrypt(xk, c32[0], c32[1], c32[2], c32[3]);
|
|
2066
|
+
for (let i = 0; i + 4 <= src32.length; i += 4) {
|
|
2067
|
+
dst32[i + 0] = src32[i + 0] ^ s0;
|
|
2068
|
+
dst32[i + 1] = src32[i + 1] ^ s1;
|
|
2069
|
+
dst32[i + 2] = src32[i + 2] ^ s2;
|
|
2070
|
+
dst32[i + 3] = src32[i + 3] ^ s3;
|
|
2071
|
+
ctrNum = ctrNum + 1 >>> 0;
|
|
2072
|
+
view.setUint32(ctrPos, ctrNum, isLE2);
|
|
2073
|
+
({ s0, s1, s2, s3 } = encrypt(xk, c32[0], c32[1], c32[2], c32[3]));
|
|
2074
|
+
}
|
|
2075
|
+
const start = BLOCK_SIZE2 * Math.floor(src32.length / BLOCK_SIZE32);
|
|
2076
|
+
if (start < srcLen) {
|
|
2077
|
+
const b32 = new Uint32Array([s0, s1, s2, s3]);
|
|
2078
|
+
const buf3 = u8(b32);
|
|
2079
|
+
for (let i = start, pos = 0; i < srcLen; i++, pos++)
|
|
2080
|
+
dst[i] = src[i] ^ buf3[pos];
|
|
2081
|
+
clean(b32);
|
|
2082
|
+
}
|
|
2083
|
+
return dst;
|
|
2084
|
+
}
|
|
2085
|
+
function computeTag(fn, isLE2, key, data, AAD) {
|
|
2086
|
+
const aadLength = AAD ? AAD.length : 0;
|
|
2087
|
+
const h = fn.create(key, data.length + aadLength);
|
|
2088
|
+
if (AAD)
|
|
2089
|
+
h.update(AAD);
|
|
2090
|
+
const num5 = u64Lengths(8 * data.length, 8 * aadLength, isLE2);
|
|
2091
|
+
h.update(data);
|
|
2092
|
+
h.update(num5);
|
|
2093
|
+
const res = h.digest();
|
|
2094
|
+
clean(num5);
|
|
2095
|
+
return res;
|
|
2096
|
+
}
|
|
2097
|
+
var gcm = /* @__PURE__ */ wrapCipher({ blockSize: 16, nonceLength: 12, tagLength: 16, varSizeNonce: true }, function aesgcm(key, nonce, AAD) {
|
|
2098
|
+
if (nonce.length < 8)
|
|
2099
|
+
throw new Error("aes/gcm: invalid nonce length");
|
|
2100
|
+
const tagLength = 16;
|
|
2101
|
+
function _computeTag(authKey, tagMask, data) {
|
|
2102
|
+
const tag = computeTag(ghash, false, authKey, data, AAD);
|
|
2103
|
+
for (let i = 0; i < tagMask.length; i++)
|
|
2104
|
+
tag[i] ^= tagMask[i];
|
|
2105
|
+
return tag;
|
|
2106
|
+
}
|
|
2107
|
+
function deriveKeys() {
|
|
2108
|
+
const xk = expandKeyLE(key);
|
|
2109
|
+
const authKey = EMPTY_BLOCK.slice();
|
|
2110
|
+
const counter = EMPTY_BLOCK.slice();
|
|
2111
|
+
ctr32(xk, false, counter, counter, authKey);
|
|
2112
|
+
if (nonce.length === 12) {
|
|
2113
|
+
counter.set(nonce);
|
|
2114
|
+
} else {
|
|
2115
|
+
const nonceLen = EMPTY_BLOCK.slice();
|
|
2116
|
+
const view = createView(nonceLen);
|
|
2117
|
+
setBigUint64(view, 8, BigInt(nonce.length * 8), false);
|
|
2118
|
+
const g = ghash.create(authKey).update(nonce).update(nonceLen);
|
|
2119
|
+
g.digestInto(counter);
|
|
2120
|
+
g.destroy();
|
|
2121
|
+
}
|
|
2122
|
+
const tagMask = ctr32(xk, false, counter, EMPTY_BLOCK);
|
|
2123
|
+
return { xk, authKey, counter, tagMask };
|
|
2124
|
+
}
|
|
2125
|
+
return {
|
|
2126
|
+
encrypt(plaintext) {
|
|
2127
|
+
const { xk, authKey, counter, tagMask } = deriveKeys();
|
|
2128
|
+
const out = new Uint8Array(plaintext.length + tagLength);
|
|
2129
|
+
const toClean = [xk, authKey, counter, tagMask];
|
|
2130
|
+
if (!isAligned32(plaintext))
|
|
2131
|
+
toClean.push(plaintext = copyBytes(plaintext));
|
|
2132
|
+
ctr32(xk, false, counter, plaintext, out.subarray(0, plaintext.length));
|
|
2133
|
+
const tag = _computeTag(authKey, tagMask, out.subarray(0, out.length - tagLength));
|
|
2134
|
+
toClean.push(tag);
|
|
2135
|
+
out.set(tag, plaintext.length);
|
|
2136
|
+
clean(...toClean);
|
|
2137
|
+
return out;
|
|
2138
|
+
},
|
|
2139
|
+
decrypt(ciphertext) {
|
|
2140
|
+
const { xk, authKey, counter, tagMask } = deriveKeys();
|
|
2141
|
+
const toClean = [xk, authKey, tagMask, counter];
|
|
2142
|
+
if (!isAligned32(ciphertext))
|
|
2143
|
+
toClean.push(ciphertext = copyBytes(ciphertext));
|
|
2144
|
+
const data = ciphertext.subarray(0, -tagLength);
|
|
2145
|
+
const passedTag = ciphertext.subarray(-tagLength);
|
|
2146
|
+
const tag = _computeTag(authKey, tagMask, data);
|
|
2147
|
+
toClean.push(tag);
|
|
2148
|
+
if (!equalBytes(tag, passedTag))
|
|
2149
|
+
throw new Error("aes/gcm: invalid ghash tag");
|
|
2150
|
+
const out = ctr32(xk, false, counter, data);
|
|
2151
|
+
clean(...toClean);
|
|
2152
|
+
return out;
|
|
2153
|
+
}
|
|
2154
|
+
};
|
|
2155
|
+
});
|
|
2156
|
+
var NONCE_LEN = 12;
|
|
2157
|
+
var RECOVERY_INFO = "cavos-stellar-dek-recovery";
|
|
2158
|
+
var PASSKEY_INFO = "cavos-stellar-dek-passkey";
|
|
2159
|
+
var ECIES_INFO = "cavos-stellar-dek-ecies";
|
|
2160
|
+
var RECOVERY_PBKDF2_ITERATIONS = 21e4;
|
|
2161
|
+
var RECOVERY_KDF_SALT = "cavos-stellar-recovery-v1";
|
|
2162
|
+
function generateDEK() {
|
|
2163
|
+
return utils.randomBytes(32);
|
|
2164
|
+
}
|
|
2165
|
+
function sealControlSeed(controlSeed, dek) {
|
|
2166
|
+
const nonce = utils.randomBytes(NONCE_LEN);
|
|
2167
|
+
const ct = gcm(dek, nonce).encrypt(controlSeed);
|
|
2168
|
+
return concat(nonce, ct);
|
|
2169
|
+
}
|
|
2170
|
+
function openControlSeed(sealed, dek) {
|
|
2171
|
+
const { nonce, ct } = splitNonce(sealed);
|
|
2172
|
+
return gcm(dek, nonce).decrypt(ct);
|
|
2173
|
+
}
|
|
2174
|
+
function wrapDEK(dek, kek) {
|
|
2175
|
+
const nonce = utils.randomBytes(NONCE_LEN);
|
|
2176
|
+
return concat(nonce, gcm(kek, nonce).encrypt(dek));
|
|
2177
|
+
}
|
|
2178
|
+
function unwrapDEK(wrapped, kek) {
|
|
2179
|
+
const { nonce, ct } = splitNonce(wrapped);
|
|
2180
|
+
return gcm(kek, nonce).decrypt(ct);
|
|
2181
|
+
}
|
|
2182
|
+
function deriveRecoveryKEK(code) {
|
|
2183
|
+
const normalised = code.trim().replace(/\s+/g, " ").toLowerCase();
|
|
2184
|
+
if (!normalised) throw new Error("kit/stellar: recovery code is empty");
|
|
2185
|
+
const stretched = pbkdf2.pbkdf2(sha256.sha256, new TextEncoder().encode(normalised), new TextEncoder().encode(RECOVERY_KDF_SALT), {
|
|
2186
|
+
c: RECOVERY_PBKDF2_ITERATIONS,
|
|
2187
|
+
dkLen: 32
|
|
2188
|
+
});
|
|
2189
|
+
return hkdf.hkdf(sha256.sha256, stretched, void 0, RECOVERY_INFO, 32);
|
|
2190
|
+
}
|
|
2191
|
+
function derivePasskeyKEK(prfOutput) {
|
|
2192
|
+
if (prfOutput.length < 32) throw new Error("kit/stellar: passkey PRF output too short");
|
|
2193
|
+
return hkdf.hkdf(sha256.sha256, prfOutput, void 0, PASSKEY_INFO, 32);
|
|
2194
|
+
}
|
|
2195
|
+
function eciesWrapDEK(dek, recipientPubSec1) {
|
|
2196
|
+
const ephPriv = p256.p256.utils.randomPrivateKey();
|
|
2197
|
+
const ephPubCompressed = p256.p256.getPublicKey(ephPriv, true);
|
|
2198
|
+
const kek = eciesKEK(p256.p256.getSharedSecret(ephPriv, recipientPubSec1, false), ephPubCompressed);
|
|
2199
|
+
const wrapped = wrapDEK(dek, kek);
|
|
2200
|
+
return concat(ephPubCompressed, wrapped);
|
|
2201
|
+
}
|
|
2202
|
+
function eciesKEK(sharedUncompressed, ephPubCompressed) {
|
|
2203
|
+
return eciesKEKFromX(sharedUncompressed.subarray(1, 33), ephPubCompressed);
|
|
2204
|
+
}
|
|
2205
|
+
function eciesKEKFromX(sharedX, ephPubCompressed) {
|
|
2206
|
+
return hkdf.hkdf(sha256.sha256, sharedX, ephPubCompressed, ECIES_INFO, 32);
|
|
2207
|
+
}
|
|
2208
|
+
var DATA_ENTRY_MAX = 64;
|
|
2209
|
+
function chunkTo64(blob) {
|
|
2210
|
+
if (blob.length <= DATA_ENTRY_MAX) return [blob];
|
|
2211
|
+
const chunks = [];
|
|
2212
|
+
for (let i = 0; i < blob.length; i += DATA_ENTRY_MAX) {
|
|
2213
|
+
chunks.push(blob.subarray(i, i + DATA_ENTRY_MAX));
|
|
2214
|
+
}
|
|
2215
|
+
return chunks;
|
|
2216
|
+
}
|
|
2217
|
+
function unchunk(chunks) {
|
|
2218
|
+
const total = chunks.reduce((n, c) => n + c.length, 0);
|
|
2219
|
+
const out = new Uint8Array(total);
|
|
2220
|
+
let off = 0;
|
|
2221
|
+
for (const c of chunks) {
|
|
2222
|
+
out.set(c, off);
|
|
2223
|
+
off += c.length;
|
|
2224
|
+
}
|
|
2225
|
+
return out;
|
|
2226
|
+
}
|
|
2227
|
+
function concat(a, b) {
|
|
2228
|
+
const out = new Uint8Array(a.length + b.length);
|
|
2229
|
+
out.set(a, 0);
|
|
2230
|
+
out.set(b, a.length);
|
|
2231
|
+
return out;
|
|
2232
|
+
}
|
|
2233
|
+
function splitNonce(blob) {
|
|
2234
|
+
return { nonce: blob.subarray(0, NONCE_LEN), ct: blob.subarray(NONCE_LEN) };
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
// src/chains/stellar/datamap.ts
|
|
2238
|
+
var CT_BASE = "cv:ct";
|
|
2239
|
+
var PASSKEY_BASE = "cv:wp";
|
|
2240
|
+
var RECOVERY_BASE = "cv:wr";
|
|
2241
|
+
var VERSION_KEY = "cv:v";
|
|
2242
|
+
var ENVELOPE_VERSION = 1;
|
|
2243
|
+
function deviceBase(slot) {
|
|
2244
|
+
return `cv:wd:${slot}`;
|
|
2245
|
+
}
|
|
2246
|
+
function toDataEntries(env) {
|
|
2247
|
+
const out = {};
|
|
2248
|
+
writeChunked(out, CT_BASE, env.ct);
|
|
2249
|
+
for (const [slot, blob] of Object.entries(env.deviceWraps)) {
|
|
2250
|
+
writeChunked(out, deviceBase(slot), blob);
|
|
2251
|
+
}
|
|
2252
|
+
if (env.passkeyWrap) writeChunked(out, PASSKEY_BASE, env.passkeyWrap);
|
|
2253
|
+
if (env.recoveryWrap) writeChunked(out, RECOVERY_BASE, env.recoveryWrap);
|
|
2254
|
+
out[VERSION_KEY] = Uint8Array.of(ENVELOPE_VERSION);
|
|
2255
|
+
return out;
|
|
2256
|
+
}
|
|
2257
|
+
function fromDataEntries(entries) {
|
|
2258
|
+
const ct = readChunked(entries, CT_BASE);
|
|
2259
|
+
if (!ct) throw new Error("kit/stellar: account has no control-seed ciphertext (cv:ct)");
|
|
2260
|
+
const deviceWraps = {};
|
|
2261
|
+
const seenSlots = /* @__PURE__ */ new Set();
|
|
2262
|
+
for (const key of Object.keys(entries)) {
|
|
2263
|
+
const m = key.match(/^cv:wd:([^/]+)\/\d+$/);
|
|
2264
|
+
if (m) seenSlots.add(m[1]);
|
|
2265
|
+
}
|
|
2266
|
+
for (const slot of seenSlots) {
|
|
2267
|
+
const blob = readChunked(entries, deviceBase(slot));
|
|
2268
|
+
if (blob) deviceWraps[slot] = blob;
|
|
2269
|
+
}
|
|
2270
|
+
return {
|
|
2271
|
+
ct,
|
|
2272
|
+
deviceWraps,
|
|
2273
|
+
passkeyWrap: readChunked(entries, PASSKEY_BASE),
|
|
2274
|
+
recoveryWrap: readChunked(entries, RECOVERY_BASE)
|
|
2275
|
+
};
|
|
2276
|
+
}
|
|
2277
|
+
function deviceWrapEntries(slot, blob) {
|
|
2278
|
+
const out = {};
|
|
2279
|
+
writeChunked(out, deviceBase(slot), blob);
|
|
2280
|
+
return out;
|
|
2281
|
+
}
|
|
2282
|
+
function writeChunked(out, base, blob) {
|
|
2283
|
+
const chunks = chunkTo64(blob);
|
|
2284
|
+
chunks.forEach((chunk, i) => {
|
|
2285
|
+
out[`${base}/${i}`] = chunk;
|
|
2286
|
+
});
|
|
2287
|
+
}
|
|
2288
|
+
function readChunked(entries, base) {
|
|
2289
|
+
const chunks = [];
|
|
2290
|
+
for (let i = 0; ; i++) {
|
|
2291
|
+
const chunk = entries[`${base}/${i}`];
|
|
2292
|
+
if (!chunk) break;
|
|
2293
|
+
chunks.push(chunk);
|
|
2294
|
+
}
|
|
2295
|
+
return chunks.length ? unchunk(chunks) : void 0;
|
|
2296
|
+
}
|
|
2297
|
+
|
|
1508
2298
|
// src/chains/stellar/StellarAdapter.ts
|
|
1509
|
-
var
|
|
2299
|
+
var TX_TIMEOUT = 180;
|
|
1510
2300
|
var StellarAdapter = class {
|
|
1511
2301
|
constructor(opts) {
|
|
1512
2302
|
this.chain = "stellar";
|
|
1513
2303
|
this.network = opts.network;
|
|
1514
2304
|
this.passphrase = STELLAR_NETWORKS[opts.network].passphrase;
|
|
1515
|
-
this.
|
|
1516
|
-
this.factoryId = opts.factoryId ?? FACTORY_CONTRACT_ID[opts.network];
|
|
1517
|
-
if (!this.factoryId) {
|
|
1518
|
-
throw new Error(`kit/stellar: no factory contract id configured for ${opts.network}`);
|
|
1519
|
-
}
|
|
1520
|
-
this.signer = opts.signer;
|
|
2305
|
+
this.horizonUrl = opts.horizonUrl ?? HORIZON_URL[opts.network];
|
|
1521
2306
|
}
|
|
1522
2307
|
server() {
|
|
1523
2308
|
if (!this._server) {
|
|
1524
|
-
this._server = new stellarSdk.
|
|
1525
|
-
allowHttp: this.
|
|
2309
|
+
this._server = new stellarSdk.Horizon.Server(this.horizonUrl, {
|
|
2310
|
+
allowHttp: this.horizonUrl.startsWith("http://")
|
|
1526
2311
|
});
|
|
1527
2312
|
}
|
|
1528
2313
|
return this._server;
|
|
1529
2314
|
}
|
|
1530
|
-
|
|
1531
|
-
|
|
2315
|
+
/** Whether the classic account already exists on-chain. */
|
|
2316
|
+
async isDeployed(address) {
|
|
2317
|
+
try {
|
|
2318
|
+
await this.server().loadAccount(address);
|
|
2319
|
+
return true;
|
|
2320
|
+
} catch (e) {
|
|
2321
|
+
if (isNotFound(e)) return false;
|
|
2322
|
+
throw e;
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
/** Read the account's `MANAGE_DATA` entries as raw bytes (name → value). */
|
|
2326
|
+
async loadDataEntries(address) {
|
|
2327
|
+
const account = await this.server().loadAccount(address);
|
|
2328
|
+
const out = {};
|
|
2329
|
+
for (const [name, b64] of Object.entries(account.data_attr ?? {})) {
|
|
2330
|
+
out[name] = new Uint8Array(Buffer.from(b64, "base64"));
|
|
2331
|
+
}
|
|
2332
|
+
return out;
|
|
2333
|
+
}
|
|
2334
|
+
/** Native XLM balance in stroops. Returns 0n if the account doesn't exist. */
|
|
2335
|
+
async balance(address) {
|
|
2336
|
+
try {
|
|
2337
|
+
const account = await this.server().loadAccount(address);
|
|
2338
|
+
const native = account.balances.find((b) => b.asset_type === "native");
|
|
2339
|
+
return native ? toStroops(native.balance) : 0n;
|
|
2340
|
+
} catch (e) {
|
|
2341
|
+
if (isNotFound(e)) return 0n;
|
|
2342
|
+
throw e;
|
|
2343
|
+
}
|
|
1532
2344
|
}
|
|
1533
2345
|
/**
|
|
1534
|
-
*
|
|
1535
|
-
*
|
|
1536
|
-
* `
|
|
1537
|
-
* `
|
|
2346
|
+
* Build the account-creation transaction (source = funder, the relayer or a
|
|
2347
|
+
* self-funded keypair):
|
|
2348
|
+
* 1. `createAccount` funds the deterministic `G…` master address,
|
|
2349
|
+
* 2. `manageData` writes the control-key envelope entries (authorized by the
|
|
2350
|
+
* still-weight-1 master),
|
|
2351
|
+
* 3. `setOptions` adds the control signer (weight 1), sets all thresholds to
|
|
2352
|
+
* 1, and zeroes the master weight — after this the master can never sign.
|
|
2353
|
+
*
|
|
2354
|
+
* The returned tx must be signed by BOTH the master keypair (for the account's
|
|
2355
|
+
* own ops) and the funder (source + fee). Sponsorship of reserves is layered on
|
|
2356
|
+
* in Phase 3.
|
|
1538
2357
|
*/
|
|
1539
|
-
|
|
1540
|
-
const
|
|
1541
|
-
const
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
)
|
|
2358
|
+
async buildCreateTx(params) {
|
|
2359
|
+
const funderAccount = await this.server().loadAccount(params.funder);
|
|
2360
|
+
const builder = new stellarSdk.TransactionBuilder(funderAccount, {
|
|
2361
|
+
fee: stellarSdk.BASE_FEE,
|
|
2362
|
+
networkPassphrase: this.passphrase
|
|
2363
|
+
});
|
|
2364
|
+
builder.addOperation(
|
|
2365
|
+
stellarSdk.Operation.createAccount({
|
|
2366
|
+
destination: params.masterAddress,
|
|
2367
|
+
startingBalance: fromStroops(params.startingBalance)
|
|
1550
2368
|
})
|
|
1551
2369
|
);
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
return
|
|
1568
|
-
}
|
|
1569
|
-
/** Host function: `account.remove_signer(signer)` (requires device auth). */
|
|
1570
|
-
buildRemoveSigner(accountAddress, signer) {
|
|
1571
|
-
return invokeFunc(accountAddress, "remove_signer", [bytesScVal(sec1Pubkey(signer))]);
|
|
1572
|
-
}
|
|
1573
|
-
/** Host function: `account.add_approver(passkey)` (requires device auth). */
|
|
1574
|
-
buildAddApprover(accountAddress, passkey) {
|
|
1575
|
-
return invokeFunc(accountAddress, "add_approver", [bytesScVal(sec1Pubkey(passkey))]);
|
|
1576
|
-
}
|
|
1577
|
-
/** Host function: `account.remove_approver(passkey)` (requires device auth). */
|
|
1578
|
-
buildRemoveApprover(accountAddress, passkey) {
|
|
1579
|
-
return invokeFunc(accountAddress, "remove_approver", [bytesScVal(sec1Pubkey(passkey))]);
|
|
1580
|
-
}
|
|
1581
|
-
/** This chain's leaf for approving `add_signer(newSigner)` at `nonce`:
|
|
1582
|
-
* `sha256(sec1(new_signer) || nonce_be8)`. The batch challenge the passkey signs
|
|
1583
|
-
* is `sha256(concat(leaves))` across chains. */
|
|
1584
|
-
passkeyLeaf(newSigner, nonce) {
|
|
1585
|
-
const msg = new Uint8Array(65 + 8);
|
|
1586
|
-
msg.set(sec1Pubkey(newSigner), 0);
|
|
1587
|
-
const n = new Uint8Array(8);
|
|
1588
|
-
let v = nonce;
|
|
1589
|
-
for (let i = 7; i >= 0; i--) {
|
|
1590
|
-
n[i] = Number(v & 0xffn);
|
|
1591
|
-
v >>= 8n;
|
|
1592
|
-
}
|
|
1593
|
-
msg.set(n, 65);
|
|
1594
|
-
return sha256.sha256(msg);
|
|
1595
|
-
}
|
|
1596
|
-
/** Host function: passkey-authorized `add_signer_via_passkey` (no device auth —
|
|
1597
|
-
* authorized by the embedded WebAuthn assertion, so any relayer can submit).
|
|
1598
|
-
* `leaves`/`leafIndex` place this chain's leaf in the multi-chain batch. */
|
|
1599
|
-
buildAddSignerViaPasskey(accountAddress, newSigner, passkey, nonce, leaves, leafIndex, assertion) {
|
|
1600
|
-
const sig = encodeLowSSignature2({ r: assertion.r, s: assertion.s});
|
|
1601
|
-
const leavesScVal = stellarSdk.xdr.ScVal.scvVec(leaves.map((l) => bytesScVal(l)));
|
|
1602
|
-
return invokeFunc(accountAddress, "add_signer_via_passkey", [
|
|
1603
|
-
bytesScVal(sec1Pubkey(newSigner)),
|
|
1604
|
-
bytesScVal(sec1Pubkey(passkey)),
|
|
1605
|
-
stellarSdk.nativeToScVal(nonce, { type: "u64" }),
|
|
1606
|
-
leavesScVal,
|
|
1607
|
-
stellarSdk.nativeToScVal(leafIndex, { type: "u32" }),
|
|
1608
|
-
bytesScVal(assertion.authenticatorData),
|
|
1609
|
-
bytesScVal(assertion.clientDataJSON),
|
|
1610
|
-
stellarSdk.nativeToScVal(assertion.challengeOffset, { type: "u32" }),
|
|
1611
|
-
bytesScVal(sig)
|
|
1612
|
-
]);
|
|
1613
|
-
}
|
|
1614
|
-
/** Read whether `passkey` is a registered approver (read-only simulation). */
|
|
1615
|
-
async isApprover(accountAddress, passkey, readSource) {
|
|
1616
|
-
if (!await this.isDeployed(accountAddress)) return false;
|
|
1617
|
-
const { Account: Account3, TransactionBuilder: TransactionBuilder2, BASE_FEE: BASE_FEE2 } = await import('@stellar/stellar-sdk');
|
|
1618
|
-
const src = new Account3(readSource, "0");
|
|
1619
|
-
const op = stellarSdk.Operation.invokeHostFunction({
|
|
1620
|
-
func: invokeFunc(accountAddress, "is_approver", [bytesScVal(sec1Pubkey(passkey))]),
|
|
1621
|
-
auth: []
|
|
1622
|
-
});
|
|
1623
|
-
const tx2 = new TransactionBuilder2(src, { fee: BASE_FEE2, networkPassphrase: this.passphrase }).addOperation(op).setTimeout(30).build();
|
|
1624
|
-
const sim = await this.server().simulateTransaction(tx2);
|
|
1625
|
-
if (stellarSdk.rpc.Api.isSimulationError(sim)) {
|
|
1626
|
-
throw new Error(`kit/stellar: is_approver simulation failed: ${sim.error}`);
|
|
1627
|
-
}
|
|
1628
|
-
if (!sim.result?.retval) return false;
|
|
1629
|
-
return stellarSdk.scValToNative(sim.result.retval) === true;
|
|
1630
|
-
}
|
|
1631
|
-
/** Read the current passkey-approval nonce (read-only simulation). */
|
|
1632
|
-
async passkeyNonce(accountAddress, readSource) {
|
|
1633
|
-
if (!await this.isDeployed(accountAddress)) return 0n;
|
|
1634
|
-
const { Account: Account3, TransactionBuilder: TransactionBuilder2, BASE_FEE: BASE_FEE2 } = await import('@stellar/stellar-sdk');
|
|
1635
|
-
const src = new Account3(readSource, "0");
|
|
1636
|
-
const op = stellarSdk.Operation.invokeHostFunction({
|
|
1637
|
-
func: invokeFunc(accountAddress, "passkey_nonce", []),
|
|
1638
|
-
auth: []
|
|
1639
|
-
});
|
|
1640
|
-
const tx2 = new TransactionBuilder2(src, { fee: BASE_FEE2, networkPassphrase: this.passphrase }).addOperation(op).setTimeout(30).build();
|
|
1641
|
-
const sim = await this.server().simulateTransaction(tx2);
|
|
1642
|
-
if (stellarSdk.rpc.Api.isSimulationError(sim)) {
|
|
1643
|
-
throw new Error(`kit/stellar: passkey_nonce simulation failed: ${sim.error}`);
|
|
1644
|
-
}
|
|
1645
|
-
if (!sim.result?.retval) return 0n;
|
|
1646
|
-
return BigInt(stellarSdk.scValToNative(sim.result.retval));
|
|
1647
|
-
}
|
|
1648
|
-
/** Host function: SEP-41 `token.transfer(from=account, to, amount)` (device auth). */
|
|
1649
|
-
buildTransfer(tokenId, accountAddress, destination, amount) {
|
|
1650
|
-
return invokeFunc(tokenId, "transfer", [
|
|
1651
|
-
new stellarSdk.Address(accountAddress).toScVal(),
|
|
1652
|
-
new stellarSdk.Address(destination).toScVal(),
|
|
1653
|
-
stellarSdk.nativeToScVal(amount, { type: "i128" })
|
|
1654
|
-
]);
|
|
2370
|
+
for (const [name, value] of Object.entries(toDataEntries(params.envelope))) {
|
|
2371
|
+
builder.addOperation(
|
|
2372
|
+
stellarSdk.Operation.manageData({ name, value: Buffer.from(value), source: params.masterAddress })
|
|
2373
|
+
);
|
|
2374
|
+
}
|
|
2375
|
+
builder.addOperation(
|
|
2376
|
+
stellarSdk.Operation.setOptions({
|
|
2377
|
+
source: params.masterAddress,
|
|
2378
|
+
masterWeight: 0,
|
|
2379
|
+
lowThreshold: 1,
|
|
2380
|
+
medThreshold: 1,
|
|
2381
|
+
highThreshold: 1,
|
|
2382
|
+
signer: { ed25519PublicKey: params.controlAddress, weight: 1 }
|
|
2383
|
+
})
|
|
2384
|
+
);
|
|
2385
|
+
return builder.setTimeout(TX_TIMEOUT).build();
|
|
1655
2386
|
}
|
|
1656
2387
|
/**
|
|
1657
|
-
*
|
|
1658
|
-
*
|
|
1659
|
-
*
|
|
1660
|
-
*
|
|
2388
|
+
* Build a **sponsored** account-creation transaction whose source is the
|
|
2389
|
+
* relayer. Wraps the account setup in `beginSponsoringFutureReserves` /
|
|
2390
|
+
* `endSponsoringFutureReserves`, so the relayer (not the user) pays every
|
|
2391
|
+
* reserve — the account is created with a 0 starting balance and holds no
|
|
2392
|
+
* locked XLM of the user's. Ops:
|
|
2393
|
+
* 0. beginSponsoringFutureReserves(G) source = relayer
|
|
2394
|
+
* 1. createAccount(G, 0) source = relayer
|
|
2395
|
+
* 2. manageData(cv:… envelope) source = G (master-signed)
|
|
2396
|
+
* 3. setOptions(control signer, master → 0) source = G
|
|
2397
|
+
* 4. endSponsoringFutureReserves() source = G
|
|
2398
|
+
*
|
|
2399
|
+
* Signed by the master (for the G ops, while it's still weight 1); the relayer
|
|
2400
|
+
* co-signs (source + fee + sponsorship) before submitting.
|
|
1661
2401
|
*/
|
|
1662
|
-
async
|
|
1663
|
-
const
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
2402
|
+
async buildSponsoredCreateTx(params) {
|
|
2403
|
+
const relayerAccount = await this.server().loadAccount(params.relayer);
|
|
2404
|
+
const builder = new stellarSdk.TransactionBuilder(relayerAccount, {
|
|
2405
|
+
fee: stellarSdk.BASE_FEE,
|
|
2406
|
+
networkPassphrase: this.passphrase
|
|
2407
|
+
});
|
|
2408
|
+
builder.addOperation(
|
|
2409
|
+
stellarSdk.Operation.beginSponsoringFutureReserves({ sponsoredId: params.masterAddress, source: params.relayer })
|
|
2410
|
+
);
|
|
2411
|
+
builder.addOperation(
|
|
2412
|
+
stellarSdk.Operation.createAccount({ destination: params.masterAddress, startingBalance: "0", source: params.relayer })
|
|
2413
|
+
);
|
|
2414
|
+
for (const [name, value] of Object.entries(toDataEntries(params.envelope))) {
|
|
2415
|
+
builder.addOperation(
|
|
2416
|
+
stellarSdk.Operation.manageData({ name, value: Buffer.from(value), source: params.masterAddress })
|
|
2417
|
+
);
|
|
2418
|
+
}
|
|
2419
|
+
builder.addOperation(
|
|
2420
|
+
stellarSdk.Operation.setOptions({
|
|
2421
|
+
source: params.masterAddress,
|
|
2422
|
+
masterWeight: 0,
|
|
2423
|
+
lowThreshold: 1,
|
|
2424
|
+
medThreshold: 1,
|
|
2425
|
+
highThreshold: 1,
|
|
2426
|
+
signer: { ed25519PublicKey: params.controlAddress, weight: 1 }
|
|
1671
2427
|
})
|
|
1672
2428
|
);
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
const pubkey = await this.signer.getPublicKey();
|
|
1676
|
-
addrCreds.signature(deviceSignatureScVal(pubkey, sig));
|
|
1677
|
-
return entry;
|
|
2429
|
+
builder.addOperation(stellarSdk.Operation.endSponsoringFutureReserves({ source: params.masterAddress }));
|
|
2430
|
+
return builder.setTimeout(TX_TIMEOUT).build();
|
|
1678
2431
|
}
|
|
1679
2432
|
/**
|
|
1680
|
-
*
|
|
1681
|
-
* `
|
|
1682
|
-
*
|
|
2433
|
+
* Build a classic native-XLM payment as an *inner* transaction whose source is
|
|
2434
|
+
* the account itself (`G…`), signed by the control key. Wrap it in a fee-bump
|
|
2435
|
+
* (see `wrapFeeBump`) so the relayer pays the fee — gasless.
|
|
1683
2436
|
*/
|
|
1684
|
-
async
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
const sim = await this.server().simulateTransaction(tx2);
|
|
1694
|
-
if (stellarSdk.rpc.Api.isSimulationError(sim) || !sim.result?.retval) return 0n;
|
|
1695
|
-
return BigInt(stellarSdk.scValToNative(sim.result.retval));
|
|
2437
|
+
async buildPaymentTx(params) {
|
|
2438
|
+
const source = await this.server().loadAccount(params.from);
|
|
2439
|
+
return new stellarSdk.TransactionBuilder(source, { fee: stellarSdk.BASE_FEE, networkPassphrase: this.passphrase }).addOperation(
|
|
2440
|
+
stellarSdk.Operation.payment({
|
|
2441
|
+
destination: params.to,
|
|
2442
|
+
asset: stellarSdk.Asset.native(),
|
|
2443
|
+
amount: fromStroops(params.amount)
|
|
2444
|
+
})
|
|
2445
|
+
).setTimeout(TX_TIMEOUT).build();
|
|
1696
2446
|
}
|
|
1697
|
-
/**
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
} catch {
|
|
1707
|
-
return false;
|
|
2447
|
+
/**
|
|
2448
|
+
* Build a data-entry write (e.g. re-wrapping the DEK for a newly approved
|
|
2449
|
+
* device) as an inner tx sourced by the account, signed by the control key.
|
|
2450
|
+
*/
|
|
2451
|
+
async buildDataTx(params) {
|
|
2452
|
+
const source = await this.server().loadAccount(params.account);
|
|
2453
|
+
const builder = new stellarSdk.TransactionBuilder(source, { fee: stellarSdk.BASE_FEE, networkPassphrase: this.passphrase });
|
|
2454
|
+
for (const [name, value] of Object.entries(params.entries)) {
|
|
2455
|
+
builder.addOperation(stellarSdk.Operation.manageData({ name, value: Buffer.from(value) }));
|
|
1708
2456
|
}
|
|
2457
|
+
return builder.setTimeout(TX_TIMEOUT).build();
|
|
1709
2458
|
}
|
|
1710
2459
|
/**
|
|
1711
|
-
*
|
|
1712
|
-
*
|
|
1713
|
-
*
|
|
2460
|
+
* Build a **sponsored** data-entry write whose source is the relayer, so the
|
|
2461
|
+
* relayer (not the 0-balance account) pays the reserve of any NEW subentry.
|
|
2462
|
+
* Adding a factor (passkey / recovery) or a device slot creates a data entry
|
|
2463
|
+
* that needs ~0.5 XLM of reserve; a sponsored account holds no XLM, so — like
|
|
2464
|
+
* account creation — the write must be wrapped in begin/endSponsoringFuture
|
|
2465
|
+
* reserves with the relayer as sponsor. Ops:
|
|
2466
|
+
* 0. beginSponsoringFutureReserves(account) source = relayer
|
|
2467
|
+
* 1..n. manageData(cv:…) source = account (control-signed)
|
|
2468
|
+
* last. endSponsoringFutureReserves() source = account
|
|
2469
|
+
*
|
|
2470
|
+
* Signed by the control key (account ops) + the relayer (source + fee + sponsor).
|
|
1714
2471
|
*/
|
|
1715
|
-
async
|
|
1716
|
-
|
|
1717
|
-
const
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
func: invokeFunc(accountAddress, "is_authorized", [bytesScVal(sec1Pubkey(signer))]),
|
|
1721
|
-
auth: []
|
|
2472
|
+
async buildSponsoredDataTx(params) {
|
|
2473
|
+
const relayerAccount = await this.server().loadAccount(params.relayer);
|
|
2474
|
+
const builder = new stellarSdk.TransactionBuilder(relayerAccount, {
|
|
2475
|
+
fee: stellarSdk.BASE_FEE,
|
|
2476
|
+
networkPassphrase: this.passphrase
|
|
1722
2477
|
});
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
2478
|
+
builder.addOperation(
|
|
2479
|
+
stellarSdk.Operation.beginSponsoringFutureReserves({ sponsoredId: params.account, source: params.relayer })
|
|
2480
|
+
);
|
|
2481
|
+
for (const [name, value] of Object.entries(params.entries)) {
|
|
2482
|
+
builder.addOperation(stellarSdk.Operation.manageData({ name, value: Buffer.from(value), source: params.account }));
|
|
1727
2483
|
}
|
|
1728
|
-
|
|
1729
|
-
return
|
|
2484
|
+
builder.addOperation(stellarSdk.Operation.endSponsoringFutureReserves({ source: params.account }));
|
|
2485
|
+
return builder.setTimeout(TX_TIMEOUT).build();
|
|
2486
|
+
}
|
|
2487
|
+
/** Wrap a control-signed inner tx in a fee-bump whose fee source is `feeSource`
|
|
2488
|
+
* (the relayer). The inner tx pays nothing; the relayer pays all fees. */
|
|
2489
|
+
wrapFeeBump(inner, feeSource) {
|
|
2490
|
+
return stellarSdk.TransactionBuilder.buildFeeBumpTransaction(feeSource, stellarSdk.BASE_FEE, inner, this.passphrase);
|
|
2491
|
+
}
|
|
2492
|
+
/** Submit a fully-signed classic transaction and return its hash. Throws with
|
|
2493
|
+
* the Horizon result codes on failure. */
|
|
2494
|
+
async submit(tx3) {
|
|
2495
|
+
try {
|
|
2496
|
+
const res = await this.server().submitTransaction(tx3);
|
|
2497
|
+
return res.hash;
|
|
2498
|
+
} catch (e) {
|
|
2499
|
+
throw new Error(`kit/stellar: submit failed: ${horizonError(e)}`);
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2502
|
+
/** Build an `Account` handle for a known address + sequence (avoids a Horizon
|
|
2503
|
+
* round-trip when the caller already has the sequence). */
|
|
2504
|
+
accountAt(address, sequence) {
|
|
2505
|
+
return new stellarSdk.Account(address, sequence);
|
|
1730
2506
|
}
|
|
1731
2507
|
};
|
|
1732
|
-
function
|
|
1733
|
-
const
|
|
1734
|
-
|
|
1735
|
-
out.set(bigIntTo32Bytes(pk.x), 1);
|
|
1736
|
-
out.set(bigIntTo32Bytes(pk.y), 33);
|
|
1737
|
-
return out;
|
|
2508
|
+
function isNotFound(e) {
|
|
2509
|
+
const status = e?.response?.status ?? e?.status;
|
|
2510
|
+
return status === 404;
|
|
1738
2511
|
}
|
|
1739
|
-
function
|
|
1740
|
-
const
|
|
1741
|
-
|
|
1742
|
-
out.set(bigIntTo32Bytes(sig.r), 0);
|
|
1743
|
-
out.set(bigIntTo32Bytes(lowS), 32);
|
|
1744
|
-
return out;
|
|
2512
|
+
function horizonError(e) {
|
|
2513
|
+
const codes = e?.response?.data?.extras?.result_codes;
|
|
2514
|
+
return codes ? JSON.stringify(codes) : String(e?.message ?? e);
|
|
1745
2515
|
}
|
|
1746
|
-
function
|
|
1747
|
-
const
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
})
|
|
1763
|
-
);
|
|
2516
|
+
function toStroops(amount) {
|
|
2517
|
+
const [whole, frac = ""] = amount.split(".");
|
|
2518
|
+
const fracPadded = (frac + "0000000").slice(0, 7);
|
|
2519
|
+
return BigInt(whole) * 10000000n + BigInt(fracPadded);
|
|
2520
|
+
}
|
|
2521
|
+
function fromStroops(stroops) {
|
|
2522
|
+
const neg = stroops < 0n;
|
|
2523
|
+
const abs = neg ? -stroops : stroops;
|
|
2524
|
+
const whole = abs / 10000000n;
|
|
2525
|
+
const frac = (abs % 10000000n).toString().padStart(7, "0");
|
|
2526
|
+
return `${neg ? "-" : ""}${whole}.${frac}`;
|
|
2527
|
+
}
|
|
2528
|
+
var MASTER_HKDF_INFO = "cavos-stellar-master";
|
|
2529
|
+
function deriveStellarMasterSeed(identity) {
|
|
2530
|
+
const ikm = deriveAddressSeedStellar(identity);
|
|
2531
|
+
return hkdf.hkdf(sha256.sha256, ikm, void 0, MASTER_HKDF_INFO, 32);
|
|
1764
2532
|
}
|
|
1765
|
-
function
|
|
1766
|
-
return stellarSdk.
|
|
2533
|
+
function deriveStellarMasterKeypair(identity) {
|
|
2534
|
+
return stellarSdk.Keypair.fromRawEd25519Seed(Buffer.from(deriveStellarMasterSeed(identity)));
|
|
2535
|
+
}
|
|
2536
|
+
function generateControlKey() {
|
|
2537
|
+
const seed = utils.randomBytes(32);
|
|
2538
|
+
return { keypair: stellarSdk.Keypair.fromRawEd25519Seed(Buffer.from(seed)), seed };
|
|
2539
|
+
}
|
|
2540
|
+
function controlKeypairFromSeed(seed) {
|
|
2541
|
+
if (seed.length !== 32) throw new Error("kit/stellar: control seed must be 32 bytes");
|
|
2542
|
+
return stellarSdk.Keypair.fromRawEd25519Seed(Buffer.from(seed));
|
|
1767
2543
|
}
|
|
1768
2544
|
|
|
1769
2545
|
// src/chains/stellar/StellarRelayer.ts
|
|
@@ -1771,7 +2547,7 @@ var StellarRelayer = class {
|
|
|
1771
2547
|
constructor(opts) {
|
|
1772
2548
|
this.opts = opts;
|
|
1773
2549
|
}
|
|
1774
|
-
/** The relayer's source/fee-payer G-account (fetched + cached
|
|
2550
|
+
/** The relayer's source/fee-payer/sponsor G-account (fetched + cached). */
|
|
1775
2551
|
async getSource() {
|
|
1776
2552
|
if (this.source) return this.source;
|
|
1777
2553
|
const res = await fetch(`${this.opts.baseUrl}/api/stellar/relay?network=${this.opts.network}`);
|
|
@@ -1780,17 +2556,16 @@ var StellarRelayer = class {
|
|
|
1780
2556
|
this.source = fee_payer;
|
|
1781
2557
|
return this.source;
|
|
1782
2558
|
}
|
|
1783
|
-
/**
|
|
1784
|
-
*
|
|
1785
|
-
|
|
1786
|
-
*/
|
|
1787
|
-
async submit(transactionXdr) {
|
|
2559
|
+
/** POST a (partially) signed transaction XDR for the relayer to co-sign + submit.
|
|
2560
|
+
* `kind` selects the validation gate. Returns the confirmed transaction hash. */
|
|
2561
|
+
async submit(kind, transactionXdr) {
|
|
1788
2562
|
const res = await fetch(`${this.opts.baseUrl}/api/stellar/relay`, {
|
|
1789
2563
|
method: "POST",
|
|
1790
2564
|
headers: { "Content-Type": "application/json" },
|
|
1791
2565
|
body: JSON.stringify({
|
|
1792
2566
|
app_id: this.opts.appId,
|
|
1793
2567
|
network: this.opts.network,
|
|
2568
|
+
kind,
|
|
1794
2569
|
transaction: transactionXdr
|
|
1795
2570
|
})
|
|
1796
2571
|
});
|
|
@@ -1798,296 +2573,388 @@ var StellarRelayer = class {
|
|
|
1798
2573
|
const detail = await res.text().catch(() => "");
|
|
1799
2574
|
throw new Error(`kit/stellar: relay failed (${res.status}) ${detail}`);
|
|
1800
2575
|
}
|
|
1801
|
-
const { hash:
|
|
1802
|
-
return
|
|
2576
|
+
const { hash: hash5 } = await res.json();
|
|
2577
|
+
return hash5;
|
|
1803
2578
|
}
|
|
1804
2579
|
};
|
|
1805
2580
|
|
|
1806
2581
|
// src/chains/stellar/CavosStellar.ts
|
|
2582
|
+
var DEFAULT_STARTING_BALANCE = 50000000n;
|
|
1807
2583
|
var CavosStellar = class _CavosStellar {
|
|
1808
|
-
constructor(identity, address, status, network, adapter,
|
|
2584
|
+
constructor(identity, address, status, network, adapter, deviceKey, control, dek, relayer) {
|
|
1809
2585
|
this.identity = identity;
|
|
1810
2586
|
this.address = address;
|
|
1811
|
-
this.status = status;
|
|
1812
2587
|
this.network = network;
|
|
1813
2588
|
this.adapter = adapter;
|
|
1814
|
-
this.
|
|
2589
|
+
this.deviceKey = deviceKey;
|
|
2590
|
+
this.control = control;
|
|
2591
|
+
this.dek = dek;
|
|
1815
2592
|
this.relayer = relayer;
|
|
1816
|
-
|
|
1817
|
-
|
|
2593
|
+
// Discriminant for the `CavosWallet` union. Classic `G…` IS the Stellar chain
|
|
2594
|
+
// now (the Soroban `C…` path was removed), so this is "stellar".
|
|
1818
2595
|
this.chain = "stellar";
|
|
2596
|
+
this.isNewAccount = false;
|
|
2597
|
+
this.statusValue = status;
|
|
1819
2598
|
}
|
|
1820
|
-
get
|
|
1821
|
-
return this.
|
|
2599
|
+
get status() {
|
|
2600
|
+
return this.statusValue;
|
|
1822
2601
|
}
|
|
1823
2602
|
static async connect(opts) {
|
|
1824
2603
|
const identity = opts.identity ?? await opts.auth?.authenticate();
|
|
1825
2604
|
if (!identity) throw new Error("kit/stellar: connect requires `identity` or `auth`");
|
|
1826
|
-
const
|
|
1827
|
-
const
|
|
1828
|
-
const
|
|
1829
|
-
|
|
1830
|
-
rpcUrl: opts.rpcUrl,
|
|
1831
|
-
factoryId: opts.factoryId,
|
|
1832
|
-
signer
|
|
1833
|
-
});
|
|
1834
|
-
const addressSeed = deriveAddressSeedStellar({ userId: identity.userId, appSalt: opts.appSalt });
|
|
2605
|
+
const adapter = new StellarAdapter({ network: opts.network, horizonUrl: opts.horizonUrl });
|
|
2606
|
+
const master = deriveStellarMasterKeypair({ userId: identity.userId, appSalt: opts.appSalt });
|
|
2607
|
+
const address = master.publicKey();
|
|
2608
|
+
const startingBalance = opts.startingBalance ?? DEFAULT_STARTING_BALANCE;
|
|
1835
2609
|
const backendUrl = opts.backendUrl ?? "https://cavos.xyz";
|
|
1836
|
-
const registry = opts.registry ?? (opts.appId ? new HttpWalletRegistry({ baseUrl: backendUrl, appId: opts.appId, network: opts.network }) : defaultRegistry2);
|
|
1837
2610
|
const relayer = opts.relayer ?? (opts.appId ? new StellarRelayer({ baseUrl: backendUrl, appId: opts.appId, network: opts.network }) : void 0);
|
|
1838
|
-
const build = (
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
2611
|
+
const build = (status, unlocked) => new _CavosStellar(
|
|
2612
|
+
identity,
|
|
2613
|
+
address,
|
|
2614
|
+
status,
|
|
2615
|
+
opts.network,
|
|
2616
|
+
adapter,
|
|
2617
|
+
opts.deviceKey,
|
|
2618
|
+
unlocked?.control,
|
|
2619
|
+
unlocked?.dek,
|
|
2620
|
+
relayer
|
|
2621
|
+
);
|
|
2622
|
+
if (await adapter.isDeployed(address)) {
|
|
2623
|
+
const unlocked = await unlockViaDevice(adapter, address, opts.deviceKey);
|
|
2624
|
+
return build(unlocked ? "ready" : "needs-device-approval", unlocked ?? void 0);
|
|
1845
2625
|
}
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
2626
|
+
if (!relayer && !opts.sourceKeypair) {
|
|
2627
|
+
throw new Error("kit/stellar: a relayer (appId) or sourceKeypair is required to create the account");
|
|
2628
|
+
}
|
|
2629
|
+
const { keypair: control, seed: controlSeed } = generateControlKey();
|
|
2630
|
+
const dek = generateDEK();
|
|
2631
|
+
const envelope = {
|
|
2632
|
+
ct: sealControlSeed(controlSeed, dek),
|
|
2633
|
+
deviceWraps: { [opts.deviceKey.slotId()]: eciesWrapDEK(dek, opts.deviceKey.publicKeySec1()) }
|
|
2634
|
+
};
|
|
2635
|
+
if (relayer) {
|
|
2636
|
+
const relayerSource = await relayer.getSource();
|
|
2637
|
+
const tx3 = await adapter.buildSponsoredCreateTx({
|
|
2638
|
+
relayer: relayerSource,
|
|
2639
|
+
masterAddress: address,
|
|
2640
|
+
controlAddress: control.publicKey(),
|
|
2641
|
+
envelope
|
|
2642
|
+
});
|
|
2643
|
+
tx3.sign(master);
|
|
2644
|
+
await relayer.submit("create", tx3.toXDR());
|
|
2645
|
+
} else {
|
|
2646
|
+
const funder = opts.sourceKeypair;
|
|
2647
|
+
const tx3 = await adapter.buildCreateTx({
|
|
2648
|
+
funder: funder.publicKey(),
|
|
2649
|
+
masterAddress: address,
|
|
2650
|
+
controlAddress: control.publicKey(),
|
|
2651
|
+
envelope,
|
|
2652
|
+
startingBalance
|
|
2653
|
+
});
|
|
2654
|
+
tx3.sign(master, funder);
|
|
2655
|
+
await adapter.submit(tx3);
|
|
2656
|
+
}
|
|
2657
|
+
const wallet = build("ready", { control, dek });
|
|
2658
|
+
wallet.isNewAccount = true;
|
|
2659
|
+
return wallet;
|
|
2660
|
+
}
|
|
2661
|
+
/** Native XLM balance of the account, in stroops. */
|
|
2662
|
+
async balance() {
|
|
2663
|
+
return this.adapter.balance(this.address);
|
|
2664
|
+
}
|
|
2665
|
+
/** True if the account has a passkey factor enrolled (`cv:wp`), so a new device
|
|
2666
|
+
* can be approved with the passkey instead of a recovery code. Mirrors the
|
|
2667
|
+
* other chains' `hasPasskey()` for the React provider. */
|
|
2668
|
+
async hasPasskey() {
|
|
2669
|
+
try {
|
|
2670
|
+
const env = fromDataEntries(await this.adapter.loadDataEntries(this.address));
|
|
2671
|
+
return !!env.passkeyWrap;
|
|
2672
|
+
} catch {
|
|
2673
|
+
return false;
|
|
1850
2674
|
}
|
|
1851
|
-
await registry.register({ userId: identity.userId, address, initialSigner: devicePubkey });
|
|
1852
|
-
const isSigner = await adapter.isAuthorizedSigner(address, devicePubkey, readSource);
|
|
1853
|
-
return build(address, isSigner ? "ready" : "needs-device-approval");
|
|
1854
2675
|
}
|
|
1855
|
-
/**
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
2676
|
+
/** Whether the control key is unlocked on this device (status ready). Classic
|
|
2677
|
+
* approvals land synchronously via Horizon, so this reflects state immediately
|
|
2678
|
+
* (no indexing delay to poll for). */
|
|
2679
|
+
async isReady() {
|
|
2680
|
+
return this.statusValue === "ready";
|
|
1859
2681
|
}
|
|
1860
2682
|
/**
|
|
1861
|
-
*
|
|
1862
|
-
*
|
|
2683
|
+
* Move `amount` stroops of native XLM to `destination`, signed by the control
|
|
2684
|
+
* key. Sponsored by default (the relayer fee-bumps and pays the fee); pass
|
|
2685
|
+
* `{ sponsored: false }` to submit directly — the account pays its own (tiny)
|
|
2686
|
+
* fee from its XLM balance. The control key signs identically in both modes;
|
|
2687
|
+
* only the fee payer differs.
|
|
1863
2688
|
*/
|
|
1864
|
-
async
|
|
1865
|
-
const
|
|
1866
|
-
const
|
|
1867
|
-
return
|
|
1868
|
-
}
|
|
1869
|
-
/** Register an already-enrolled passkey pubkey as an approver (gasless).
|
|
1870
|
-
* Idempotent. Lets one passkey be registered across chains without re-prompting. */
|
|
1871
|
-
async addApprover(pubkey) {
|
|
1872
|
-
if (this.status !== "ready") {
|
|
1873
|
-
throw new Error("kit/stellar: addApprover requires a ready, authorized device");
|
|
1874
|
-
}
|
|
1875
|
-
const readSource = await this.resolveSource();
|
|
1876
|
-
if (await this.adapter.isApprover(this.address, pubkey, readSource)) return {};
|
|
1877
|
-
const func = this.adapter.buildAddApprover(this.address, pubkey);
|
|
1878
|
-
const transactionHash = await this.submitHostFunction(func, this.address);
|
|
1879
|
-
return { transactionHash };
|
|
2689
|
+
async execute(amount, destination, opts) {
|
|
2690
|
+
const control = this.requireControl();
|
|
2691
|
+
const inner = await this.adapter.buildPaymentTx({ from: this.address, to: destination, amount });
|
|
2692
|
+
return this.submitInner(inner, control, opts);
|
|
1880
2693
|
}
|
|
1881
2694
|
/**
|
|
1882
|
-
*
|
|
1883
|
-
*
|
|
1884
|
-
*
|
|
1885
|
-
*
|
|
2695
|
+
* Enroll a passkey as an unlock factor: wrap the DEK under the passkey's PRF
|
|
2696
|
+
* output and write the `cv:wp` entry. This is the synced anchor used to approve
|
|
2697
|
+
* a new device or recover — it survives device loss. Idempotent-ish: writing it
|
|
2698
|
+
* again just overwrites the wrap of the same DEK. Requires a ready device.
|
|
1886
2699
|
*/
|
|
1887
|
-
async
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
const { leaf, nonce } = await this.passkeyLeafForThisDevice();
|
|
1892
|
-
const leaves = [leaf];
|
|
1893
|
-
const assertion = await passkey.assert(batchChallenge(leaves));
|
|
1894
|
-
const { transactionHash } = await this.submitPasskeyApproval(assertion, leaves, 0, nonce);
|
|
1895
|
-
return transactionHash;
|
|
1896
|
-
}
|
|
1897
|
-
/** This device's leaf + passkey nonce for a (possibly multi-chain) batch. */
|
|
1898
|
-
async passkeyLeafForThisDevice() {
|
|
1899
|
-
const readSource = await this.resolveSource();
|
|
1900
|
-
const nonce = await this.adapter.passkeyNonce(this.address, readSource);
|
|
1901
|
-
return { leaf: this.adapter.passkeyLeaf(this.devicePubkey, nonce), nonce };
|
|
1902
|
-
}
|
|
1903
|
-
/** Submit `add_signer_via_passkey` given a shared assertion + batch position.
|
|
1904
|
-
* No device auth entry — authorized purely by the passkey assertion. */
|
|
1905
|
-
async submitPasskeyApproval(assertion, leaves, leafIndex, nonce) {
|
|
1906
|
-
const readSource = await this.resolveSource();
|
|
1907
|
-
const digest = webauthnDigest(assertion.authenticatorData, assertion.clientDataJSON);
|
|
1908
|
-
const candidates = recoverCandidatePublicKeys(assertion.r, assertion.s, digest);
|
|
1909
|
-
let approver = null;
|
|
1910
|
-
for (const cand of candidates) {
|
|
1911
|
-
if (await this.adapter.isApprover(this.address, cand.publicKey, readSource)) {
|
|
1912
|
-
approver = cand.publicKey;
|
|
1913
|
-
break;
|
|
1914
|
-
}
|
|
1915
|
-
}
|
|
1916
|
-
if (!approver) throw new Error("kit/stellar: this passkey is not a registered approver");
|
|
1917
|
-
const func = this.adapter.buildAddSignerViaPasskey(
|
|
1918
|
-
this.address,
|
|
1919
|
-
this.devicePubkey,
|
|
1920
|
-
approver,
|
|
1921
|
-
nonce,
|
|
1922
|
-
leaves,
|
|
1923
|
-
leafIndex,
|
|
1924
|
-
assertion
|
|
1925
|
-
);
|
|
1926
|
-
return { transactionHash: await this.submitHostFunction(func, void 0) };
|
|
1927
|
-
}
|
|
1928
|
-
/** Move `amount` stroops of native XLM to `destination` (device-signed). */
|
|
1929
|
-
async execute(amount, destination) {
|
|
1930
|
-
return this.executeTransfer(NATIVE_SAC_ID[this.network], amount, destination);
|
|
1931
|
-
}
|
|
1932
|
-
/** Read this account's balance of `tokenId` (defaults to native XLM), in stroops. */
|
|
1933
|
-
async balance(tokenId = NATIVE_SAC_ID[this.network]) {
|
|
1934
|
-
const readSource = await this.resolveSource();
|
|
1935
|
-
return this.adapter.readBalance(tokenId, this.address, readSource);
|
|
1936
|
-
}
|
|
1937
|
-
/** Transfer `amount` of any SEP-41 token out of the account (device-signed). */
|
|
1938
|
-
async executeTransfer(tokenId, amount, destination) {
|
|
1939
|
-
if (this.status !== "ready") {
|
|
1940
|
-
throw new Error("kit/stellar: this device is not yet an authorized signer of the wallet");
|
|
1941
|
-
}
|
|
1942
|
-
const func = this.adapter.buildTransfer(tokenId, this.address, destination, amount);
|
|
1943
|
-
return this.submitHostFunction(func, this.address);
|
|
2700
|
+
async enrollPasskey(prfOutput) {
|
|
2701
|
+
const { control, dek } = this.requireUnlocked();
|
|
2702
|
+
const wrap = wrapDEK(dek, derivePasskeyKEK(prfOutput));
|
|
2703
|
+
return this.writeFactor(PASSKEY_BASE, wrap, control);
|
|
1944
2704
|
}
|
|
1945
2705
|
/**
|
|
1946
|
-
*
|
|
1947
|
-
*
|
|
1948
|
-
*
|
|
2706
|
+
* Set up a recovery code as an unlock factor: wrap the DEK under the code's KEK
|
|
2707
|
+
* and write the `cv:wr` entry. Optional in v1 — the integrating app decides when
|
|
2708
|
+
* to surface it. The code never leaves the device; only the wrap goes on-chain.
|
|
2709
|
+
* Requires a ready device.
|
|
1949
2710
|
*/
|
|
1950
2711
|
async setupRecovery(code) {
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
const { publicKey: backupPubkey } = deriveBackupKey(code);
|
|
1955
|
-
const readSource = await this.resolveSource();
|
|
1956
|
-
if (await this.adapter.isAuthorizedSigner(this.address, backupPubkey, readSource)) return void 0;
|
|
1957
|
-
return this.addSigner(backupPubkey);
|
|
2712
|
+
const { control, dek } = this.requireUnlocked();
|
|
2713
|
+
const wrap = wrapDEK(dek, deriveRecoveryKEK(code));
|
|
2714
|
+
return this.writeFactor(RECOVERY_BASE, wrap, control);
|
|
1958
2715
|
}
|
|
1959
2716
|
/**
|
|
1960
|
-
*
|
|
1961
|
-
*
|
|
1962
|
-
*
|
|
2717
|
+
* From a new browser/device (`needs-device-approval`), approve THIS device using
|
|
2718
|
+
* the user's synced passkey: unlock the DEK via the passkey factor, then wrap it
|
|
2719
|
+
* to this device's slot so future sessions unlock silently. Flips status to
|
|
2720
|
+
* `ready`. No trip back to an already-authorized device.
|
|
1963
2721
|
*/
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
const existing = await registry.lookup(opts.identity.userId);
|
|
1977
|
-
if (!existing) {
|
|
1978
|
-
throw new Error("kit/stellar: no account found for this identity \u2014 nothing to recover");
|
|
1979
|
-
}
|
|
1980
|
-
const relayer = opts.relayer ?? (opts.appId ? new StellarRelayer({ baseUrl: backendUrl, appId: opts.appId, network: opts.network }) : void 0);
|
|
1981
|
-
const backupHandle = new _CavosStellar(
|
|
1982
|
-
opts.identity,
|
|
1983
|
-
existing.address,
|
|
1984
|
-
"ready",
|
|
1985
|
-
opts.network,
|
|
1986
|
-
backupAdapter,
|
|
1987
|
-
devicePubkey,
|
|
1988
|
-
relayer,
|
|
1989
|
-
opts.sourceKeypair
|
|
2722
|
+
async approveThisDeviceWithPasskey(prfOutput) {
|
|
2723
|
+
return this.approveThisDevice(
|
|
2724
|
+
await unlockViaPasskey(this.adapter, this.address, prfOutput),
|
|
2725
|
+
"passkey"
|
|
2726
|
+
);
|
|
2727
|
+
}
|
|
2728
|
+
/** Approve THIS device using the recovery code (same as the passkey path, for
|
|
2729
|
+
* the backup factor). */
|
|
2730
|
+
async approveThisDeviceWithRecovery(code) {
|
|
2731
|
+
return this.approveThisDevice(
|
|
2732
|
+
await unlockViaRecovery(this.adapter, this.address, code),
|
|
2733
|
+
"recovery code"
|
|
1990
2734
|
);
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
2735
|
+
}
|
|
2736
|
+
/** The control key's public G address (the weight-1 real signer), for display. */
|
|
2737
|
+
get controlAddress() {
|
|
2738
|
+
return this.control?.publicKey();
|
|
2739
|
+
}
|
|
2740
|
+
// --- internals ----------------------------------------------------------
|
|
2741
|
+
async approveThisDevice(unlocked, factor) {
|
|
2742
|
+
if (this.statusValue === "ready") {
|
|
2743
|
+
throw new Error("kit/stellar: this device is already authorized");
|
|
1994
2744
|
}
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2745
|
+
if (!unlocked) {
|
|
2746
|
+
throw new Error(`kit/stellar: could not unlock the account with the ${factor} \u2014 wrong factor or not enrolled`);
|
|
2747
|
+
}
|
|
2748
|
+
const slot = this.deviceKey.slotId();
|
|
2749
|
+
const wrap = eciesWrapDEK(unlocked.dek, this.deviceKey.publicKeySec1());
|
|
2750
|
+
const hash5 = await this.submitDataWrite(deviceWrapEntries(slot, wrap), unlocked.control);
|
|
2751
|
+
this.control = unlocked.control;
|
|
2752
|
+
this.dek = unlocked.dek;
|
|
2753
|
+
this.statusValue = "ready";
|
|
2754
|
+
return hash5;
|
|
2755
|
+
}
|
|
2756
|
+
/** Write a single-factor wrap (passkey/recovery) into the account data entries,
|
|
2757
|
+
* signed by the control key. Overwrites cleanly if the base already existed and
|
|
2758
|
+
* the new blob has the same chunk count. */
|
|
2759
|
+
async writeFactor(base, wrap, control) {
|
|
2760
|
+
const entries = {};
|
|
2761
|
+
chunkTo64(wrap).forEach((chunk, i) => {
|
|
2762
|
+
entries[`${base}/${i}`] = chunk;
|
|
2000
2763
|
});
|
|
2001
|
-
return
|
|
2002
|
-
opts.identity,
|
|
2003
|
-
existing.address,
|
|
2004
|
-
"ready",
|
|
2005
|
-
opts.network,
|
|
2006
|
-
adapter,
|
|
2007
|
-
devicePubkey,
|
|
2008
|
-
relayer,
|
|
2009
|
-
opts.sourceKeypair
|
|
2010
|
-
);
|
|
2764
|
+
return this.submitDataWrite(entries, control);
|
|
2011
2765
|
}
|
|
2012
|
-
/**
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2766
|
+
/**
|
|
2767
|
+
* Sign an inner (account-sourced) payment tx with the control key and submit it:
|
|
2768
|
+
* - sponsored (default) → with a relayer, wrap in a fee-bump (relayer pays
|
|
2769
|
+
* the fee) and POST; falls back to self-funded if no relayer;
|
|
2770
|
+
* - `{ sponsored: false }` → submit directly (the account pays its own fee).
|
|
2771
|
+
* Payments add no subentries, so no reserve sponsorship is needed here.
|
|
2772
|
+
*/
|
|
2773
|
+
async submitInner(inner, control, opts) {
|
|
2774
|
+
inner.sign(control);
|
|
2775
|
+
const sponsored = opts?.sponsored !== false;
|
|
2776
|
+
if (sponsored && this.relayer) {
|
|
2777
|
+
const feeSource = await this.relayer.getSource();
|
|
2778
|
+
const bump = this.adapter.wrapFeeBump(inner, feeSource);
|
|
2779
|
+
return this.relayer.submit("fee-bump", bump.toXDR());
|
|
2780
|
+
}
|
|
2781
|
+
return this.adapter.submit(inner);
|
|
2017
2782
|
}
|
|
2018
2783
|
/**
|
|
2019
|
-
*
|
|
2020
|
-
*
|
|
2021
|
-
*
|
|
2784
|
+
* Write data entries (add a factor / device slot) — which create NEW subentries
|
|
2785
|
+
* that each need ~0.5 XLM of reserve. A relayer-sponsored account holds no XLM,
|
|
2786
|
+
* so the write must be sponsored by the relayer (source + sponsor), exactly like
|
|
2787
|
+
* account creation — a plain fee-bump would fail with `op_low_reserve`.
|
|
2788
|
+
* - sponsored (default) → with a relayer, build a sponsored write (relayer
|
|
2789
|
+
* source + begin/end sponsoring), control-sign the account ops, relay
|
|
2790
|
+
* co-signs + submits; falls back to self-funded if no relayer;
|
|
2791
|
+
* - `{ sponsored: false }` → the account writes directly (it must hold its
|
|
2792
|
+
* own reserve for the new subentries).
|
|
2022
2793
|
*/
|
|
2023
|
-
async
|
|
2024
|
-
const
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
throw new Error(`kit/stellar: simulation failed: ${sim.error}`);
|
|
2035
|
-
}
|
|
2036
|
-
const validUntil = (await server.getLatestLedger()).sequence + 100;
|
|
2037
|
-
const entries = sim.result?.auth ?? [];
|
|
2038
|
-
const signedAuth = [];
|
|
2039
|
-
for (const entry of entries) {
|
|
2040
|
-
if (authAccount && isAddressCredentialFor(entry, authAccount)) {
|
|
2041
|
-
signedAuth.push(await this.adapter.signAuthEntry(entry, validUntil));
|
|
2042
|
-
} else {
|
|
2043
|
-
signedAuth.push(entry);
|
|
2044
|
-
}
|
|
2794
|
+
async submitDataWrite(entries, control, opts) {
|
|
2795
|
+
const sponsored = opts?.sponsored !== false;
|
|
2796
|
+
if (sponsored && this.relayer) {
|
|
2797
|
+
const relayerSource = await this.relayer.getSource();
|
|
2798
|
+
const tx4 = await this.adapter.buildSponsoredDataTx({
|
|
2799
|
+
relayer: relayerSource,
|
|
2800
|
+
account: this.address,
|
|
2801
|
+
entries
|
|
2802
|
+
});
|
|
2803
|
+
tx4.sign(control);
|
|
2804
|
+
return this.relayer.submit("sponsored-data", tx4.toXDR());
|
|
2045
2805
|
}
|
|
2046
|
-
const
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
if (stellarSdk.rpc.Api.isSimulationError(authSim)) {
|
|
2054
|
-
throw new Error(`kit/stellar: auth simulation failed: ${authSim.error}`);
|
|
2055
|
-
}
|
|
2056
|
-
const assembled = stellarSdk.rpc.assembleTransaction(built, authSim).build();
|
|
2057
|
-
if (this.relayer) {
|
|
2058
|
-
return this.relayer.submit(assembled.toXDR());
|
|
2059
|
-
}
|
|
2060
|
-
if (this.sourceKeypair) {
|
|
2061
|
-
assembled.sign(this.sourceKeypair);
|
|
2062
|
-
return this.sendAndConfirm(assembled);
|
|
2063
|
-
}
|
|
2064
|
-
throw new Error("kit/stellar: no relayer or sourceKeypair configured to submit");
|
|
2065
|
-
}
|
|
2066
|
-
/** Submit a signed tx via RPC and poll to confirmation. Returns the hash. */
|
|
2067
|
-
async sendAndConfirm(tx2) {
|
|
2068
|
-
const server = this.adapter.server();
|
|
2069
|
-
const sent = await server.sendTransaction(tx2);
|
|
2070
|
-
if (sent.status === "ERROR") {
|
|
2071
|
-
throw new Error(`kit/stellar: submit rejected: ${JSON.stringify(sent.errorResult)}`);
|
|
2072
|
-
}
|
|
2073
|
-
const hash6 = sent.hash;
|
|
2074
|
-
for (let i = 0; i < 30; i++) {
|
|
2075
|
-
const got = await server.getTransaction(hash6);
|
|
2076
|
-
if (got.status === stellarSdk.rpc.Api.GetTransactionStatus.SUCCESS) return hash6;
|
|
2077
|
-
if (got.status === stellarSdk.rpc.Api.GetTransactionStatus.FAILED) {
|
|
2078
|
-
throw new Error(`kit/stellar: tx ${hash6} failed`);
|
|
2079
|
-
}
|
|
2080
|
-
await new Promise((r) => setTimeout(r, 1e3));
|
|
2806
|
+
const tx3 = await this.adapter.buildDataTx({ account: this.address, entries });
|
|
2807
|
+
tx3.sign(control);
|
|
2808
|
+
return this.adapter.submit(tx3);
|
|
2809
|
+
}
|
|
2810
|
+
requireControl() {
|
|
2811
|
+
if (this.statusValue !== "ready" || !this.control) {
|
|
2812
|
+
throw new Error("kit/stellar: control key not unlocked on this device (needs approval)");
|
|
2081
2813
|
}
|
|
2082
|
-
|
|
2814
|
+
return this.control;
|
|
2815
|
+
}
|
|
2816
|
+
requireUnlocked() {
|
|
2817
|
+
const control = this.requireControl();
|
|
2818
|
+
if (!this.dek) throw new Error("kit/stellar: DEK unavailable on this device");
|
|
2819
|
+
return { control, dek: this.dek };
|
|
2083
2820
|
}
|
|
2084
2821
|
};
|
|
2085
|
-
function
|
|
2086
|
-
const
|
|
2087
|
-
|
|
2088
|
-
|
|
2822
|
+
async function unlockViaDevice(adapter, address, deviceKey) {
|
|
2823
|
+
const env = await loadEnvelope(adapter, address);
|
|
2824
|
+
const wrap = env.deviceWraps[deviceKey.slotId()];
|
|
2825
|
+
if (!wrap) return null;
|
|
2826
|
+
try {
|
|
2827
|
+
const dek = await deviceKey.unwrap(wrap);
|
|
2828
|
+
return openControl(env, dek);
|
|
2829
|
+
} catch {
|
|
2830
|
+
return null;
|
|
2831
|
+
}
|
|
2832
|
+
}
|
|
2833
|
+
async function unlockViaPasskey(adapter, address, prfOutput) {
|
|
2834
|
+
const env = await loadEnvelope(adapter, address);
|
|
2835
|
+
if (!env.passkeyWrap) return null;
|
|
2836
|
+
try {
|
|
2837
|
+
const dek = unwrapDEK(env.passkeyWrap, derivePasskeyKEK(prfOutput));
|
|
2838
|
+
return openControl(env, dek);
|
|
2839
|
+
} catch {
|
|
2840
|
+
return null;
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2843
|
+
async function unlockViaRecovery(adapter, address, code) {
|
|
2844
|
+
const env = await loadEnvelope(adapter, address);
|
|
2845
|
+
if (!env.recoveryWrap) return null;
|
|
2846
|
+
try {
|
|
2847
|
+
const dek = unwrapDEK(env.recoveryWrap, deriveRecoveryKEK(code));
|
|
2848
|
+
return openControl(env, dek);
|
|
2849
|
+
} catch {
|
|
2850
|
+
return null;
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
async function loadEnvelope(adapter, address) {
|
|
2854
|
+
return fromDataEntries(await adapter.loadDataEntries(address));
|
|
2855
|
+
}
|
|
2856
|
+
function openControl(env, dek) {
|
|
2857
|
+
const controlSeed = openControlSeed(env.ct, dek);
|
|
2858
|
+
return { control: controlKeypairFromSeed(controlSeed), dek };
|
|
2859
|
+
}
|
|
2860
|
+
function deviceSlotId(publicKeySec1) {
|
|
2861
|
+
const h = sha256.sha256(publicKeySec1);
|
|
2862
|
+
let s = "";
|
|
2863
|
+
for (const b of h.subarray(0, 4)) s += b.toString(16).padStart(2, "0");
|
|
2864
|
+
return s;
|
|
2865
|
+
}
|
|
2866
|
+
|
|
2867
|
+
// src/chains/stellar/WebCryptoDeviceUnwrapKey.ts
|
|
2868
|
+
var IDB_NAME2 = "cavos-kit-stellar";
|
|
2869
|
+
var IDB_STORE2 = "unwrap-keys";
|
|
2870
|
+
var WebCryptoDeviceUnwrapKey = class _WebCryptoDeviceUnwrapKey {
|
|
2871
|
+
constructor(privateKey, publicRaw, keyId) {
|
|
2872
|
+
this.privateKey = privateKey;
|
|
2873
|
+
this.publicRaw = publicRaw;
|
|
2874
|
+
this.keyId = keyId;
|
|
2875
|
+
}
|
|
2876
|
+
/** Create a fresh device unwrap key and persist it. */
|
|
2877
|
+
static async create(opts) {
|
|
2878
|
+
assertSecureContext2();
|
|
2879
|
+
const pair = await crypto.subtle.generateKey(
|
|
2880
|
+
{ name: "ECDH", namedCurve: "P-256" },
|
|
2881
|
+
false,
|
|
2882
|
+
// private key is NON-extractable
|
|
2883
|
+
["deriveBits"]
|
|
2884
|
+
);
|
|
2885
|
+
const publicRaw = new Uint8Array(await crypto.subtle.exportKey("raw", pair.publicKey));
|
|
2886
|
+
await idbPut2(opts.keyId, { privateKey: pair.privateKey, publicRaw });
|
|
2887
|
+
return new _WebCryptoDeviceUnwrapKey(pair.privateKey, publicRaw, opts.keyId);
|
|
2888
|
+
}
|
|
2889
|
+
/** Load an existing device unwrap key, or null if none exists yet. */
|
|
2890
|
+
static async load(opts) {
|
|
2891
|
+
const rec = await idbGet2(opts.keyId);
|
|
2892
|
+
if (!rec) return null;
|
|
2893
|
+
return new _WebCryptoDeviceUnwrapKey(rec.privateKey, rec.publicRaw, opts.keyId);
|
|
2894
|
+
}
|
|
2895
|
+
/** Load the device unwrap key, creating one on first use. */
|
|
2896
|
+
static async loadOrCreate(opts) {
|
|
2897
|
+
return await _WebCryptoDeviceUnwrapKey.load(opts) ?? await _WebCryptoDeviceUnwrapKey.create(opts);
|
|
2898
|
+
}
|
|
2899
|
+
publicKeySec1() {
|
|
2900
|
+
return this.publicRaw;
|
|
2901
|
+
}
|
|
2902
|
+
slotId() {
|
|
2903
|
+
return deviceSlotId(this.publicRaw);
|
|
2904
|
+
}
|
|
2905
|
+
async unwrap(blob) {
|
|
2906
|
+
const ephPubCompressed = blob.subarray(0, 33);
|
|
2907
|
+
const wrapped = blob.subarray(33);
|
|
2908
|
+
const ephUncompressed = p256.p256.ProjectivePoint.fromHex(ephPubCompressed).toRawBytes(false);
|
|
2909
|
+
const ephKey = await crypto.subtle.importKey(
|
|
2910
|
+
"raw",
|
|
2911
|
+
ephUncompressed,
|
|
2912
|
+
{ name: "ECDH", namedCurve: "P-256" },
|
|
2913
|
+
false,
|
|
2914
|
+
[]
|
|
2915
|
+
);
|
|
2916
|
+
const sharedX = new Uint8Array(
|
|
2917
|
+
await crypto.subtle.deriveBits({ name: "ECDH", public: ephKey }, this.privateKey, 256)
|
|
2918
|
+
);
|
|
2919
|
+
const kek = eciesKEKFromX(sharedX, ephPubCompressed);
|
|
2920
|
+
return unwrapDEK(wrapped, kek);
|
|
2921
|
+
}
|
|
2922
|
+
};
|
|
2923
|
+
function assertSecureContext2() {
|
|
2924
|
+
const ok = typeof crypto !== "undefined" && typeof crypto.subtle !== "undefined" && (typeof window === "undefined" || window.isSecureContext);
|
|
2925
|
+
if (!ok) {
|
|
2926
|
+
throw new Error(
|
|
2927
|
+
"Cavos: WebCrypto is unavailable. Device keys require a secure context \u2014 use HTTPS, or http://localhost."
|
|
2928
|
+
);
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
function openDb2() {
|
|
2932
|
+
return new Promise((resolve, reject) => {
|
|
2933
|
+
const req = indexedDB.open(IDB_NAME2, 1);
|
|
2934
|
+
req.onupgradeneeded = () => req.result.createObjectStore(IDB_STORE2);
|
|
2935
|
+
req.onsuccess = () => resolve(req.result);
|
|
2936
|
+
req.onerror = () => reject(req.error);
|
|
2937
|
+
});
|
|
2938
|
+
}
|
|
2939
|
+
async function idbPut2(keyId, value) {
|
|
2940
|
+
const db = await openDb2();
|
|
2941
|
+
await tx2(db, "readwrite", (store) => store.put(value, keyId));
|
|
2942
|
+
db.close();
|
|
2943
|
+
}
|
|
2944
|
+
async function idbGet2(keyId) {
|
|
2945
|
+
const db = await openDb2();
|
|
2946
|
+
const result = await tx2(db, "readonly", (store) => store.get(keyId));
|
|
2947
|
+
db.close();
|
|
2948
|
+
return result ?? null;
|
|
2949
|
+
}
|
|
2950
|
+
function tx2(db, mode, run) {
|
|
2951
|
+
return new Promise((resolve, reject) => {
|
|
2952
|
+
const store = db.transaction(IDB_STORE2, mode).objectStore(IDB_STORE2);
|
|
2953
|
+
const req = run(store);
|
|
2954
|
+
req.onsuccess = () => resolve(req.result);
|
|
2955
|
+
req.onerror = () => reject(req.error);
|
|
2956
|
+
});
|
|
2089
2957
|
}
|
|
2090
|
-
var defaultRegistry2 = new InMemoryWalletRegistry();
|
|
2091
2958
|
|
|
2092
2959
|
// src/recovery/HttpRecoveryClient.ts
|
|
2093
2960
|
function toHex2(n) {
|
|
@@ -2187,6 +3054,9 @@ var Cavos = class _Cavos {
|
|
|
2187
3054
|
this.chain = "starknet";
|
|
2188
3055
|
/** Request id of the pending device-addition, when status is needs-device-approval. */
|
|
2189
3056
|
this.pendingRequestId = null;
|
|
3057
|
+
/** True when this connect just created & deployed a brand-new account (first
|
|
3058
|
+
* sign-up), so the UI can offer a one-time "secure your account" step. */
|
|
3059
|
+
this.isNewAccount = false;
|
|
2190
3060
|
}
|
|
2191
3061
|
/**
|
|
2192
3062
|
* Unified entry point. Pick a `chain` and an `network` environment; the kit
|
|
@@ -2216,17 +3086,16 @@ var Cavos = class _Cavos {
|
|
|
2216
3086
|
});
|
|
2217
3087
|
}
|
|
2218
3088
|
if (opts.chain === "stellar") {
|
|
3089
|
+
const identity = opts.identity ?? (opts.auth ? await opts.auth.authenticate() : void 0);
|
|
3090
|
+
if (!identity) throw new Error("kit: Stellar connect requires `identity` or `auth`");
|
|
3091
|
+
const deviceKey = opts.stellarDeviceKey ?? await WebCryptoDeviceUnwrapKey.loadOrCreate({ keyId: `${identity.userId}:${opts.appSalt}` });
|
|
2219
3092
|
return CavosStellar.connect({
|
|
2220
3093
|
network: STELLAR_ENV[opts.network],
|
|
2221
|
-
|
|
2222
|
-
...opts.identity ? { identity: opts.identity } : {},
|
|
3094
|
+
identity,
|
|
2223
3095
|
appSalt: opts.appSalt,
|
|
3096
|
+
deviceKey,
|
|
2224
3097
|
...opts.appId ? { appId: opts.appId } : {},
|
|
2225
3098
|
...opts.backendUrl ? { backendUrl: opts.backendUrl } : {},
|
|
2226
|
-
...opts.registry ? { registry: opts.registry } : {},
|
|
2227
|
-
...opts.rpcUrl ? { rpcUrl: opts.rpcUrl } : {},
|
|
2228
|
-
...opts.factoryId ? { factoryId: opts.factoryId } : {},
|
|
2229
|
-
...opts.createSigner ? { createSigner: opts.createSigner } : {},
|
|
2230
3099
|
...opts.stellarRelayer ? { relayer: opts.stellarRelayer } : {},
|
|
2231
3100
|
...opts.stellarSourceKeypair ? { sourceKeypair: opts.stellarSourceKeypair } : {}
|
|
2232
3101
|
});
|
|
@@ -2276,13 +3145,13 @@ var Cavos = class _Cavos {
|
|
|
2276
3145
|
cairoVersion: "1"
|
|
2277
3146
|
});
|
|
2278
3147
|
const backendUrl = opts.backendUrl ?? "https://cavos.xyz";
|
|
2279
|
-
const registry = opts.registry ?? (opts.appId ? new HttpWalletRegistry({ baseUrl: backendUrl, appId: opts.appId, network: opts.network }) :
|
|
3148
|
+
const registry = opts.registry ?? (opts.appId ? new HttpWalletRegistry({ baseUrl: backendUrl, appId: opts.appId, network: opts.network }) : defaultRegistry2);
|
|
2280
3149
|
const recovery = opts.recovery ?? (opts.appId ? new HttpRecoveryClient({ baseUrl: backendUrl, appId: opts.appId }) : null);
|
|
2281
3150
|
const existing = await registry.lookup(identity.userId);
|
|
2282
3151
|
if (existing) {
|
|
2283
3152
|
const account2 = makeAccount(existing.address);
|
|
2284
3153
|
const isSigner2 = await adapter.isAuthorizedSigner(existing.address, devicePubkey);
|
|
2285
|
-
const
|
|
3154
|
+
const cavos2 = new _Cavos(
|
|
2286
3155
|
identity,
|
|
2287
3156
|
existing.address,
|
|
2288
3157
|
isSigner2 ? "ready" : "needs-device-approval",
|
|
@@ -2296,7 +3165,7 @@ var Cavos = class _Cavos {
|
|
|
2296
3165
|
const fresh = dedup && Date.now() - dedup.requestedAt < DEVICE_REQUEST_DEDUP_MS;
|
|
2297
3166
|
try {
|
|
2298
3167
|
if (fresh) {
|
|
2299
|
-
|
|
3168
|
+
cavos2.pendingRequestId = dedup.requestId;
|
|
2300
3169
|
} else {
|
|
2301
3170
|
const { requestId } = await recovery.requestDeviceAddition({
|
|
2302
3171
|
userId: identity.userId,
|
|
@@ -2304,16 +3173,16 @@ var Cavos = class _Cavos {
|
|
|
2304
3173
|
newSigner: devicePubkey,
|
|
2305
3174
|
...identity.email ? { email: identity.email } : {}
|
|
2306
3175
|
});
|
|
2307
|
-
|
|
3176
|
+
cavos2.pendingRequestId = requestId;
|
|
2308
3177
|
lastDeviceRequest.set(identity.userId, { requestId, requestedAt: Date.now() });
|
|
2309
3178
|
}
|
|
2310
3179
|
} catch (e) {
|
|
2311
3180
|
console.warn("[Cavos] requestDeviceAddition failed:", e);
|
|
2312
3181
|
}
|
|
2313
3182
|
}
|
|
2314
|
-
return
|
|
3183
|
+
return cavos2;
|
|
2315
3184
|
}
|
|
2316
|
-
const address = adapter.computeAddress({ addressSeed
|
|
3185
|
+
const address = adapter.computeAddress({ addressSeed });
|
|
2317
3186
|
const account = makeAccount(address);
|
|
2318
3187
|
const alreadyDeployed = await isDeployed(provider, address);
|
|
2319
3188
|
if (!alreadyDeployed) {
|
|
@@ -2321,10 +3190,11 @@ var Cavos = class _Cavos {
|
|
|
2321
3190
|
address,
|
|
2322
3191
|
class_hash: classHash,
|
|
2323
3192
|
salt: starknet.num.toHex(addressSeed),
|
|
2324
|
-
calldata: adapter.constructorCalldata(addressSeed
|
|
3193
|
+
calldata: adapter.constructorCalldata(addressSeed),
|
|
2325
3194
|
version: 1
|
|
2326
3195
|
};
|
|
2327
|
-
const
|
|
3196
|
+
const initCall = adapter.buildInitialize(address, devicePubkey);
|
|
3197
|
+
const deployRes = await account.executePaymasterTransaction([initCall], {
|
|
2328
3198
|
feeMode: { mode: "sponsored" },
|
|
2329
3199
|
deploymentData
|
|
2330
3200
|
});
|
|
@@ -2342,7 +3212,7 @@ var Cavos = class _Cavos {
|
|
|
2342
3212
|
console.warn("[Cavos] isAuthorizedSigner read failed:", e);
|
|
2343
3213
|
isSigner = !alreadyDeployed;
|
|
2344
3214
|
}
|
|
2345
|
-
|
|
3215
|
+
const cavos = new _Cavos(
|
|
2346
3216
|
identity,
|
|
2347
3217
|
address,
|
|
2348
3218
|
isSigner ? "ready" : "needs-device-approval",
|
|
@@ -2351,24 +3221,33 @@ var Cavos = class _Cavos {
|
|
|
2351
3221
|
devicePubkey,
|
|
2352
3222
|
paymasterConfig
|
|
2353
3223
|
);
|
|
3224
|
+
cavos.isNewAccount = !alreadyDeployed && isSigner;
|
|
3225
|
+
return cavos;
|
|
2354
3226
|
}
|
|
2355
3227
|
/** This device's public key (e.g. to request addition to an existing wallet). */
|
|
2356
3228
|
get publicKey() {
|
|
2357
3229
|
return this.devicePubkey;
|
|
2358
3230
|
}
|
|
2359
3231
|
/** Execute a sponsored (gasless) multicall, signed silently by the device. */
|
|
2360
|
-
async execute(calls) {
|
|
3232
|
+
async execute(calls, opts) {
|
|
2361
3233
|
if (this.status !== "ready") {
|
|
2362
3234
|
throw new Error("kit: this device is not yet an authorized signer of the wallet");
|
|
2363
3235
|
}
|
|
3236
|
+
if (opts?.sponsored === false) {
|
|
3237
|
+
const res2 = await this.account.execute(calls);
|
|
3238
|
+
return { transactionHash: res2.transaction_hash };
|
|
3239
|
+
}
|
|
2364
3240
|
const res = await this.account.executePaymasterTransaction(calls, {
|
|
2365
3241
|
feeMode: { mode: "sponsored" }
|
|
2366
3242
|
});
|
|
2367
3243
|
return { transactionHash: res.transaction_hash };
|
|
2368
3244
|
}
|
|
2369
|
-
/**
|
|
2370
|
-
|
|
2371
|
-
|
|
3245
|
+
/**
|
|
3246
|
+
* Authorize an additional device signer. Sponsored by default; pass
|
|
3247
|
+
* `{ sponsored: false }` to pay the fee from the account's own ETH balance.
|
|
3248
|
+
*/
|
|
3249
|
+
async addSigner(pubkey, opts) {
|
|
3250
|
+
return this.execute([this.adapter.buildAddSigner(this.address, pubkey)], opts);
|
|
2372
3251
|
}
|
|
2373
3252
|
/**
|
|
2374
3253
|
* Enroll a passkey as an APPROVER so the user can later add devices from any
|
|
@@ -2377,27 +3256,45 @@ var Cavos = class _Cavos {
|
|
|
2377
3256
|
* approver. Call this whenever the app decides to prompt "turn on device
|
|
2378
3257
|
* approvals". Returns the passkey's public key + the enrollment tx hash.
|
|
2379
3258
|
*/
|
|
2380
|
-
async enrollPasskey(passkey, params) {
|
|
3259
|
+
async enrollPasskey(passkey, params, opts) {
|
|
2381
3260
|
const enrolled = await passkey.enroll(params);
|
|
2382
|
-
const { transactionHash } = await this.addApprover(enrolled.publicKey);
|
|
3261
|
+
const { transactionHash } = await this.addApprover(enrolled.publicKey, opts);
|
|
2383
3262
|
return { publicKey: enrolled.publicKey, transactionHash };
|
|
2384
3263
|
}
|
|
2385
3264
|
/**
|
|
2386
|
-
* Register an ALREADY-enrolled passkey public key as an approver (gasless
|
|
2387
|
-
* device-signed). Idempotent. Use this to register ONE passkey across
|
|
2388
|
-
* chains without re-prompting `passkey.enroll()` on each: enroll once,
|
|
2389
|
-
* call `addApprover(pubkey)` on each chain's wallet.
|
|
3265
|
+
* Register an ALREADY-enrolled passkey public key as an approver (gasless by
|
|
3266
|
+
* default, device-signed). Idempotent. Use this to register ONE passkey across
|
|
3267
|
+
* multiple chains without re-prompting `passkey.enroll()` on each: enroll once,
|
|
3268
|
+
* then call `addApprover(pubkey)` on each chain's wallet. Pass
|
|
3269
|
+
* `{ sponsored: false }` to pay the fee from the account's own balance.
|
|
2390
3270
|
*/
|
|
2391
|
-
async addApprover(pubkey) {
|
|
3271
|
+
async addApprover(pubkey, opts) {
|
|
2392
3272
|
if (this.status !== "ready") {
|
|
2393
3273
|
throw new Error("kit: addApprover requires a ready, authorized device");
|
|
2394
3274
|
}
|
|
2395
3275
|
if (await this.adapter.isApprover(this.address, pubkey)) return {};
|
|
2396
|
-
const { transactionHash } = await this.execute(
|
|
2397
|
-
this.adapter.buildAddApprover(this.address, pubkey)
|
|
2398
|
-
|
|
3276
|
+
const { transactionHash } = await this.execute(
|
|
3277
|
+
[this.adapter.buildAddApprover(this.address, pubkey)],
|
|
3278
|
+
opts
|
|
3279
|
+
);
|
|
3280
|
+
try {
|
|
3281
|
+
await this.account.waitForTransaction(transactionHash);
|
|
3282
|
+
} catch (e) {
|
|
3283
|
+
console.warn("[Cavos] add_approver receipt wait failed:", e);
|
|
3284
|
+
}
|
|
2399
3285
|
return { transactionHash };
|
|
2400
3286
|
}
|
|
3287
|
+
/** True if this account already has a passkey enrolled as an approver, so a
|
|
3288
|
+
* new device can be approved with the passkey instead of the email flow. */
|
|
3289
|
+
async hasPasskey() {
|
|
3290
|
+
return this.adapter.hasPasskeyApprover(this.address);
|
|
3291
|
+
}
|
|
3292
|
+
/** Re-read (from chain) whether THIS device is now an authorized signer.
|
|
3293
|
+
* Cheap and side-effect free — used to poll for readiness after a passkey /
|
|
3294
|
+
* device approval submits, before the new signer is indexed. */
|
|
3295
|
+
async isReady() {
|
|
3296
|
+
return this.adapter.isAuthorizedSigner(this.address, this.devicePubkey);
|
|
3297
|
+
}
|
|
2401
3298
|
/**
|
|
2402
3299
|
* From a brand-new browser (status `needs-device-approval`), use the user's
|
|
2403
3300
|
* synced passkey to authorize adding THIS device — no trip back to an already-
|
|
@@ -2466,11 +3363,11 @@ var Cavos = class _Cavos {
|
|
|
2466
3363
|
* add_signer (gasless). Returns the transaction hash (or undefined when the
|
|
2467
3364
|
* backup was already set up).
|
|
2468
3365
|
*/
|
|
2469
|
-
async setupRecovery(code) {
|
|
3366
|
+
async setupRecovery(code, opts) {
|
|
2470
3367
|
const { publicKey: backupPubkey } = deriveBackupKey(code);
|
|
2471
3368
|
const already = await this.adapter.isAuthorizedSigner(this.address, backupPubkey);
|
|
2472
3369
|
if (already) return void 0;
|
|
2473
|
-
return this.addSigner(backupPubkey);
|
|
3370
|
+
return this.addSigner(backupPubkey, opts);
|
|
2474
3371
|
}
|
|
2475
3372
|
/**
|
|
2476
3373
|
* Recover an account after losing every device signer. Derives the backup key
|
|
@@ -2497,11 +3394,11 @@ var Cavos = class _Cavos {
|
|
|
2497
3394
|
const backup = BackupSigner.fromCode(opts.code);
|
|
2498
3395
|
const backupAdapter = new StarknetAdapter({ classHash, signer: backup, provider });
|
|
2499
3396
|
const backendUrl = opts.backendUrl ?? "https://cavos.xyz";
|
|
2500
|
-
const
|
|
2501
|
-
|
|
2502
|
-
if (!existing) {
|
|
3397
|
+
const address = opts.address ?? await lookupAddress(opts, backendUrl, network);
|
|
3398
|
+
if (!address) {
|
|
2503
3399
|
throw new Error("kit: no account found for this identity \u2014 nothing to recover");
|
|
2504
3400
|
}
|
|
3401
|
+
const existing = { address };
|
|
2505
3402
|
const backupAccount = new starknet.Account({
|
|
2506
3403
|
provider,
|
|
2507
3404
|
address: existing.address,
|
|
@@ -2532,7 +3429,7 @@ var Cavos = class _Cavos {
|
|
|
2532
3429
|
return new _Cavos(opts.identity, existing.address, "ready", account, adapter, devicePubkey);
|
|
2533
3430
|
}
|
|
2534
3431
|
};
|
|
2535
|
-
var
|
|
3432
|
+
var defaultRegistry2 = new InMemoryWalletRegistry();
|
|
2536
3433
|
var DEVICE_REQUEST_DEDUP_MS = 5 * 60 * 1e3;
|
|
2537
3434
|
var lastDeviceRequest = /* @__PURE__ */ new Map();
|
|
2538
3435
|
async function isDeployed(provider, address) {
|
|
@@ -2543,6 +3440,11 @@ async function isDeployed(provider, address) {
|
|
|
2543
3440
|
return false;
|
|
2544
3441
|
}
|
|
2545
3442
|
}
|
|
3443
|
+
async function lookupAddress(opts, backendUrl, network) {
|
|
3444
|
+
const registry = opts.registry ?? (opts.appId ? new HttpWalletRegistry({ baseUrl: backendUrl, appId: opts.appId, network }) : defaultRegistry2);
|
|
3445
|
+
const existing = await registry.lookup(opts.identity.userId);
|
|
3446
|
+
return existing?.address ?? null;
|
|
3447
|
+
}
|
|
2546
3448
|
async function paymasterExecuteDirect(paymaster, userAddress, call) {
|
|
2547
3449
|
const body = {
|
|
2548
3450
|
jsonrpc: "2.0",
|
|
@@ -2577,6 +3479,101 @@ async function paymasterExecuteDirect(paymaster, userAddress, call) {
|
|
|
2577
3479
|
}
|
|
2578
3480
|
return { transactionHash: json.result?.transaction_hash ?? json.result?.tracking_id };
|
|
2579
3481
|
}
|
|
3482
|
+
var PRF_SALT = sha256.sha256(new TextEncoder().encode("cavos-stellar-prf-v1"));
|
|
3483
|
+
var PasskeyPrf = class {
|
|
3484
|
+
constructor(opts = {}) {
|
|
3485
|
+
if (typeof window === "undefined" || !navigator.credentials) {
|
|
3486
|
+
throw new Error("kit/passkey-prf: WebAuthn is only available in a browser");
|
|
3487
|
+
}
|
|
3488
|
+
this.rpId = opts.rpId ?? window.location.hostname;
|
|
3489
|
+
this.rpName = opts.rpName ?? this.rpId;
|
|
3490
|
+
if (isIpAddress(this.rpId)) {
|
|
3491
|
+
throw new Error(
|
|
3492
|
+
`kit/passkey-prf: passkeys can't use an IP address as the domain ("${this.rpId}"). Use http://localhost, a real HTTPS domain, or a tunnel \u2014 or pass an explicit \`rpId\`.`
|
|
3493
|
+
);
|
|
3494
|
+
}
|
|
3495
|
+
}
|
|
3496
|
+
/** True if this platform advertises a usable passkey (platform authenticator). */
|
|
3497
|
+
static async isSupported() {
|
|
3498
|
+
if (typeof window === "undefined" || !window.PublicKeyCredential) return false;
|
|
3499
|
+
try {
|
|
3500
|
+
return await window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
|
|
3501
|
+
} catch {
|
|
3502
|
+
return false;
|
|
3503
|
+
}
|
|
3504
|
+
}
|
|
3505
|
+
/**
|
|
3506
|
+
* Create a new synced, discoverable passkey with the PRF extension enabled, then
|
|
3507
|
+
* immediately read its PRF secret. Returns the credential id and the 32-byte
|
|
3508
|
+
* secret so the caller can enroll the passkey factor in one step. Some
|
|
3509
|
+
* authenticators don't return PRF results on create — in that case `secret` is
|
|
3510
|
+
* undefined and the caller should follow up with `getSecret()`.
|
|
3511
|
+
*/
|
|
3512
|
+
async enroll(params) {
|
|
3513
|
+
const cred = await navigator.credentials.create({
|
|
3514
|
+
publicKey: {
|
|
3515
|
+
challenge: buf(crypto.getRandomValues(new Uint8Array(32))),
|
|
3516
|
+
rp: { id: this.rpId, name: this.rpName },
|
|
3517
|
+
user: {
|
|
3518
|
+
id: buf(userHandle(params.userId)),
|
|
3519
|
+
name: params.userName,
|
|
3520
|
+
displayName: params.displayName ?? params.userName
|
|
3521
|
+
},
|
|
3522
|
+
pubKeyCredParams: [{ type: "public-key", alg: -7 }],
|
|
3523
|
+
// ES256 (P-256)
|
|
3524
|
+
authenticatorSelection: {
|
|
3525
|
+
residentKey: "required",
|
|
3526
|
+
requireResidentKey: true,
|
|
3527
|
+
userVerification: "required"
|
|
3528
|
+
},
|
|
3529
|
+
attestation: "none",
|
|
3530
|
+
extensions: { prf: { eval: { first: buf(PRF_SALT) } } }
|
|
3531
|
+
}
|
|
3532
|
+
});
|
|
3533
|
+
if (!cred) throw new Error("kit/passkey-prf: enrollment cancelled");
|
|
3534
|
+
return { credentialId: new Uint8Array(cred.rawId), secret: readPrf(cred) };
|
|
3535
|
+
}
|
|
3536
|
+
/**
|
|
3537
|
+
* Get the passkey's 32-byte PRF secret. Uses discoverable credentials (no
|
|
3538
|
+
* `allowCredentials`), so it works from a brand-new browser — the OS shows the
|
|
3539
|
+
* synced passkey picker. Throws if the authenticator doesn't support PRF.
|
|
3540
|
+
*/
|
|
3541
|
+
async getSecret() {
|
|
3542
|
+
const cred = await navigator.credentials.get({
|
|
3543
|
+
publicKey: {
|
|
3544
|
+
challenge: buf(crypto.getRandomValues(new Uint8Array(32))),
|
|
3545
|
+
rpId: this.rpId,
|
|
3546
|
+
allowCredentials: [],
|
|
3547
|
+
userVerification: "required",
|
|
3548
|
+
extensions: { prf: { eval: { first: buf(PRF_SALT) } } }
|
|
3549
|
+
}
|
|
3550
|
+
});
|
|
3551
|
+
if (!cred) throw new Error("kit/passkey-prf: assertion cancelled");
|
|
3552
|
+
const secret = readPrf(cred);
|
|
3553
|
+
if (!secret) {
|
|
3554
|
+
throw new Error(
|
|
3555
|
+
"kit/passkey-prf: this authenticator did not return a PRF result \u2014 PRF is unsupported here"
|
|
3556
|
+
);
|
|
3557
|
+
}
|
|
3558
|
+
return secret;
|
|
3559
|
+
}
|
|
3560
|
+
};
|
|
3561
|
+
function readPrf(cred) {
|
|
3562
|
+
const results = cred.getClientExtensionResults().prf?.results?.first;
|
|
3563
|
+
return results ? new Uint8Array(results) : void 0;
|
|
3564
|
+
}
|
|
3565
|
+
function isIpAddress(host) {
|
|
3566
|
+
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(host)) return true;
|
|
3567
|
+
if (host.includes(":")) return true;
|
|
3568
|
+
return false;
|
|
3569
|
+
}
|
|
3570
|
+
function userHandle(userId) {
|
|
3571
|
+
const bytes = new TextEncoder().encode(userId);
|
|
3572
|
+
return bytes.length <= 64 ? bytes : sha256.sha256(bytes);
|
|
3573
|
+
}
|
|
3574
|
+
function buf(bytes) {
|
|
3575
|
+
return bytes.slice();
|
|
3576
|
+
}
|
|
2580
3577
|
var CavosAuth = class {
|
|
2581
3578
|
constructor(opts = {}) {
|
|
2582
3579
|
this.opts = opts;
|
|
@@ -2714,6 +3711,90 @@ function bytesToChunks(bytes) {
|
|
|
2714
3711
|
for (const b of bytes.subarray(0, 31)) w = w << 8n | BigInt(b);
|
|
2715
3712
|
return w;
|
|
2716
3713
|
}
|
|
3714
|
+
var PasskeySigner = class {
|
|
3715
|
+
constructor(opts = {}) {
|
|
3716
|
+
if (typeof window === "undefined" || !navigator.credentials) {
|
|
3717
|
+
throw new Error("kit/passkey: WebAuthn is only available in a browser");
|
|
3718
|
+
}
|
|
3719
|
+
this.rpId = opts.rpId ?? window.location.hostname;
|
|
3720
|
+
this.rpName = opts.rpName ?? this.rpId;
|
|
3721
|
+
if (isIpAddress2(this.rpId)) {
|
|
3722
|
+
throw new Error(
|
|
3723
|
+
`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.)`
|
|
3724
|
+
);
|
|
3725
|
+
}
|
|
3726
|
+
}
|
|
3727
|
+
/** True if this platform advertises a usable passkey (platform authenticator). */
|
|
3728
|
+
static async isSupported() {
|
|
3729
|
+
if (typeof window === "undefined" || !window.PublicKeyCredential) return false;
|
|
3730
|
+
try {
|
|
3731
|
+
return await window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
|
|
3732
|
+
} catch {
|
|
3733
|
+
return false;
|
|
3734
|
+
}
|
|
3735
|
+
}
|
|
3736
|
+
/** Create a new synced passkey and return its P-256 public key. */
|
|
3737
|
+
async enroll(params) {
|
|
3738
|
+
const challenge = crypto.getRandomValues(new Uint8Array(32));
|
|
3739
|
+
const cred = await navigator.credentials.create({
|
|
3740
|
+
publicKey: {
|
|
3741
|
+
challenge: buf2(challenge),
|
|
3742
|
+
rp: { id: this.rpId, name: this.rpName },
|
|
3743
|
+
user: {
|
|
3744
|
+
id: buf2(userHandle2(params.userId)),
|
|
3745
|
+
name: params.userName,
|
|
3746
|
+
displayName: params.displayName ?? params.userName
|
|
3747
|
+
},
|
|
3748
|
+
pubKeyCredParams: [{ type: "public-key", alg: -7 }],
|
|
3749
|
+
// ES256 (P-256)
|
|
3750
|
+
authenticatorSelection: {
|
|
3751
|
+
residentKey: "required",
|
|
3752
|
+
requireResidentKey: true,
|
|
3753
|
+
userVerification: "preferred"
|
|
3754
|
+
},
|
|
3755
|
+
attestation: "none"
|
|
3756
|
+
}
|
|
3757
|
+
});
|
|
3758
|
+
if (!cred) throw new Error("kit/passkey: enrollment cancelled");
|
|
3759
|
+
const response = cred.response;
|
|
3760
|
+
const spki = new Uint8Array(response.getPublicKey());
|
|
3761
|
+
return { publicKey: spkiToPublicKey(spki), credentialId: new Uint8Array(cred.rawId) };
|
|
3762
|
+
}
|
|
3763
|
+
/**
|
|
3764
|
+
* Produce a WebAuthn assertion over `challenge` (a 32-byte value the caller
|
|
3765
|
+
* derives from the signer being added + the on-chain nonce). Uses discoverable
|
|
3766
|
+
* credentials — no `allowCredentials` — so it works on a brand-new browser.
|
|
3767
|
+
*/
|
|
3768
|
+
async assert(challenge) {
|
|
3769
|
+
const cred = await navigator.credentials.get({
|
|
3770
|
+
publicKey: {
|
|
3771
|
+
challenge: buf2(challenge),
|
|
3772
|
+
rpId: this.rpId,
|
|
3773
|
+
allowCredentials: [],
|
|
3774
|
+
userVerification: "preferred"
|
|
3775
|
+
}
|
|
3776
|
+
});
|
|
3777
|
+
if (!cred) throw new Error("kit/passkey: assertion cancelled");
|
|
3778
|
+
const response = cred.response;
|
|
3779
|
+
const authenticatorData = new Uint8Array(response.authenticatorData);
|
|
3780
|
+
const clientDataJSON = new Uint8Array(response.clientDataJSON);
|
|
3781
|
+
const { r, s } = derToRs(new Uint8Array(response.signature));
|
|
3782
|
+
const challengeOffset = challengeOffsetOf(clientDataJSON, base64urlEncode(challenge));
|
|
3783
|
+
return { authenticatorData, clientDataJSON, r, s, challengeOffset };
|
|
3784
|
+
}
|
|
3785
|
+
};
|
|
3786
|
+
function isIpAddress2(host) {
|
|
3787
|
+
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(host)) return true;
|
|
3788
|
+
if (host.includes(":")) return true;
|
|
3789
|
+
return false;
|
|
3790
|
+
}
|
|
3791
|
+
function userHandle2(userId) {
|
|
3792
|
+
const bytes = new TextEncoder().encode(userId);
|
|
3793
|
+
return bytes.length <= 64 ? bytes : sha256.sha256(bytes);
|
|
3794
|
+
}
|
|
3795
|
+
function buf2(bytes) {
|
|
3796
|
+
return bytes.slice();
|
|
3797
|
+
}
|
|
2717
3798
|
var cavosLogoBase64 = "iVBORw0KGgoAAAANSUhEUgAAADMAAAA/CAYAAABNY/BRAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAlZSURBVHgB7VpbaCRZGf6r6lR3VV+STieZTBKCmZlO59LDisZBR2Q3D8v6MMrCgswirIqi4LI+7OKyIDp4YUAUXWFdxBurIIw+rcIgroMsKqvoMATdzSbpJEwmM8nk0rn3pbrrVJ39T3X3TE/S6a5TXVn2Yb88hKquc/nPOd/3X6oAjgfK4ODA47FY7AO1N9vb20/395+8wH+HY4AM/kOJx+PhcLjtjzs7O5naHyRJ2orFOq/29PRoeEnAZ/htDJ+g1dvbu8mYDWjUAztg27bM73d1da7jJQWfDfLTGN4XHR4aulE0siDhXz3g7oBRyAWHh5P/gbJBvs3Br474zO2hoTM/YGB9CJhEGj8sKcyi5xKJ05fwkoFP8MsY5dTAwMOKrDxvW7aryeGRYyoh3xkYOPkR8EkQ/DBGQUIH9Wjk79Qsihwb2TRN2haN/7evry+I1yq0iFaNcQjf1dW1VTLyaIgkRmjGSNHI0Y6O2BpemdCiILRiDOcJ5UQuGnlZ2JD73RAUBB37eQNaFASvDR2pQuX6LrOtc3yFoQU4gmDT88OJxAvQgiB4NUbp7+//mCTL37Ityxc14sIhK/L3sd8PgkdB8GIMSSQSSizW/i9qGn76CZlSk8ba2ycHBwe5GAjvtuhE+AA0GAzs4jmn3nlSH6wiCOGwvgpl/gjtkIgxFcIPv1EyCorksyE1wxAjX4jgOK/jhQUCc3T7oEP4kWTiG8w2P85aJHzTwSTggjCRTJx+FgQEwa0xysDAwDhI8mUkqg3vAlBYbELUH+O4KXDJHzfGkPHxcaktGrlOqZCHh62trXzt9d7eXl5AeOWSWaJtbdE3K2M2NajZxBzCF4vGtsE9PHPPE9u2SlAmcS0MBswEt8DjbOSzNJVKuRKERsZUCD/0ulHIB0UJbzN2F+rOj22BACRJJsVCrm1kZPg1aCIIDXcGCf8cMHvCi4e3aGmy3n3DMCZBHIptmY8lk4mnGz10lDHE8cSS8iOLWsKEV4gC2dzelXq/FQrGH2RZ3M9y4SGK8jIGtUNwBH/q9UomJiYAPfwkpd48vKIQ3AH7rwCH081CofAnEgiCB8ilokEx3ZipzvPQAweuHcJnMhsbRj5niRC+Bjxcu4PFjB2o4yN2d3e3mc02+XMgDO5Q92lqbKyuINQa4xB+JJm8hoSPYK7uKdhTFEXOZ7PPw9E7Ku/u7b0gKbIEHsAFAZW1A+d5FQ4IwgMDjowkn2GMPuo9pGeUBDT79srK76GB515ZWfl1IKBR/jx4AK4Cr/JcSCROfan2ftUYnvqexf8vWR49PM7cVtQA2d3cPA/l7T/KGN6/sr29M4HcIeDpuOGWWNQOqIFfnThx4hRU+FP1rAxV4v+0VPREePQdVlDTsJ38+J3V1esuJmjh7vwbV/diIKjhKjMvBjmC0N3dNV8ZT3HO7djY2AaG9B2yCE8kpwbG1IAmMQZv5nK5R5aWlvYqHbsJWvjYEp6Iro6O9ms49kOmWUK7mCSSazKwqaZHtqam3u6RRkaGrimy+gnsWiCCtqlZMueyueyVXM74OVcoKB8tC8ThtItEIifa26NfDunhJwMBNQHlhXVnFq5msWS8Vu2sFXhSpWPo5zjq5u/jfRyE1Nt74pOlkrXDPbebBqg2Cs3nd9u7u+cWFxcNqKgSePQXFfCxOdkZqlu4WCwmQqFAhFJ3fWLgit7RjhFJIp09PZ1/wZAd3ELpjGMGLcPIcHLdpMVfLizc+nbNzyJGyRUDdF1XL+l69Cso9zFgFqYQplMMcDUfRYWt7c3POE8nk0OvYL7wOVwbUUVAUQSm6SGZlgqXZ+dufhPKBXA32aTzzdCZMz9Ug8GvY626KsNCqoa7YkuK8ovZ2bmv8obcAHt0dHSxZOT6eSAH4rCxGol9Bm5Mz8ycq/bZ4HnHt6RSo/8rGoWzFW/iKfIgajA9m06P8fbVQcn09PSgFopyQ9zn6PchY/IkmSXjw5gNvgTNnZ2VTCZ/UzGEz8GDj2BUC0WUiiFOjFftxHm/iCnIQEDTVCw6eCWzhOWhZ5ADoQYTlPEtdEwl8ufREE+OEudnBTSdrK2t9UF5l53ou3ZAury8vFIq0aeIQqrqIgqpVCwwXI8XG7S3u7vjLxtGgS+YF2MYUQNKMVd4IpPJ8Be990KoQ6s3Pz//O0lRX2VeXy1gq0i47Slo0B5zmSclt1J1ANiKx6JXFm7dehUO8PKgMU4oPTMz8wQqVAYjcy/Jk2RjIMs/YIDDKy9hQJnCXEQGD7tiO4TXbqfT6c/Cfd90D/XONd82FUPqvrIgiGeD1DQhFNIuwuHdYRgZX3R8iDAY1ZHwqJaDUJb1Q7w+iqR8NGl7e21EDerC2SCeA9D10IV6v4VC4U8xW/gE25jEkY2NjQSUCV93NRpJIl1ZycxZlvk12WWoUwtN00br3VdV9RQIQiEqOuXiF9bX129Cg5yp6STT6YWfoh/9Gy62qFy31bvphCsC4DzBsf+cXlj8LTQRpWbGOIIwOzv7qB7S90X4U6lNawduh0U0Eg2x9VB4Oz03V/0SqiVjOPi2kmyucDKohQlI7g2Kx+OB2utoNKq51jCJEz4s89weKt8bNGvilgsUw30zu7MzjrGQ5/IQL4C4fNRWiUZ2dzcfgkpx0k0jEWJbt1ZWJlGpLskeq5FuwXMrrPE+e+fO2lsgUCQRVSkJQ+3vYQ50ncdHcBzA48Uk5R+z8/M/AdF0AMTgRNgYqX4UHVjRa3n1KOAZxGJixEQP/whUImGR9l7KM06Evbq6hoIQEhIEhzNHsgZDej3EFYtL971IWARea0342iOTzxeyDxPiCIIrYvOsEOprAOPCkslsnp+amuK74ekIt1I4s27evP1Pm9kvYoQg0OwwDVBQMINilzE/4Z86euZiq1VAOZ2ef46QwDSv+YInMCrL5Mbs3ByvH3BL3/WvmqrgR0J9e3o6pelREBcERoN6BGZm07xu4Nl/VeFHfdb5gg/fADiCwIsM7pqhIRhRYI2sEzwS/iD8KjbT/f397b3dvU+rgaCb6j3jL6b2s9nHMLPlX3H44rP8rJyzpeXlq6i9r0CzgNApCsk/w928Bj4Z4vQL/oEbIGOE8EVM6JaOdidYWyJkGiPxp6FO6vteg1NETKVSWHbFkP9BRPn92uf8xHG8oHHez9+9e/esacbjtT9gShBbXV0drYzrayjE8Q6F7uAm3ZgyLQAAAABJRU5ErkJggg==";
|
|
2718
3799
|
var STYLE_ID = "cavos-modal-styles-v2";
|
|
2719
3800
|
function injectStyles() {
|
|
@@ -2817,6 +3898,17 @@ var EmailIcon = () => /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "16", heig
|
|
|
2817
3898
|
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "2", y: "4", width: "20", height: "16", rx: "2" }),
|
|
2818
3899
|
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "m22 7-8.97 5.7a1.94 1.94 0 01-2.06 0L2 7" })
|
|
2819
3900
|
] });
|
|
3901
|
+
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: [
|
|
3902
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M2 12C2 6.5 6.5 2 12 2a10 10 0 0 1 8 4" }),
|
|
3903
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M5 19.5C5.5 18 6 15 6 12c0-.7.12-1.37.34-2" }),
|
|
3904
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M17.29 21.02c.12-.6.43-2.3.5-3.02" }),
|
|
3905
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 10a2 2 0 0 0-2 2c0 1.02-.1 2.51-.26 4" }),
|
|
3906
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8.65 22c.21-.66.45-1.32.57-2" }),
|
|
3907
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M14 13.12c0 2.38 0 6.38-1 8.88" }),
|
|
3908
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M2 16h.01" }),
|
|
3909
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M21.8 16c.2-2 .131-5.354 0-6" }),
|
|
3910
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9 6.8a6 6 0 0 1 9 5.2c0 .47 0 1.17-.02 2" })
|
|
3911
|
+
] });
|
|
2820
3912
|
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
3913
|
function Spinner({ size = 16, color = "#888" }) {
|
|
2822
3914
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -2967,7 +4059,11 @@ function CavosAuthModal({
|
|
|
2967
4059
|
authError,
|
|
2968
4060
|
clearAuthError,
|
|
2969
4061
|
resendDeviceApproval,
|
|
2970
|
-
recover
|
|
4062
|
+
recover,
|
|
4063
|
+
passkeySupported,
|
|
4064
|
+
enrollPasskeyDefault,
|
|
4065
|
+
approveDeviceWithPasskey,
|
|
4066
|
+
setupRecovery
|
|
2971
4067
|
} = useCavos();
|
|
2972
4068
|
const isMobile = useIsMobile();
|
|
2973
4069
|
const isLight = theme !== "dark";
|
|
@@ -2995,7 +4091,12 @@ function CavosAuthModal({
|
|
|
2995
4091
|
const [deviceResendBusy, setDeviceResendBusy] = react.useState(false);
|
|
2996
4092
|
const [recoverCode, setRecoverCode] = react.useState("");
|
|
2997
4093
|
const [recoverBusy, setRecoverBusy] = react.useState(false);
|
|
4094
|
+
const [pkBusy, setPkBusy] = react.useState(false);
|
|
4095
|
+
const [pkError, setPkError] = react.useState("");
|
|
4096
|
+
const [savedRecoveryCode, setSavedRecoveryCode] = react.useState("");
|
|
4097
|
+
const [copied, setCopied] = react.useState(false);
|
|
2998
4098
|
const doneHandledRef = react.useRef(false);
|
|
4099
|
+
const secureHandledRef = react.useRef(false);
|
|
2999
4100
|
const closeTimerRef = react.useRef(null);
|
|
3000
4101
|
const otpBoxRefs = react.useRef([null, null, null, null, null, null]);
|
|
3001
4102
|
const setOtpDigit = (i, raw) => {
|
|
@@ -3056,6 +4157,7 @@ function CavosAuthModal({
|
|
|
3056
4157
|
setOtpCode("");
|
|
3057
4158
|
setError("");
|
|
3058
4159
|
doneHandledRef.current = false;
|
|
4160
|
+
secureHandledRef.current = false;
|
|
3059
4161
|
}, 1600);
|
|
3060
4162
|
}, [onClose]);
|
|
3061
4163
|
react.useEffect(() => {
|
|
@@ -3066,13 +4168,26 @@ function CavosAuthModal({
|
|
|
3066
4168
|
doneHandledRef.current = false;
|
|
3067
4169
|
}
|
|
3068
4170
|
if (isAuthenticated && address && walletStatus.isReady) {
|
|
3069
|
-
|
|
4171
|
+
if (walletStatus.isNewAccount && !secureHandledRef.current) {
|
|
4172
|
+
if (screen !== "secure-account" && screen !== "recovery-code" && !doneHandledRef.current) {
|
|
4173
|
+
setScreen("secure-account");
|
|
4174
|
+
}
|
|
4175
|
+
} else {
|
|
4176
|
+
triggerDone(address);
|
|
4177
|
+
}
|
|
3070
4178
|
}
|
|
3071
|
-
if (walletStatus.
|
|
3072
|
-
|
|
3073
|
-
|
|
4179
|
+
if (walletStatus.needsDeviceApproval && // A deploy in progress owns the screen; never fight the deploying branch
|
|
4180
|
+
// above (both flags true would oscillate deploying ↔ approval → loop).
|
|
4181
|
+
!walletStatus.isDeploying && screen !== "recover" && screen !== "device-approval" && screen !== "passkey-approval") {
|
|
4182
|
+
if (walletStatus.hasPasskey && passkeySupported) {
|
|
4183
|
+
setScreen("passkey-approval");
|
|
4184
|
+
doneHandledRef.current = false;
|
|
4185
|
+
} else if (walletStatus.awaitingApproval) {
|
|
4186
|
+
setScreen("device-approval");
|
|
4187
|
+
doneHandledRef.current = false;
|
|
4188
|
+
}
|
|
3074
4189
|
}
|
|
3075
|
-
}, [open, isAuthenticated, address, walletStatus.isReady, walletStatus.isDeploying, walletStatus.awaitingApproval, screen, triggerDone]);
|
|
4190
|
+
}, [open, isAuthenticated, address, walletStatus.isReady, walletStatus.isDeploying, walletStatus.awaitingApproval, walletStatus.needsDeviceApproval, walletStatus.hasPasskey, walletStatus.isNewAccount, passkeySupported, screen, triggerDone]);
|
|
3076
4191
|
react.useEffect(() => () => {
|
|
3077
4192
|
if (closeTimerRef.current) clearTimeout(closeTimerRef.current);
|
|
3078
4193
|
}, []);
|
|
@@ -3082,9 +4197,61 @@ function CavosAuthModal({
|
|
|
3082
4197
|
setEmail("");
|
|
3083
4198
|
setOtpCode("");
|
|
3084
4199
|
setError("");
|
|
4200
|
+
setPkError("");
|
|
4201
|
+
setSavedRecoveryCode("");
|
|
4202
|
+
setCopied(false);
|
|
3085
4203
|
doneHandledRef.current = false;
|
|
4204
|
+
secureHandledRef.current = false;
|
|
3086
4205
|
onClose();
|
|
3087
4206
|
};
|
|
4207
|
+
const finishSecureStep = () => {
|
|
4208
|
+
secureHandledRef.current = true;
|
|
4209
|
+
if (address) triggerDone(address);
|
|
4210
|
+
};
|
|
4211
|
+
const handleSetupPasskey = async () => {
|
|
4212
|
+
setPkBusy(true);
|
|
4213
|
+
setPkError("");
|
|
4214
|
+
try {
|
|
4215
|
+
await enrollPasskeyDefault();
|
|
4216
|
+
finishSecureStep();
|
|
4217
|
+
} catch (e) {
|
|
4218
|
+
setPkError(e instanceof Error ? e.message : "We couldn't set up your passkey. Try again.");
|
|
4219
|
+
} finally {
|
|
4220
|
+
setPkBusy(false);
|
|
4221
|
+
}
|
|
4222
|
+
};
|
|
4223
|
+
const handleSaveRecovery = async () => {
|
|
4224
|
+
setPkBusy(true);
|
|
4225
|
+
setPkError("");
|
|
4226
|
+
try {
|
|
4227
|
+
const code = await setupRecovery();
|
|
4228
|
+
setSavedRecoveryCode(code);
|
|
4229
|
+
setScreen("recovery-code");
|
|
4230
|
+
} catch (e) {
|
|
4231
|
+
setPkError(e instanceof Error ? e.message : "We couldn't create your recovery phrase. Try again.");
|
|
4232
|
+
} finally {
|
|
4233
|
+
setPkBusy(false);
|
|
4234
|
+
}
|
|
4235
|
+
};
|
|
4236
|
+
const handleCopyRecovery = async () => {
|
|
4237
|
+
try {
|
|
4238
|
+
await navigator.clipboard.writeText(savedRecoveryCode);
|
|
4239
|
+
setCopied(true);
|
|
4240
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
4241
|
+
} catch {
|
|
4242
|
+
}
|
|
4243
|
+
};
|
|
4244
|
+
const handlePasskeyApprove = async () => {
|
|
4245
|
+
setPkBusy(true);
|
|
4246
|
+
setPkError("");
|
|
4247
|
+
try {
|
|
4248
|
+
await approveDeviceWithPasskey();
|
|
4249
|
+
setPkBusy(false);
|
|
4250
|
+
} catch (e) {
|
|
4251
|
+
setPkError(e instanceof Error ? e.message : "We couldn't verify your passkey. Try again or use email.");
|
|
4252
|
+
setPkBusy(false);
|
|
4253
|
+
}
|
|
4254
|
+
};
|
|
3088
4255
|
const handleOAuth = async (provider) => {
|
|
3089
4256
|
setError("");
|
|
3090
4257
|
setBusy(true);
|
|
@@ -3175,6 +4342,85 @@ function CavosAuthModal({
|
|
|
3175
4342
|
setDeviceResendBusy(false);
|
|
3176
4343
|
}
|
|
3177
4344
|
};
|
|
4345
|
+
if (screen === "secure-account") {
|
|
4346
|
+
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: [
|
|
4347
|
+
isMobile && /* @__PURE__ */ jsxRuntime.jsx("div", { style: handle }),
|
|
4348
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: isMobile ? "28px 24px 28px" : "44px 24px 28px", textAlign: "center" }, children: [
|
|
4349
|
+
/* @__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: [
|
|
4350
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" }),
|
|
4351
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M9 12l2 2 4-4" })
|
|
4352
|
+
] }) }),
|
|
4353
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { style: { margin: "0 0 8px", fontSize: "17px", fontWeight: 600, color: textColor, letterSpacing: "-0.02em" }, children: "Keep your account safe" }),
|
|
4354
|
+
/* @__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." }),
|
|
4355
|
+
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 }),
|
|
4356
|
+
passkeySupported ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
4357
|
+
/* @__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: [
|
|
4358
|
+
pkBusy ? /* @__PURE__ */ jsxRuntime.jsx(Spinner, { size: 16, color: "#fff" }) : /* @__PURE__ */ jsxRuntime.jsx(FingerprintIcon, { size: 18, color: "#fff" }),
|
|
4359
|
+
pkBusy ? "Setting up\u2026" : "Set up a passkey"
|
|
4360
|
+
] }),
|
|
4361
|
+
/* @__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." }),
|
|
4362
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", alignItems: "center", gap: "12px", margin: "18px 0" }, children: [
|
|
4363
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "cavos-divider-line", style: { background: isLight ? "rgba(0,0,0,0.08)" : "rgba(255,255,255,0.1)" } }),
|
|
4364
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "11px", color: subTextColor, textTransform: "uppercase", letterSpacing: "0.06em" }, children: "or" }),
|
|
4365
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "cavos-divider-line", style: { background: isLight ? "rgba(0,0,0,0.08)" : "rgba(255,255,255,0.1)" } })
|
|
4366
|
+
] }),
|
|
4367
|
+
/* @__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" })
|
|
4368
|
+
] }) : /* @__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: [
|
|
4369
|
+
pkBusy && /* @__PURE__ */ jsxRuntime.jsx(Spinner, { size: 16, color: "#fff" }),
|
|
4370
|
+
pkBusy ? "Setting up\u2026" : "Save a recovery phrase"
|
|
4371
|
+
] }),
|
|
4372
|
+
/* @__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" })
|
|
4373
|
+
] }),
|
|
4374
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: footer, children: [
|
|
4375
|
+
/* @__PURE__ */ jsxRuntime.jsx(CavosLogo, { invert: !isLight }),
|
|
4376
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Secured by Cavos" })
|
|
4377
|
+
] })
|
|
4378
|
+
] }) });
|
|
4379
|
+
}
|
|
4380
|
+
if (screen === "recovery-code") {
|
|
4381
|
+
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: [
|
|
4382
|
+
isMobile && /* @__PURE__ */ jsxRuntime.jsx("div", { style: handle }),
|
|
4383
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: isMobile ? "28px 24px 28px" : "44px 24px 28px", textAlign: "center" }, children: [
|
|
4384
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { style: { margin: "0 0 8px", fontSize: "17px", fontWeight: 600, color: textColor, letterSpacing: "-0.02em" }, children: "Save your recovery phrase" }),
|
|
4385
|
+
/* @__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." }),
|
|
4386
|
+
/* @__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 }),
|
|
4387
|
+
/* @__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" }) }),
|
|
4388
|
+
/* @__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" })
|
|
4389
|
+
] }),
|
|
4390
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: footer, children: [
|
|
4391
|
+
/* @__PURE__ */ jsxRuntime.jsx(CavosLogo, { invert: !isLight }),
|
|
4392
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Secured by Cavos" })
|
|
4393
|
+
] })
|
|
4394
|
+
] }) });
|
|
4395
|
+
}
|
|
4396
|
+
if (screen === "passkey-approval") {
|
|
4397
|
+
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: [
|
|
4398
|
+
isMobile && /* @__PURE__ */ jsxRuntime.jsx("div", { style: handle }),
|
|
4399
|
+
/* @__PURE__ */ jsxRuntime.jsx("button", { className: "cavos-close", style: close, onClick: handleClose, "aria-label": "Close", children: /* @__PURE__ */ jsxRuntime.jsx(CloseX, {}) }),
|
|
4400
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: { padding: isMobile ? "28px 24px 32px" : "48px 24px 32px", textAlign: "center" }, children: [
|
|
4401
|
+
/* @__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 }) }),
|
|
4402
|
+
/* @__PURE__ */ jsxRuntime.jsx("h2", { style: { margin: "0 0 8px", fontSize: "17px", fontWeight: 600, color: textColor, letterSpacing: "-0.02em" }, children: "Verify it's you" }),
|
|
4403
|
+
/* @__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." }),
|
|
4404
|
+
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 }),
|
|
4405
|
+
/* @__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: [
|
|
4406
|
+
pkBusy && /* @__PURE__ */ jsxRuntime.jsx(Spinner, { size: 15, color: "#fff" }),
|
|
4407
|
+
pkBusy ? "Verifying\u2026" : "Continue with passkey"
|
|
4408
|
+
] }),
|
|
4409
|
+
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: () => {
|
|
4410
|
+
setScreen("device-approval");
|
|
4411
|
+
setPkError("");
|
|
4412
|
+
}, children: "Approve by email instead" }),
|
|
4413
|
+
/* @__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: () => {
|
|
4414
|
+
setScreen("recover");
|
|
4415
|
+
setPkError("");
|
|
4416
|
+
}, children: "Use a recovery phrase" })
|
|
4417
|
+
] }),
|
|
4418
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { style: footer, children: [
|
|
4419
|
+
/* @__PURE__ */ jsxRuntime.jsx(CavosLogo, { invert: !isLight }),
|
|
4420
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Secured by Cavos" })
|
|
4421
|
+
] })
|
|
4422
|
+
] }) });
|
|
4423
|
+
}
|
|
3178
4424
|
if (screen === "device-approval") {
|
|
3179
4425
|
const expired = walletStatus.awaitingApproval && !walletStatus.pendingRequestId;
|
|
3180
4426
|
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 +4480,7 @@ function CavosAuthModal({
|
|
|
3234
4480
|
}, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { ...card, position: "relative" }, children: [
|
|
3235
4481
|
isMobile && /* @__PURE__ */ jsxRuntime.jsx("div", { style: handle }),
|
|
3236
4482
|
/* @__PURE__ */ jsxRuntime.jsx("button", { className: "cavos-close", style: { ...close, left: "16px", right: "auto" }, onClick: () => {
|
|
3237
|
-
setScreen("device-approval");
|
|
4483
|
+
setScreen(walletStatus.hasPasskey && passkeySupported ? "passkey-approval" : "device-approval");
|
|
3238
4484
|
setError("");
|
|
3239
4485
|
setRecoverCode("");
|
|
3240
4486
|
}, "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 +4719,36 @@ var INITIAL_STATUS = {
|
|
|
3473
4719
|
isReady: false,
|
|
3474
4720
|
needsDeviceApproval: false,
|
|
3475
4721
|
awaitingApproval: false,
|
|
3476
|
-
pendingRequestId: null
|
|
4722
|
+
pendingRequestId: null,
|
|
4723
|
+
hasPasskey: false,
|
|
4724
|
+
isNewAccount: false
|
|
3477
4725
|
};
|
|
3478
4726
|
function CavosProvider({ config, modal, children }) {
|
|
3479
4727
|
const [auth] = react.useState(
|
|
3480
4728
|
() => new CavosAuth({ appId: config.appId, backendUrl: config.authBackendUrl })
|
|
3481
4729
|
);
|
|
3482
|
-
const [
|
|
4730
|
+
const [wallet, setWallet] = react.useState(null);
|
|
3483
4731
|
const [identity, setIdentity] = react.useState(null);
|
|
3484
4732
|
const [walletStatus, setWalletStatus] = react.useState(INITIAL_STATUS);
|
|
3485
4733
|
const [isLoading, setIsLoading] = react.useState(false);
|
|
3486
4734
|
const [authError, setAuthError] = react.useState(null);
|
|
3487
4735
|
const [modalOpen, setModalOpen] = react.useState(false);
|
|
4736
|
+
const [passkeySupported, setPasskeySupported] = react.useState(false);
|
|
3488
4737
|
const [branding, setBranding] = react.useState({});
|
|
4738
|
+
react.useEffect(() => {
|
|
4739
|
+
let cancelled = false;
|
|
4740
|
+
PasskeySigner.isSupported().then((ok) => {
|
|
4741
|
+
if (!cancelled) setPasskeySupported(ok);
|
|
4742
|
+
}).catch(() => {
|
|
4743
|
+
});
|
|
4744
|
+
return () => {
|
|
4745
|
+
cancelled = true;
|
|
4746
|
+
};
|
|
4747
|
+
}, []);
|
|
3489
4748
|
const configRef = react.useRef(config);
|
|
3490
4749
|
react.useEffect(() => {
|
|
3491
4750
|
configRef.current = config;
|
|
3492
|
-
}
|
|
4751
|
+
});
|
|
3493
4752
|
react.useEffect(() => {
|
|
3494
4753
|
if (!config.appId || typeof window === "undefined") return;
|
|
3495
4754
|
const base = config.authBackendUrl ?? "https://cavos.xyz";
|
|
@@ -3503,41 +4762,52 @@ function CavosProvider({ config, modal, children }) {
|
|
|
3503
4762
|
const closeModal = react.useCallback(() => setModalOpen(false), []);
|
|
3504
4763
|
const clearAuthError = react.useCallback(() => setAuthError(null), []);
|
|
3505
4764
|
const resendDeviceApproval = react.useCallback(async () => {
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
4765
|
+
const cfg = configRef.current;
|
|
4766
|
+
if (!identity || !wallet || wallet.chain !== "starknet" || !wallet.pendingRequestId) return;
|
|
4767
|
+
const backendUrl = cfg.authBackendUrl ?? "https://cavos.xyz";
|
|
4768
|
+
if (!cfg.appId) return;
|
|
4769
|
+
const recovery = new HttpRecoveryClient({ baseUrl: backendUrl, appId: cfg.appId });
|
|
3510
4770
|
await recovery.requestDeviceAddition({
|
|
3511
4771
|
userId: identity.userId,
|
|
3512
|
-
accountAddress:
|
|
3513
|
-
newSigner:
|
|
4772
|
+
accountAddress: wallet.address,
|
|
4773
|
+
newSigner: wallet.publicKey,
|
|
3514
4774
|
...identity.email ? { email: identity.email } : {}
|
|
3515
4775
|
});
|
|
3516
|
-
}, [identity,
|
|
3517
|
-
const connect = react.useCallback(async (id) => {
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
4776
|
+
}, [identity, wallet]);
|
|
4777
|
+
const connect = react.useCallback(async (id, opts) => {
|
|
4778
|
+
const cfg = configRef.current;
|
|
4779
|
+
if (!opts?.silent) setWalletStatus({ ...INITIAL_STATUS, isDeploying: true });
|
|
4780
|
+
const w = await Cavos.connect({
|
|
4781
|
+
chain: cfg.chain ?? "starknet",
|
|
4782
|
+
network: cfg.network,
|
|
3522
4783
|
identity: id,
|
|
3523
|
-
appSalt:
|
|
3524
|
-
...
|
|
3525
|
-
...
|
|
3526
|
-
...
|
|
3527
|
-
...
|
|
4784
|
+
appSalt: cfg.appSalt,
|
|
4785
|
+
...cfg.paymasterApiKey ? { paymasterApiKey: cfg.paymasterApiKey } : {},
|
|
4786
|
+
...cfg.appId ? { appId: cfg.appId } : {},
|
|
4787
|
+
...cfg.authBackendUrl ? { backendUrl: cfg.authBackendUrl } : {},
|
|
4788
|
+
...cfg.rpcUrl ? { rpcUrl: cfg.rpcUrl } : {}
|
|
3528
4789
|
});
|
|
3529
|
-
|
|
4790
|
+
setWallet(w);
|
|
3530
4791
|
setIdentity(id);
|
|
3531
|
-
const pendingRequestId =
|
|
4792
|
+
const pendingRequestId = w.chain === "starknet" ? w.pendingRequestId : null;
|
|
4793
|
+
let hasPasskey = false;
|
|
4794
|
+
if (w.status === "needs-device-approval") {
|
|
4795
|
+
try {
|
|
4796
|
+
hasPasskey = await w.hasPasskey();
|
|
4797
|
+
} catch {
|
|
4798
|
+
}
|
|
4799
|
+
}
|
|
3532
4800
|
setWalletStatus({
|
|
3533
4801
|
isDeploying: false,
|
|
3534
|
-
isReady:
|
|
3535
|
-
needsDeviceApproval:
|
|
3536
|
-
awaitingApproval:
|
|
3537
|
-
pendingRequestId
|
|
4802
|
+
isReady: w.status === "ready",
|
|
4803
|
+
needsDeviceApproval: w.status === "needs-device-approval",
|
|
4804
|
+
awaitingApproval: w.status === "needs-device-approval" && !!pendingRequestId,
|
|
4805
|
+
pendingRequestId,
|
|
4806
|
+
hasPasskey,
|
|
4807
|
+
isNewAccount: w.isNewAccount
|
|
3538
4808
|
});
|
|
3539
|
-
modal?.onSuccess?.(
|
|
3540
|
-
return
|
|
4809
|
+
modal?.onSuccess?.(w.address);
|
|
4810
|
+
return w;
|
|
3541
4811
|
}, [modal]);
|
|
3542
4812
|
react.useEffect(() => {
|
|
3543
4813
|
if (typeof window === "undefined") return;
|
|
@@ -3588,76 +4858,145 @@ function CavosProvider({ config, modal, children }) {
|
|
|
3588
4858
|
const id = await auth.verifyOtp(email, code);
|
|
3589
4859
|
await connect(id);
|
|
3590
4860
|
}, [auth, connect]);
|
|
3591
|
-
const execute = react.useCallback(async (calls) => {
|
|
3592
|
-
if (!
|
|
3593
|
-
if (
|
|
4861
|
+
const execute = react.useCallback(async (calls, opts) => {
|
|
4862
|
+
if (!wallet) throw new Error("Not logged in");
|
|
4863
|
+
if (wallet.chain !== "starknet") {
|
|
3594
4864
|
throw new Error(
|
|
3595
|
-
"kit: useCavos().execute(calls) is Starknet-only. On Solana use the `wallet` handle: wallet.execute(amount, dest)."
|
|
4865
|
+
"kit: useCavos().execute(calls) is Starknet-only. On Solana/Stellar use the `wallet` handle: wallet.execute(amount, dest)."
|
|
3596
4866
|
);
|
|
3597
4867
|
}
|
|
3598
|
-
return
|
|
3599
|
-
}, [
|
|
4868
|
+
return wallet.execute(calls, opts);
|
|
4869
|
+
}, [wallet]);
|
|
3600
4870
|
const addSigner = react.useCallback(
|
|
3601
4871
|
async (pubkey) => {
|
|
3602
|
-
if (!
|
|
3603
|
-
if (
|
|
3604
|
-
throw new Error("kit: addSigner via useCavos() is Starknet-only; use the `wallet` handle on
|
|
4872
|
+
if (!wallet) throw new Error("Not logged in");
|
|
4873
|
+
if (wallet.chain !== "starknet") {
|
|
4874
|
+
throw new Error("kit: addSigner via useCavos() is Starknet-only; use the `wallet` handle on other chains.");
|
|
3605
4875
|
}
|
|
3606
|
-
return
|
|
4876
|
+
return wallet.addSigner(pubkey);
|
|
3607
4877
|
},
|
|
3608
|
-
[
|
|
4878
|
+
[wallet]
|
|
3609
4879
|
);
|
|
3610
4880
|
const enrollPasskey = react.useCallback(
|
|
3611
4881
|
async (passkey, params) => {
|
|
3612
|
-
if (!
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
const approveThisDeviceWithPasskey = react.useCallback(
|
|
3618
|
-
async (passkey, submit) => {
|
|
3619
|
-
if (!cavos) throw new Error("Not logged in");
|
|
3620
|
-
if (cavos.chain === "starknet") {
|
|
3621
|
-
return cavos.approveThisDeviceWithPasskey({ passkey, ...submit ? { submit } : {} });
|
|
4882
|
+
if (!wallet) throw new Error("Not logged in");
|
|
4883
|
+
if (wallet.chain === "stellar") {
|
|
4884
|
+
throw new Error(
|
|
4885
|
+
"kit: on Stellar, use enrollPasskeyDefault() \u2014 the passkey factor is a WebAuthn PRF secret, not a signer object."
|
|
4886
|
+
);
|
|
3622
4887
|
}
|
|
3623
|
-
|
|
3624
|
-
return { transactionHash };
|
|
4888
|
+
return wallet.enrollPasskey(passkey, params);
|
|
3625
4889
|
},
|
|
3626
|
-
[
|
|
4890
|
+
[wallet]
|
|
3627
4891
|
);
|
|
4892
|
+
const rpName = branding.appName ?? modal?.appName ?? "Cavos";
|
|
4893
|
+
const enrollPasskeyDefault = react.useCallback(async () => {
|
|
4894
|
+
if (!wallet || !identity) throw new Error("Not logged in");
|
|
4895
|
+
if (wallet.status !== "ready") throw new Error("kit: no ready device to enroll a passkey on");
|
|
4896
|
+
if (wallet.chain === "stellar") {
|
|
4897
|
+
const prf = new PasskeyPrf({ rpName });
|
|
4898
|
+
const { secret } = await prf.enroll({
|
|
4899
|
+
userId: identity.userId,
|
|
4900
|
+
userName: identity.email ?? identity.userId,
|
|
4901
|
+
...identity.email ? { displayName: identity.email } : {}
|
|
4902
|
+
});
|
|
4903
|
+
await wallet.enrollPasskey(secret ?? await prf.getSecret());
|
|
4904
|
+
return;
|
|
4905
|
+
}
|
|
4906
|
+
const passkey = new PasskeySigner({ rpName });
|
|
4907
|
+
await wallet.enrollPasskey(passkey, {
|
|
4908
|
+
userId: identity.userId,
|
|
4909
|
+
userName: identity.email ?? identity.userId,
|
|
4910
|
+
...identity.email ? { displayName: identity.email } : {}
|
|
4911
|
+
});
|
|
4912
|
+
}, [wallet, identity, rpName]);
|
|
4913
|
+
const approveDeviceWithPasskey = react.useCallback(async () => {
|
|
4914
|
+
if (!wallet || !identity) throw new Error("Not logged in");
|
|
4915
|
+
if (wallet.status !== "needs-device-approval") {
|
|
4916
|
+
await connect(identity);
|
|
4917
|
+
return;
|
|
4918
|
+
}
|
|
4919
|
+
if (wallet.chain === "stellar") {
|
|
4920
|
+
const prf = new PasskeyPrf({ rpName });
|
|
4921
|
+
await wallet.approveThisDeviceWithPasskey(await prf.getSecret());
|
|
4922
|
+
} else if (wallet.chain === "starknet") {
|
|
4923
|
+
const passkey = new PasskeySigner({ rpName });
|
|
4924
|
+
await wallet.approveThisDeviceWithPasskey({ passkey });
|
|
4925
|
+
} else {
|
|
4926
|
+
const passkey = new PasskeySigner({ rpName });
|
|
4927
|
+
await wallet.approveThisDeviceWithPasskey(passkey);
|
|
4928
|
+
}
|
|
4929
|
+
setWalletStatus((s) => ({ ...s, isDeploying: true, needsDeviceApproval: false, awaitingApproval: false }));
|
|
4930
|
+
const deadline = Date.now() + 6e4;
|
|
4931
|
+
for (; ; ) {
|
|
4932
|
+
let ready = false;
|
|
4933
|
+
try {
|
|
4934
|
+
ready = await wallet.isReady();
|
|
4935
|
+
} catch {
|
|
4936
|
+
}
|
|
4937
|
+
if (ready) break;
|
|
4938
|
+
if (Date.now() > deadline) {
|
|
4939
|
+
setWalletStatus((s) => ({ ...s, isDeploying: false, needsDeviceApproval: true }));
|
|
4940
|
+
throw new Error(
|
|
4941
|
+
"Your device is being added, but it's taking longer than usual. Please try again in a moment."
|
|
4942
|
+
);
|
|
4943
|
+
}
|
|
4944
|
+
await new Promise((r) => setTimeout(r, 3e3));
|
|
4945
|
+
}
|
|
4946
|
+
await connect(identity, { silent: true });
|
|
4947
|
+
}, [wallet, identity, rpName, connect]);
|
|
3628
4948
|
const setupRecovery = react.useCallback(async () => {
|
|
3629
|
-
if (!
|
|
4949
|
+
if (!wallet) throw new Error("Not logged in");
|
|
3630
4950
|
const code = generateRecoveryCode();
|
|
3631
|
-
await
|
|
4951
|
+
await wallet.setupRecovery(code);
|
|
3632
4952
|
return code;
|
|
3633
|
-
}, [
|
|
4953
|
+
}, [wallet]);
|
|
3634
4954
|
const recover = react.useCallback(async (code) => {
|
|
3635
4955
|
if (!identity) throw new Error("Sign in first so we know which account to recover.");
|
|
4956
|
+
const cfg = configRef.current;
|
|
3636
4957
|
setAuthError(null);
|
|
3637
4958
|
setWalletStatus({ ...INITIAL_STATUS, isDeploying: true });
|
|
3638
4959
|
try {
|
|
3639
|
-
const chain =
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
4960
|
+
const chain = cfg.chain ?? "starknet";
|
|
4961
|
+
let w;
|
|
4962
|
+
if (chain === "solana") {
|
|
4963
|
+
w = await CavosSolana.recover({
|
|
4964
|
+
code,
|
|
4965
|
+
identity,
|
|
4966
|
+
network: cfg.network === "mainnet" ? "solana-mainnet" : "solana-devnet",
|
|
4967
|
+
appSalt: cfg.appSalt,
|
|
4968
|
+
...cfg.appId ? { appId: cfg.appId } : {},
|
|
4969
|
+
...cfg.authBackendUrl ? { backendUrl: cfg.authBackendUrl } : {},
|
|
4970
|
+
...cfg.rpcUrl ? { rpcUrl: cfg.rpcUrl } : {}
|
|
4971
|
+
});
|
|
4972
|
+
} else if (chain === "stellar") {
|
|
4973
|
+
const sw = await Cavos.connect({
|
|
4974
|
+
chain: "stellar",
|
|
4975
|
+
network: cfg.network,
|
|
4976
|
+
identity,
|
|
4977
|
+
appSalt: cfg.appSalt,
|
|
4978
|
+
...cfg.appId ? { appId: cfg.appId } : {},
|
|
4979
|
+
...cfg.authBackendUrl ? { backendUrl: cfg.authBackendUrl } : {}
|
|
4980
|
+
});
|
|
4981
|
+
if (sw.chain === "stellar" && sw.status === "needs-device-approval") {
|
|
4982
|
+
await sw.approveThisDeviceWithRecovery(code);
|
|
4983
|
+
}
|
|
4984
|
+
w = sw;
|
|
4985
|
+
} else {
|
|
4986
|
+
w = await Cavos.recover({
|
|
4987
|
+
code,
|
|
4988
|
+
identity,
|
|
4989
|
+
network: cfg.network,
|
|
4990
|
+
appSalt: cfg.appSalt,
|
|
4991
|
+
paymasterApiKey: cfg.paymasterApiKey ?? "",
|
|
4992
|
+
...cfg.appId ? { appId: cfg.appId } : {},
|
|
4993
|
+
...cfg.authBackendUrl ? { backendUrl: cfg.authBackendUrl } : {},
|
|
4994
|
+
...cfg.rpcUrl ? { rpcUrl: cfg.rpcUrl } : {}
|
|
4995
|
+
});
|
|
4996
|
+
}
|
|
4997
|
+
setWallet(w);
|
|
3659
4998
|
setWalletStatus({ ...INITIAL_STATUS, isReady: true });
|
|
3660
|
-
modal?.onSuccess?.(
|
|
4999
|
+
modal?.onSuccess?.(w.address);
|
|
3661
5000
|
} catch (e) {
|
|
3662
5001
|
const msg = e instanceof Error ? e.message : "Recovery failed. Check your code and try again.";
|
|
3663
5002
|
setAuthError(msg);
|
|
@@ -3667,9 +5006,10 @@ function CavosProvider({ config, modal, children }) {
|
|
|
3667
5006
|
}, [identity, modal]);
|
|
3668
5007
|
react.useEffect(() => {
|
|
3669
5008
|
if (!walletStatus.awaitingApproval || !walletStatus.pendingRequestId || !identity) return;
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
const
|
|
5009
|
+
const cfg = configRef.current;
|
|
5010
|
+
if (!cfg.appId) return;
|
|
5011
|
+
const backendUrl = cfg.authBackendUrl ?? "https://cavos.xyz";
|
|
5012
|
+
const recovery = new HttpRecoveryClient({ baseUrl: backendUrl, appId: cfg.appId });
|
|
3673
5013
|
const requestId = walletStatus.pendingRequestId;
|
|
3674
5014
|
let cancelled = false;
|
|
3675
5015
|
const tick = async () => {
|
|
@@ -3693,7 +5033,7 @@ function CavosProvider({ config, modal, children }) {
|
|
|
3693
5033
|
};
|
|
3694
5034
|
}, [walletStatus.awaitingApproval, walletStatus.pendingRequestId, identity, connect]);
|
|
3695
5035
|
const logout = react.useCallback(() => {
|
|
3696
|
-
|
|
5036
|
+
setWallet(null);
|
|
3697
5037
|
setIdentity(null);
|
|
3698
5038
|
setWalletStatus(INITIAL_STATUS);
|
|
3699
5039
|
setAuthError(null);
|
|
@@ -3701,11 +5041,11 @@ function CavosProvider({ config, modal, children }) {
|
|
|
3701
5041
|
const value = {
|
|
3702
5042
|
openModal,
|
|
3703
5043
|
closeModal,
|
|
3704
|
-
isAuthenticated: !!
|
|
5044
|
+
isAuthenticated: !!wallet,
|
|
3705
5045
|
user: identity ? { userId: identity.userId, email: identity.email, provider: identity.provider } : null,
|
|
3706
5046
|
chain: config.chain ?? "starknet",
|
|
3707
|
-
wallet
|
|
3708
|
-
address:
|
|
5047
|
+
wallet,
|
|
5048
|
+
address: wallet?.address ?? null,
|
|
3709
5049
|
walletStatus,
|
|
3710
5050
|
isLoading,
|
|
3711
5051
|
authError,
|
|
@@ -3718,7 +5058,9 @@ function CavosProvider({ config, modal, children }) {
|
|
|
3718
5058
|
execute,
|
|
3719
5059
|
addSigner,
|
|
3720
5060
|
enrollPasskey,
|
|
3721
|
-
|
|
5061
|
+
passkeySupported,
|
|
5062
|
+
enrollPasskeyDefault,
|
|
5063
|
+
approveDeviceWithPasskey,
|
|
3722
5064
|
resendDeviceApproval,
|
|
3723
5065
|
setupRecovery,
|
|
3724
5066
|
recover,
|
|
@@ -3746,6 +5088,11 @@ function useCavos() {
|
|
|
3746
5088
|
if (!ctx) throw new Error("useCavos must be used within a CavosProvider");
|
|
3747
5089
|
return ctx;
|
|
3748
5090
|
}
|
|
5091
|
+
/*! Bundled license information:
|
|
5092
|
+
|
|
5093
|
+
@noble/ciphers/esm/utils.js:
|
|
5094
|
+
(*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) *)
|
|
5095
|
+
*/
|
|
3749
5096
|
|
|
3750
5097
|
exports.CavosAuthModal = CavosAuthModal;
|
|
3751
5098
|
exports.CavosProvider = CavosProvider;
|