@cavos/kit 0.0.2 → 0.0.3

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/index.js CHANGED
@@ -7,6 +7,7 @@ var web3_js = require('@solana/web3.js');
7
7
  var hkdf = require('@noble/hashes/hkdf');
8
8
  var pbkdf2 = require('@noble/hashes/pbkdf2');
9
9
  var utils = require('@noble/hashes/utils');
10
+ var stellarSdk = require('@stellar/stellar-sdk');
10
11
 
11
12
  // src/Cavos.ts
12
13
 
@@ -32,6 +33,18 @@ function hexToBytes(hex) {
32
33
  for (let i = 0; i < out.length; i++) out[i] = parseInt(padded.slice(i * 2, i * 2 + 2), 16);
33
34
  return out;
34
35
  }
36
+ function bytesToByteArrayCalldata(bytes) {
37
+ const CHUNK = 31;
38
+ const fullCount = Math.floor(bytes.length / CHUNK);
39
+ const out = [String(fullCount)];
40
+ for (let i = 0; i < fullCount; i++) {
41
+ out.push("0x" + bytesToBigInt(bytes.subarray(i * CHUNK, i * CHUNK + CHUNK)).toString(16));
42
+ }
43
+ const rem = bytes.subarray(fullCount * CHUNK);
44
+ out.push("0x" + (rem.length ? bytesToBigInt(rem).toString(16) : "0"));
45
+ out.push(String(rem.length));
46
+ return out;
47
+ }
35
48
  function bigIntTo32Bytes(value) {
36
49
  const out = new Uint8Array(32);
37
50
  let v = value;
@@ -169,7 +182,7 @@ var CAVOS_PAYMASTER_URL = {
169
182
  mainnet: "https://paymaster.cavos.xyz"
170
183
  };
171
184
  var DEVICE_ACCOUNT_CLASS_HASH = {
172
- sepolia: "0x1840aded59e8a0d2b440a134cb9079a7fc11b06c77f58ed189ab436a034ca6a",
185
+ sepolia: "0x25cbc5423e8ee895febb0ef2c3945b408da44d0039d915fbdd681fe6b6ba66b",
173
186
  mainnet: "0x1840aded59e8a0d2b440a134cb9079a7fc11b06c77f58ed189ab436a034ca6a"
174
187
  };
175
188
 
@@ -233,6 +246,73 @@ var StarknetAdapter = class {
233
246
  const sig = await this.opts.signer.sign(bigIntTo32Bytes(txHash));
234
247
  return signatureToFelts(sig).map((f) => starknet.num.toHex(f));
235
248
  }
249
+ // --- passkey approvers ---
250
+ buildAddApprover(accountAddress, passkey) {
251
+ return { contractAddress: accountAddress, entrypoint: "add_approver", calldata: pubkeyCalldata(passkey) };
252
+ }
253
+ buildRemoveApprover(accountAddress, passkey) {
254
+ return { contractAddress: accountAddress, entrypoint: "remove_approver", calldata: pubkeyCalldata(passkey) };
255
+ }
256
+ async isApprover(accountAddress, passkey) {
257
+ if (!this.opts.provider) throw new Error("kit/starknet: provider required for reads");
258
+ const res = await this.opts.provider.callContract({
259
+ contractAddress: accountAddress,
260
+ entrypoint: "is_approver",
261
+ calldata: pubkeyCalldata(passkey)
262
+ });
263
+ return BigInt(res[0] ?? 0) !== 0n;
264
+ }
265
+ async getPasskeyNonce(accountAddress) {
266
+ if (!this.opts.provider) throw new Error("kit/starknet: provider required for reads");
267
+ const res = await this.opts.provider.callContract({
268
+ contractAddress: accountAddress,
269
+ entrypoint: "get_passkey_nonce",
270
+ calldata: []
271
+ });
272
+ return BigInt(res[0] ?? 0);
273
+ }
274
+ /** This chain's leaf for approving `add_signer(newSigner)` at `nonce`:
275
+ * `sha256(new_x || new_y || nonce)` (coords 32B BE, nonce 16B BE). The batch
276
+ * challenge the passkey signs is `sha256(concat(leaves))` across chains. */
277
+ passkeyLeaf(newSigner, nonce) {
278
+ const msg = new Uint8Array(32 + 32 + 16);
279
+ msg.set(bigIntTo32Bytes(newSigner.x), 0);
280
+ msg.set(bigIntTo32Bytes(newSigner.y), 32);
281
+ msg.set(bigIntTo32Bytes(nonce).subarray(16), 64);
282
+ return sha256.sha256(msg);
283
+ }
284
+ /** Passkey-authorized `add_signer` call. `leaves`/`leafIndex` place this chain's
285
+ * leaf in the multi-chain batch (single chain → `[leaf]`, index 0). `yParity`
286
+ * matches the raw `(r, s)` — the contract normalizes high-S internally. */
287
+ buildAddSignerViaPasskey(accountAddress, newSigner, nonce, leaves, leafIndex, assertion, yParity) {
288
+ const [rl, rh] = u256ToFelts(assertion.r);
289
+ const [sl, sh] = u256ToFelts(assertion.s);
290
+ const leavesCalldata = [String(leaves.length)];
291
+ for (const leaf of leaves) {
292
+ const [lo, hi] = u256ToFelts(bytesToBigInt(leaf));
293
+ leavesCalldata.push(starknet.num.toHex(lo), starknet.num.toHex(hi));
294
+ }
295
+ return {
296
+ contractAddress: accountAddress,
297
+ entrypoint: "add_signer_via_passkey",
298
+ calldata: [
299
+ ...pubkeyCalldata(newSigner),
300
+ // new_x, new_y (u256 pairs)
301
+ starknet.num.toHex(nonce),
302
+ ...leavesCalldata,
303
+ // Array<u256> leaves
304
+ String(leafIndex),
305
+ ...bytesToByteArrayCalldata(assertion.authenticatorData),
306
+ ...bytesToByteArrayCalldata(assertion.clientDataJSON),
307
+ String(assertion.challengeOffset),
308
+ starknet.num.toHex(rl),
309
+ starknet.num.toHex(rh),
310
+ starknet.num.toHex(sl),
311
+ starknet.num.toHex(sh),
312
+ yParity ? "0x1" : "0x0"
313
+ ]
314
+ };
315
+ }
236
316
  };
237
317
  function pubkeyCalldata(pk) {
238
318
  const [xl, xh] = u256ToFelts(pk.x);
@@ -326,6 +406,9 @@ function deriveAddressSeed({ userId, appSalt }) {
326
406
  function deriveAddressSeedSolana({ userId, appSalt }) {
327
407
  return sha256.sha256(new TextEncoder().encode(`cavos:solana:v1:${userId}:${appSalt}`));
328
408
  }
409
+ function deriveAddressSeedStellar({ userId, appSalt }) {
410
+ return sha256.sha256(new TextEncoder().encode(`cavos:stellar:v1:${userId}:${appSalt}`));
411
+ }
329
412
  function feltFromString(s) {
330
413
  const bytes = new TextEncoder().encode(s);
331
414
  const chunks = [];
@@ -347,6 +430,8 @@ var DOMAIN_ADD = "cavos:add_signer:v1";
347
430
  var DOMAIN_REMOVE = "cavos:remove_signer:v1";
348
431
  var DOMAIN_TRANSFER = "cavos:transfer:v1";
349
432
  var DOMAIN_EXECUTE = "cavos:execute:v1";
433
+ var DOMAIN_ADD_APPROVER = "cavos:add_approver:v1";
434
+ var DOMAIN_REMOVE_APPROVER = "cavos:remove_approver:v1";
350
435
  var SECP256R1_N = 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551n;
351
436
  var SOLANA_NETWORKS = {
352
437
  "solana-devnet": "https://api.devnet.solana.com",
@@ -439,6 +524,99 @@ var SolanaAdapter = class {
439
524
  });
440
525
  return [precompileIx, ix];
441
526
  }
527
+ /** `[precompile, add_approver]` bundle enrolling a passkey approver (device-signed). */
528
+ async buildAddApprover(account, passkey) {
529
+ const accountPk = new web3_js.PublicKey(account);
530
+ const compressed = compressedPubkey(passkey);
531
+ const nonce = await this.fetchNonce(accountPk);
532
+ const message = concatBytes(
533
+ Buffer.from(DOMAIN_ADD_APPROVER),
534
+ accountPk.toBuffer(),
535
+ compressed,
536
+ u64le(nonce)
537
+ );
538
+ const { precompileIx } = await this.signToPrecompile(message);
539
+ const ix = new web3_js.TransactionInstruction({
540
+ programId: this.programId,
541
+ keys: this.guardedKeys(accountPk),
542
+ data: Buffer.concat([anchorDiscriminator("add_approver"), Buffer.from(compressed)])
543
+ });
544
+ return [precompileIx, ix];
545
+ }
546
+ /** `[precompile, remove_approver]` bundle (device-signed). */
547
+ async buildRemoveApprover(account, passkey) {
548
+ const accountPk = new web3_js.PublicKey(account);
549
+ const compressed = compressedPubkey(passkey);
550
+ const nonce = await this.fetchNonce(accountPk);
551
+ const message = concatBytes(
552
+ Buffer.from(DOMAIN_REMOVE_APPROVER),
553
+ accountPk.toBuffer(),
554
+ compressed,
555
+ u64le(nonce)
556
+ );
557
+ const { precompileIx } = await this.signToPrecompile(message);
558
+ const ix = new web3_js.TransactionInstruction({
559
+ programId: this.programId,
560
+ keys: this.guardedKeys(accountPk),
561
+ data: Buffer.concat([anchorDiscriminator("remove_approver"), Buffer.from(compressed)])
562
+ });
563
+ return [precompileIx, ix];
564
+ }
565
+ /** This chain's leaf for approving `add_signer(newSigner)` at `nonce`:
566
+ * `sha256(compressed(new_signer) || passkey_nonce_le8)`. The batch challenge the
567
+ * passkey signs is `sha256(concat(leaves))` across chains. */
568
+ passkeyLeaf(newSigner, nonce) {
569
+ return sha256.sha256(concatBytes(compressedPubkey(newSigner), u64le(nonce)));
570
+ }
571
+ /**
572
+ * `[precompile(passkey), add_signer_via_passkey]` bundle. The precompile ix
573
+ * verifies the PASSKEY's WebAuthn assertion over `authData || sha256(clientDataJSON)`;
574
+ * the program ix binds the challenge to `newSigner` + the passkey nonce and adds
575
+ * the signer. No device signature — a gasless relayer can submit it.
576
+ */
577
+ buildAddSignerViaPasskey(account, newSigner, passkey, leaves, leafIndex, assertion) {
578
+ const accountPk = new web3_js.PublicKey(account);
579
+ const newCompressed = compressedPubkey(newSigner);
580
+ const passkeyCompressed = compressedPubkey(passkey);
581
+ const clientHash = sha256.sha256(assertion.clientDataJSON);
582
+ const message = concatBytes(assertion.authenticatorData, clientHash);
583
+ const signature = encodeLowSSignature(assertion.r, assertion.s);
584
+ const precompileIx = buildSecp256r1Instruction(passkeyCompressed, signature, message);
585
+ const leavesBlob = Buffer.concat([u32le(leaves.length), ...leaves.map((l) => Buffer.from(l))]);
586
+ const data = Buffer.concat([
587
+ anchorDiscriminator("add_signer_via_passkey"),
588
+ Buffer.from(newCompressed),
589
+ leavesBlob,
590
+ u32le(leafIndex),
591
+ serializeVecU8(assertion.authenticatorData),
592
+ serializeVecU8(assertion.clientDataJSON),
593
+ u32le(assertion.challengeOffset)
594
+ ]);
595
+ const ix = new web3_js.TransactionInstruction({
596
+ programId: this.programId,
597
+ keys: this.guardedKeys(accountPk),
598
+ data
599
+ });
600
+ return [precompileIx, ix];
601
+ }
602
+ /** Read whether `passkey` is a registered approver. */
603
+ async isApprover(account, passkey) {
604
+ const approvers = await this.fetchApprovers(new web3_js.PublicKey(account));
605
+ const target = Buffer.from(compressedPubkey(passkey)).toString("hex");
606
+ return approvers.some((a) => Buffer.from(a).toString("hex") === target);
607
+ }
608
+ /** Read the current passkey-approval nonce. */
609
+ async passkeyNonce(account) {
610
+ const info = await this.requireConnection().getAccountInfo(new web3_js.PublicKey(account));
611
+ if (!info) return 0n;
612
+ const d = info.data;
613
+ const signersLenOff = 8 + 32 + 1 + 8 + COMPRESSED_PUBKEY_SIZE;
614
+ const signerCount = d.readUInt32LE(signersLenOff);
615
+ const approversLenOff = signersLenOff + 4 + signerCount * COMPRESSED_PUBKEY_SIZE;
616
+ const approverCount = d.readUInt32LE(approversLenOff);
617
+ const passkeyNonceOff = approversLenOff + 4 + approverCount * COMPRESSED_PUBKEY_SIZE;
618
+ return readU64le(d, passkeyNonceOff);
619
+ }
442
620
  /** `[precompile, execute_transfer]` bundle moving lamports out of the account. */
443
621
  async buildExecuteTransfer(account, destination, amount) {
444
622
  const accountPk = new web3_js.PublicKey(account);
@@ -560,6 +738,22 @@ var SolanaAdapter = class {
560
738
  }
561
739
  return out;
562
740
  }
741
+ async fetchApprovers(account) {
742
+ const info = await this.requireConnection().getAccountInfo(account);
743
+ if (!info) return [];
744
+ const d = info.data;
745
+ const signersLenOff = 8 + 32 + 1 + 8 + COMPRESSED_PUBKEY_SIZE;
746
+ const signerCount = d.readUInt32LE(signersLenOff);
747
+ const approversLenOff = signersLenOff + 4 + signerCount * COMPRESSED_PUBKEY_SIZE;
748
+ const count = d.readUInt32LE(approversLenOff);
749
+ const out = [];
750
+ let off = approversLenOff + 4;
751
+ for (let i = 0; i < count; i++) {
752
+ out.push(Uint8Array.from(d.subarray(off, off + COMPRESSED_PUBKEY_SIZE)));
753
+ off += COMPRESSED_PUBKEY_SIZE;
754
+ }
755
+ return out;
756
+ }
563
757
  requireConnection() {
564
758
  if (!this.opts.connection) throw new Error("kit/solana: connection required for reads");
565
759
  return this.opts.connection;
@@ -572,10 +766,10 @@ function compressedPubkey(pk) {
572
766
  return out;
573
767
  }
574
768
  function encodeLowSSignature(r, s) {
575
- const lowS = s > SECP256R1_N / 2n ? SECP256R1_N - s : s;
769
+ const lowS2 = s > SECP256R1_N / 2n ? SECP256R1_N - s : s;
576
770
  const out = new Uint8Array(SIGNATURE_SIZE);
577
771
  out.set(bigIntTo32Bytes(r), 0);
578
- out.set(bigIntTo32Bytes(lowS), 32);
772
+ out.set(bigIntTo32Bytes(lowS2), 32);
579
773
  return out;
580
774
  }
581
775
  function buildSecp256r1Instruction(compressed, signature, message) {
@@ -614,13 +808,18 @@ function buildSecp256r1Instruction(compressed, signature, message) {
614
808
  function anchorDiscriminator(name) {
615
809
  return Buffer.from(sha256.sha256(`global:${name}`).slice(0, 8));
616
810
  }
811
+ function u32le(n) {
812
+ const b = Buffer.alloc(4);
813
+ b.writeUInt32LE(n);
814
+ return b;
815
+ }
617
816
  function u64le(n) {
618
817
  const b = Buffer.alloc(8);
619
818
  new DataView(b.buffer, b.byteOffset, 8).setBigUint64(0, BigInt(n), true);
620
819
  return b;
621
820
  }
622
- function readU64le(buf, offset) {
623
- return new DataView(buf.buffer, buf.byteOffset, buf.length).getBigUint64(
821
+ function readU64le(buf2, offset) {
822
+ return new DataView(buf2.buffer, buf2.byteOffset, buf2.length).getBigUint64(
624
823
  offset,
625
824
  true
626
825
  );
@@ -1008,6 +1207,77 @@ var WORDLIST = [
1008
1207
  "beach",
1009
1208
  "dusk"
1010
1209
  ];
1210
+ function base64urlEncode(bytes) {
1211
+ let bin = "";
1212
+ for (const b of bytes) bin += String.fromCharCode(b);
1213
+ const b64 = typeof btoa !== "undefined" ? btoa(bin) : Buffer.from(bytes).toString("base64");
1214
+ return b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
1215
+ }
1216
+ function derToRs(der) {
1217
+ let i = 0;
1218
+ if (der[i++] !== 48) throw new Error("kit/webauthn: bad DER (no SEQUENCE)");
1219
+ if (der[i] & 128) i += 1 + (der[i] & 127);
1220
+ else i += 1;
1221
+ if (der[i++] !== 2) throw new Error("kit/webauthn: bad DER (no r INTEGER)");
1222
+ const rlen = der[i++];
1223
+ const r = bytesToBigInt(der.subarray(i, i + rlen));
1224
+ i += rlen;
1225
+ if (der[i++] !== 2) throw new Error("kit/webauthn: bad DER (no s INTEGER)");
1226
+ const slen = der[i++];
1227
+ const s = bytesToBigInt(der.subarray(i, i + slen));
1228
+ return { r, s };
1229
+ }
1230
+ function spkiToPublicKey(spki) {
1231
+ const idx = spki.lastIndexOf(4, spki.length - 65);
1232
+ const start = spki.length - 65;
1233
+ const prefix = spki[start];
1234
+ if (prefix !== 4) {
1235
+ if (idx < 0) throw new Error("kit/webauthn: no uncompressed EC point in SPKI");
1236
+ return { x: bytesToBigInt(spki.subarray(idx + 1, idx + 33)), y: bytesToBigInt(spki.subarray(idx + 33, idx + 65)) };
1237
+ }
1238
+ return {
1239
+ x: bytesToBigInt(spki.subarray(start + 1, start + 33)),
1240
+ y: bytesToBigInt(spki.subarray(start + 33, start + 65))
1241
+ };
1242
+ }
1243
+ function batchChallenge(leaves) {
1244
+ const total = leaves.reduce((n, l) => n + l.length, 0);
1245
+ const cat = new Uint8Array(total);
1246
+ let o = 0;
1247
+ for (const l of leaves) {
1248
+ cat.set(l, o);
1249
+ o += l.length;
1250
+ }
1251
+ return sha256.sha256(cat);
1252
+ }
1253
+ function webauthnDigest(authenticatorData, clientDataJSON) {
1254
+ const clientHash = sha256.sha256(clientDataJSON);
1255
+ const msg = new Uint8Array(authenticatorData.length + clientHash.length);
1256
+ msg.set(authenticatorData, 0);
1257
+ msg.set(clientHash, authenticatorData.length);
1258
+ return sha256.sha256(msg);
1259
+ }
1260
+ function recoverCandidatePublicKeys(r, s, digest) {
1261
+ const out = [];
1262
+ for (const bit of [0, 1]) {
1263
+ try {
1264
+ const point = new p256.p256.Signature(r, s).addRecoveryBit(bit).recoverPublicKey(digest).toAffine();
1265
+ out.push({ publicKey: { x: point.x, y: point.y }, yParity: bit === 1 });
1266
+ } catch {
1267
+ }
1268
+ }
1269
+ return out;
1270
+ }
1271
+ function lowS(s) {
1272
+ const n = p256.p256.CURVE.n;
1273
+ return s > n / 2n ? n - s : s;
1274
+ }
1275
+ function challengeOffsetOf(clientDataJSON, challengeB64) {
1276
+ const text = new TextDecoder().decode(clientDataJSON);
1277
+ const idx = text.indexOf(challengeB64);
1278
+ if (idx < 0) throw new Error("kit/webauthn: challenge not found in clientDataJSON");
1279
+ return idx;
1280
+ }
1011
1281
 
1012
1282
  // src/chains/solana/CavosSolana.ts
1013
1283
  var CavosSolana = class _CavosSolana {
@@ -1088,6 +1358,69 @@ var CavosSolana = class _CavosSolana {
1088
1358
  const ixs = await this.adapter.buildAddSigner(this.address, pubkey);
1089
1359
  return this.send(ixs);
1090
1360
  }
1361
+ /**
1362
+ * Enroll a passkey as an approver (2FA-style step-up). Device-signed + gasless;
1363
+ * requires a ready device. Idempotent. Returns the passkey pubkey + tx hash.
1364
+ */
1365
+ async enrollPasskey(passkey, params) {
1366
+ const enrolled = await passkey.enroll(params);
1367
+ const { transactionHash } = await this.addApprover(enrolled.publicKey);
1368
+ return { publicKey: enrolled.publicKey, transactionHash };
1369
+ }
1370
+ /** Register an already-enrolled passkey pubkey as an approver (gasless).
1371
+ * Idempotent. Lets one passkey be registered across chains without re-prompting. */
1372
+ async addApprover(pubkey) {
1373
+ if (this.status !== "ready") {
1374
+ throw new Error("kit/solana: addApprover requires a ready, authorized device");
1375
+ }
1376
+ if (await this.adapter.isApprover(this.address, pubkey)) return {};
1377
+ const ixs = await this.adapter.buildAddApprover(this.address, pubkey);
1378
+ const transactionHash = await this.send(ixs);
1379
+ return { transactionHash };
1380
+ }
1381
+ /**
1382
+ * From a fresh browser (status `needs-device-approval`), approve adding THIS
1383
+ * device with the user's synced passkey. Gasless via the relayer — the bundle
1384
+ * carries the passkey's WebAuthn assertion, so no device signature is needed.
1385
+ */
1386
+ async approveThisDeviceWithPasskey(passkey) {
1387
+ if (this.status === "ready") {
1388
+ throw new Error("kit/solana: this device is already an authorized signer");
1389
+ }
1390
+ const { leaf, nonce } = await this.passkeyLeafForThisDevice();
1391
+ const leaves = [leaf];
1392
+ const assertion = await passkey.assert(batchChallenge(leaves));
1393
+ const { transactionHash } = await this.submitPasskeyApproval(assertion, leaves, 0, nonce);
1394
+ return transactionHash;
1395
+ }
1396
+ /** This device's leaf + passkey nonce for a (possibly multi-chain) batch. */
1397
+ async passkeyLeafForThisDevice() {
1398
+ const nonce = await this.adapter.passkeyNonce(this.address);
1399
+ return { leaf: this.adapter.passkeyLeaf(this.devicePubkey, nonce), nonce };
1400
+ }
1401
+ /** Submit `add_signer_via_passkey` given a shared assertion + batch position.
1402
+ * Used by `approveThisDeviceWithPasskey` and `approveDeviceEverywhere`. */
1403
+ async submitPasskeyApproval(assertion, leaves, leafIndex, _nonce) {
1404
+ const digest = webauthnDigest(assertion.authenticatorData, assertion.clientDataJSON);
1405
+ const candidates = recoverCandidatePublicKeys(assertion.r, assertion.s, digest);
1406
+ let approver = null;
1407
+ for (const cand of candidates) {
1408
+ if (await this.adapter.isApprover(this.address, cand.publicKey)) {
1409
+ approver = cand.publicKey;
1410
+ break;
1411
+ }
1412
+ }
1413
+ if (!approver) throw new Error("kit/solana: this passkey is not a registered approver");
1414
+ const ixs = this.adapter.buildAddSignerViaPasskey(
1415
+ this.address,
1416
+ this.devicePubkey,
1417
+ approver,
1418
+ leaves,
1419
+ leafIndex,
1420
+ assertion
1421
+ );
1422
+ return { transactionHash: await this.send(ixs) };
1423
+ }
1091
1424
  /** Move `amount` lamports out of the account to `destination` (device-signed). */
1092
1425
  async execute(amount, destination) {
1093
1426
  if (this.status !== "ready") {
@@ -1200,6 +1533,619 @@ var CavosSolana = class _CavosSolana {
1200
1533
  };
1201
1534
  var defaultRegistry = new InMemoryWalletRegistry();
1202
1535
 
1536
+ // src/chains/stellar/constants.ts
1537
+ var FACTORY_CONTRACT_ID = {
1538
+ // Re-deployed 2026-07-01 with the passkey-approval device-account wasm (batched
1539
+ // multi-chain challenge). The factory pins the wasm hash immutably, so a new
1540
+ // wasm needs a new factory → new account addresses; testnet has no prod wallets.
1541
+ "stellar-testnet": "CBCJIODXIEBOXXD66KCUCF7ZDYJARKI4ZIVQOVWPULOBH5XGNCDP6W3I",
1542
+ // Set once the factory is deployed to mainnet (its address differs — network id
1543
+ // is part of contract-address derivation).
1544
+ "stellar-mainnet": ""
1545
+ };
1546
+ var DEVICE_ACCOUNT_WASM_HASH = {
1547
+ "stellar-testnet": "2671b085578e59a385ef5a5664e42f0450322fe3249539f588e1263ed5a31dce",
1548
+ "stellar-mainnet": ""
1549
+ };
1550
+ var STELLAR_NETWORKS = {
1551
+ "stellar-testnet": {
1552
+ rpcUrl: "https://soroban-testnet.stellar.org",
1553
+ passphrase: "Test SDF Network ; September 2015"
1554
+ },
1555
+ "stellar-mainnet": {
1556
+ rpcUrl: "https://soroban-rpc.mainnet.stellar.gateway.fm",
1557
+ passphrase: "Public Global Stellar Network ; September 2015"
1558
+ }
1559
+ };
1560
+ var NATIVE_SAC_ID = {
1561
+ "stellar-testnet": "CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC",
1562
+ "stellar-mainnet": "CAS3J7GYLGXMF6TDJBBYYSE3HQ6BBSMLNUQ34T6TZMYMW2EVH34XOWMA"
1563
+ };
1564
+
1565
+ // src/chains/stellar/StellarAdapter.ts
1566
+ var SECP256R1_N2 = 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551n;
1567
+ var StellarAdapter = class {
1568
+ constructor(opts) {
1569
+ this.chain = "stellar";
1570
+ this.network = opts.network;
1571
+ this.passphrase = STELLAR_NETWORKS[opts.network].passphrase;
1572
+ this.rpcUrl = opts.rpcUrl ?? STELLAR_NETWORKS[opts.network].rpcUrl;
1573
+ this.factoryId = opts.factoryId ?? FACTORY_CONTRACT_ID[opts.network];
1574
+ if (!this.factoryId) {
1575
+ throw new Error(`kit/stellar: no factory contract id configured for ${opts.network}`);
1576
+ }
1577
+ this.signer = opts.signer;
1578
+ }
1579
+ server() {
1580
+ if (!this._server) {
1581
+ this._server = new stellarSdk.rpc.Server(this.rpcUrl, {
1582
+ allowHttp: this.rpcUrl.startsWith("http://")
1583
+ });
1584
+ }
1585
+ return this._server;
1586
+ }
1587
+ networkId() {
1588
+ return stellarSdk.hash(Buffer.from(this.passphrase));
1589
+ }
1590
+ /**
1591
+ * Deterministic account address for `(addressSeed, initialSigner)` — computed
1592
+ * off-chain, byte-identical to the factory's on-chain `account_address`.
1593
+ * `contractId = sha256(HashIdPreimage(networkId, factory, salt))` with
1594
+ * `salt = sha256(addressSeed || sec1(initialSigner))`.
1595
+ */
1596
+ computeAddress(addressSeed, initialSigner) {
1597
+ const salt = this.accountSalt(addressSeed, initialSigner);
1598
+ const preimage = stellarSdk.xdr.HashIdPreimage.envelopeTypeContractId(
1599
+ new stellarSdk.xdr.HashIdPreimageContractId({
1600
+ networkId: this.networkId(),
1601
+ contractIdPreimage: stellarSdk.xdr.ContractIdPreimage.contractIdPreimageFromAddress(
1602
+ new stellarSdk.xdr.ContractIdPreimageFromAddress({
1603
+ address: new stellarSdk.Address(this.factoryId).toScAddress(),
1604
+ salt
1605
+ })
1606
+ )
1607
+ })
1608
+ );
1609
+ return stellarSdk.StrKey.encodeContract(stellarSdk.hash(preimage.toXDR()));
1610
+ }
1611
+ /** `salt = sha256(addressSeed(32) || sec1(initialSigner)(65))` — matches the factory. */
1612
+ accountSalt(addressSeed, initialSigner) {
1613
+ return stellarSdk.hash(Buffer.concat([Buffer.from(addressSeed), Buffer.from(sec1Pubkey(initialSigner))]));
1614
+ }
1615
+ /** Host function: `factory.deploy(address_seed, initial_signer)`. */
1616
+ buildDeploy(addressSeed, initialSigner) {
1617
+ return invokeFunc(this.factoryId, "deploy", [
1618
+ bytesScVal(addressSeed),
1619
+ bytesScVal(sec1Pubkey(initialSigner))
1620
+ ]);
1621
+ }
1622
+ /** Host function: `account.add_signer(new_signer)` (requires device auth). */
1623
+ buildAddSigner(accountAddress, signer) {
1624
+ return invokeFunc(accountAddress, "add_signer", [bytesScVal(sec1Pubkey(signer))]);
1625
+ }
1626
+ /** Host function: `account.remove_signer(signer)` (requires device auth). */
1627
+ buildRemoveSigner(accountAddress, signer) {
1628
+ return invokeFunc(accountAddress, "remove_signer", [bytesScVal(sec1Pubkey(signer))]);
1629
+ }
1630
+ /** Host function: `account.add_approver(passkey)` (requires device auth). */
1631
+ buildAddApprover(accountAddress, passkey) {
1632
+ return invokeFunc(accountAddress, "add_approver", [bytesScVal(sec1Pubkey(passkey))]);
1633
+ }
1634
+ /** Host function: `account.remove_approver(passkey)` (requires device auth). */
1635
+ buildRemoveApprover(accountAddress, passkey) {
1636
+ return invokeFunc(accountAddress, "remove_approver", [bytesScVal(sec1Pubkey(passkey))]);
1637
+ }
1638
+ /** This chain's leaf for approving `add_signer(newSigner)` at `nonce`:
1639
+ * `sha256(sec1(new_signer) || nonce_be8)`. The batch challenge the passkey signs
1640
+ * is `sha256(concat(leaves))` across chains. */
1641
+ passkeyLeaf(newSigner, nonce) {
1642
+ const msg = new Uint8Array(65 + 8);
1643
+ msg.set(sec1Pubkey(newSigner), 0);
1644
+ const n = new Uint8Array(8);
1645
+ let v = nonce;
1646
+ for (let i = 7; i >= 0; i--) {
1647
+ n[i] = Number(v & 0xffn);
1648
+ v >>= 8n;
1649
+ }
1650
+ msg.set(n, 65);
1651
+ return sha256.sha256(msg);
1652
+ }
1653
+ /** Host function: passkey-authorized `add_signer_via_passkey` (no device auth —
1654
+ * authorized by the embedded WebAuthn assertion, so any relayer can submit).
1655
+ * `leaves`/`leafIndex` place this chain's leaf in the multi-chain batch. */
1656
+ buildAddSignerViaPasskey(accountAddress, newSigner, passkey, nonce, leaves, leafIndex, assertion) {
1657
+ const sig = encodeLowSSignature2({ r: assertion.r, s: assertion.s});
1658
+ const leavesScVal = stellarSdk.xdr.ScVal.scvVec(leaves.map((l) => bytesScVal(l)));
1659
+ return invokeFunc(accountAddress, "add_signer_via_passkey", [
1660
+ bytesScVal(sec1Pubkey(newSigner)),
1661
+ bytesScVal(sec1Pubkey(passkey)),
1662
+ stellarSdk.nativeToScVal(nonce, { type: "u64" }),
1663
+ leavesScVal,
1664
+ stellarSdk.nativeToScVal(leafIndex, { type: "u32" }),
1665
+ bytesScVal(assertion.authenticatorData),
1666
+ bytesScVal(assertion.clientDataJSON),
1667
+ stellarSdk.nativeToScVal(assertion.challengeOffset, { type: "u32" }),
1668
+ bytesScVal(sig)
1669
+ ]);
1670
+ }
1671
+ /** Read whether `passkey` is a registered approver (read-only simulation). */
1672
+ async isApprover(accountAddress, passkey, readSource) {
1673
+ if (!await this.isDeployed(accountAddress)) return false;
1674
+ const { Account: Account3, TransactionBuilder: TransactionBuilder2, BASE_FEE: BASE_FEE2 } = await import('@stellar/stellar-sdk');
1675
+ const src = new Account3(readSource, "0");
1676
+ const op = stellarSdk.Operation.invokeHostFunction({
1677
+ func: invokeFunc(accountAddress, "is_approver", [bytesScVal(sec1Pubkey(passkey))]),
1678
+ auth: []
1679
+ });
1680
+ const tx2 = new TransactionBuilder2(src, { fee: BASE_FEE2, networkPassphrase: this.passphrase }).addOperation(op).setTimeout(30).build();
1681
+ const sim = await this.server().simulateTransaction(tx2);
1682
+ if (stellarSdk.rpc.Api.isSimulationError(sim)) {
1683
+ throw new Error(`kit/stellar: is_approver simulation failed: ${sim.error}`);
1684
+ }
1685
+ if (!sim.result?.retval) return false;
1686
+ return stellarSdk.scValToNative(sim.result.retval) === true;
1687
+ }
1688
+ /** Read the current passkey-approval nonce (read-only simulation). */
1689
+ async passkeyNonce(accountAddress, readSource) {
1690
+ if (!await this.isDeployed(accountAddress)) return 0n;
1691
+ const { Account: Account3, TransactionBuilder: TransactionBuilder2, BASE_FEE: BASE_FEE2 } = await import('@stellar/stellar-sdk');
1692
+ const src = new Account3(readSource, "0");
1693
+ const op = stellarSdk.Operation.invokeHostFunction({
1694
+ func: invokeFunc(accountAddress, "passkey_nonce", []),
1695
+ auth: []
1696
+ });
1697
+ const tx2 = new TransactionBuilder2(src, { fee: BASE_FEE2, networkPassphrase: this.passphrase }).addOperation(op).setTimeout(30).build();
1698
+ const sim = await this.server().simulateTransaction(tx2);
1699
+ if (stellarSdk.rpc.Api.isSimulationError(sim)) {
1700
+ throw new Error(`kit/stellar: passkey_nonce simulation failed: ${sim.error}`);
1701
+ }
1702
+ if (!sim.result?.retval) return 0n;
1703
+ return BigInt(stellarSdk.scValToNative(sim.result.retval));
1704
+ }
1705
+ /** Host function: SEP-41 `token.transfer(from=account, to, amount)` (device auth). */
1706
+ buildTransfer(tokenId, accountAddress, destination, amount) {
1707
+ return invokeFunc(tokenId, "transfer", [
1708
+ new stellarSdk.Address(accountAddress).toScVal(),
1709
+ new stellarSdk.Address(destination).toScVal(),
1710
+ stellarSdk.nativeToScVal(amount, { type: "i128" })
1711
+ ]);
1712
+ }
1713
+ /**
1714
+ * Sign a Soroban authorization entry with the silent device key, producing the
1715
+ * `Vec<DeviceSignature>` the account's `__check_auth` verifies. The device
1716
+ * signs `sha256(preimage)` (WebCrypto hashes once more internally), which is
1717
+ * exactly what the contract recomputes. Mutates + returns the entry.
1718
+ */
1719
+ async signAuthEntry(entry, validUntilLedger) {
1720
+ const addrCreds = entry.credentials().address();
1721
+ addrCreds.signatureExpirationLedger(validUntilLedger);
1722
+ const preimage = stellarSdk.xdr.HashIdPreimage.envelopeTypeSorobanAuthorization(
1723
+ new stellarSdk.xdr.HashIdPreimageSorobanAuthorization({
1724
+ networkId: this.networkId(),
1725
+ nonce: addrCreds.nonce(),
1726
+ signatureExpirationLedger: validUntilLedger,
1727
+ invocation: entry.rootInvocation()
1728
+ })
1729
+ );
1730
+ const payload = stellarSdk.hash(preimage.toXDR());
1731
+ const sig = await this.signer.sign(new Uint8Array(payload));
1732
+ const pubkey = await this.signer.getPublicKey();
1733
+ addrCreds.signature(deviceSignatureScVal(pubkey, sig));
1734
+ return entry;
1735
+ }
1736
+ /**
1737
+ * Read a SEP-41 token balance of `account` via a read-only simulation of
1738
+ * `token.balance(account)`. Returns 0 when the account isn't deployed or holds
1739
+ * none. `readSource` is any funded G-account (used only for the simulation).
1740
+ */
1741
+ async readBalance(tokenId, account, readSource) {
1742
+ if (!await this.isDeployed(account)) return 0n;
1743
+ const { Account: Account3, TransactionBuilder: TransactionBuilder2, BASE_FEE: BASE_FEE2 } = await import('@stellar/stellar-sdk');
1744
+ const src = new Account3(readSource, "0");
1745
+ const op = stellarSdk.Operation.invokeHostFunction({
1746
+ func: invokeFunc(tokenId, "balance", [new stellarSdk.Address(account).toScVal()]),
1747
+ auth: []
1748
+ });
1749
+ const tx2 = new TransactionBuilder2(src, { fee: BASE_FEE2, networkPassphrase: this.passphrase }).addOperation(op).setTimeout(30).build();
1750
+ const sim = await this.server().simulateTransaction(tx2);
1751
+ if (stellarSdk.rpc.Api.isSimulationError(sim) || !sim.result?.retval) return 0n;
1752
+ return BigInt(stellarSdk.scValToNative(sim.result.retval));
1753
+ }
1754
+ /** Whether the account contract instance exists on-chain (is deployed). */
1755
+ async isDeployed(accountAddress) {
1756
+ try {
1757
+ const res = await this.server().getContractData(
1758
+ accountAddress,
1759
+ stellarSdk.xdr.ScVal.scvLedgerKeyContractInstance(),
1760
+ stellarSdk.rpc.Durability.Persistent
1761
+ );
1762
+ return !!res;
1763
+ } catch {
1764
+ return false;
1765
+ }
1766
+ }
1767
+ /**
1768
+ * Read whether `signer` is a currently-authorized signer of the account, via a
1769
+ * read-only simulation of `account.is_authorized(signer)`. `readSource` is any
1770
+ * funded G-account (used only for the simulation's source/sequence).
1771
+ */
1772
+ async isAuthorizedSigner(accountAddress, signer, readSource) {
1773
+ if (!await this.isDeployed(accountAddress)) return false;
1774
+ const { Account: Account3, TransactionBuilder: TransactionBuilder2, BASE_FEE: BASE_FEE2 } = await import('@stellar/stellar-sdk');
1775
+ const src = new Account3(readSource, "0");
1776
+ const op = stellarSdk.Operation.invokeHostFunction({
1777
+ func: invokeFunc(accountAddress, "is_authorized", [bytesScVal(sec1Pubkey(signer))]),
1778
+ auth: []
1779
+ });
1780
+ const tx2 = new TransactionBuilder2(src, { fee: BASE_FEE2, networkPassphrase: this.passphrase }).addOperation(op).setTimeout(30).build();
1781
+ const sim = await this.server().simulateTransaction(tx2);
1782
+ if (stellarSdk.rpc.Api.isSimulationError(sim)) {
1783
+ throw new Error(`kit/stellar: is_authorized simulation failed: ${sim.error}`);
1784
+ }
1785
+ if (!sim.result?.retval) return false;
1786
+ return stellarSdk.scValToNative(sim.result.retval) === true;
1787
+ }
1788
+ };
1789
+ function sec1Pubkey(pk) {
1790
+ const out = new Uint8Array(65);
1791
+ out[0] = 4;
1792
+ out.set(bigIntTo32Bytes(pk.x), 1);
1793
+ out.set(bigIntTo32Bytes(pk.y), 33);
1794
+ return out;
1795
+ }
1796
+ function encodeLowSSignature2(sig) {
1797
+ const lowS2 = sig.s > SECP256R1_N2 / 2n ? SECP256R1_N2 - sig.s : sig.s;
1798
+ const out = new Uint8Array(64);
1799
+ out.set(bigIntTo32Bytes(sig.r), 0);
1800
+ out.set(bigIntTo32Bytes(lowS2), 32);
1801
+ return out;
1802
+ }
1803
+ function deviceSignatureScVal(pubkey, sig) {
1804
+ const element = stellarSdk.nativeToScVal(
1805
+ {
1806
+ public_key: Buffer.from(sec1Pubkey(pubkey)),
1807
+ signature: Buffer.from(encodeLowSSignature2(sig))
1808
+ },
1809
+ { type: { public_key: ["symbol", "bytes"], signature: ["symbol", "bytes"] } }
1810
+ );
1811
+ return stellarSdk.xdr.ScVal.scvVec([element]);
1812
+ }
1813
+ function invokeFunc(contractId, method, args) {
1814
+ return stellarSdk.xdr.HostFunction.hostFunctionTypeInvokeContract(
1815
+ new stellarSdk.xdr.InvokeContractArgs({
1816
+ contractAddress: new stellarSdk.Address(contractId).toScAddress(),
1817
+ functionName: method,
1818
+ args
1819
+ })
1820
+ );
1821
+ }
1822
+ function bytesScVal(bytes) {
1823
+ return stellarSdk.xdr.ScVal.scvBytes(Buffer.from(bytes));
1824
+ }
1825
+
1826
+ // src/chains/stellar/StellarRelayer.ts
1827
+ var StellarRelayer = class {
1828
+ constructor(opts) {
1829
+ this.opts = opts;
1830
+ }
1831
+ /** The relayer's source/fee-payer G-account (fetched + cached from the backend). */
1832
+ async getSource() {
1833
+ if (this.source) return this.source;
1834
+ const res = await fetch(`${this.opts.baseUrl}/api/stellar/relay?network=${this.opts.network}`);
1835
+ if (!res.ok) throw new Error(`kit/stellar: relayer source lookup failed (${res.status})`);
1836
+ const { fee_payer } = await res.json();
1837
+ this.source = fee_payer;
1838
+ return this.source;
1839
+ }
1840
+ /**
1841
+ * POST the assembled, device-authorized transaction XDR to the relayer to sign
1842
+ * the envelope + submit. Returns the confirmed transaction hash.
1843
+ */
1844
+ async submit(transactionXdr) {
1845
+ const res = await fetch(`${this.opts.baseUrl}/api/stellar/relay`, {
1846
+ method: "POST",
1847
+ headers: { "Content-Type": "application/json" },
1848
+ body: JSON.stringify({
1849
+ app_id: this.opts.appId,
1850
+ network: this.opts.network,
1851
+ transaction: transactionXdr
1852
+ })
1853
+ });
1854
+ if (!res.ok) {
1855
+ const detail = await res.text().catch(() => "");
1856
+ throw new Error(`kit/stellar: relay failed (${res.status}) ${detail}`);
1857
+ }
1858
+ const { hash: hash6 } = await res.json();
1859
+ return hash6;
1860
+ }
1861
+ };
1862
+
1863
+ // src/chains/stellar/CavosStellar.ts
1864
+ var CavosStellar = class _CavosStellar {
1865
+ constructor(identity, address, status, network, adapter, devicePubkey, relayer, sourceKeypair) {
1866
+ this.identity = identity;
1867
+ this.address = address;
1868
+ this.status = status;
1869
+ this.network = network;
1870
+ this.adapter = adapter;
1871
+ this.devicePubkey = devicePubkey;
1872
+ this.relayer = relayer;
1873
+ this.sourceKeypair = sourceKeypair;
1874
+ /** Discriminant for the `CavosWallet` union — narrows `execute()` per chain. */
1875
+ this.chain = "stellar";
1876
+ }
1877
+ get publicKey() {
1878
+ return this.devicePubkey;
1879
+ }
1880
+ static async connect(opts) {
1881
+ const identity = opts.identity ?? await opts.auth?.authenticate();
1882
+ if (!identity) throw new Error("kit/stellar: connect requires `identity` or `auth`");
1883
+ const signer = opts.createSigner ? await opts.createSigner(`${identity.userId}:${opts.appSalt}`) : await WebCryptoSigner.loadOrCreate({ keyId: `${identity.userId}:${opts.appSalt}` });
1884
+ const devicePubkey = await signer.getPublicKey();
1885
+ const adapter = new StellarAdapter({
1886
+ network: opts.network,
1887
+ rpcUrl: opts.rpcUrl,
1888
+ factoryId: opts.factoryId,
1889
+ signer
1890
+ });
1891
+ const addressSeed = deriveAddressSeedStellar({ userId: identity.userId, appSalt: opts.appSalt });
1892
+ const backendUrl = opts.backendUrl ?? "https://cavos.xyz";
1893
+ const registry = opts.registry ?? (opts.appId ? new HttpWalletRegistry({ baseUrl: backendUrl, appId: opts.appId, network: opts.network }) : defaultRegistry2);
1894
+ const relayer = opts.relayer ?? (opts.appId ? new StellarRelayer({ baseUrl: backendUrl, appId: opts.appId, network: opts.network }) : void 0);
1895
+ const build = (address2, status) => new _CavosStellar(identity, address2, status, opts.network, adapter, devicePubkey, relayer, opts.sourceKeypair);
1896
+ const self = build("", "needs-device-approval");
1897
+ const readSource = await self.resolveSource();
1898
+ const existing = await registry.lookup(identity.userId);
1899
+ if (existing) {
1900
+ const isSigner2 = await adapter.isAuthorizedSigner(existing.address, devicePubkey, readSource);
1901
+ return build(existing.address, isSigner2 ? "ready" : "needs-device-approval");
1902
+ }
1903
+ const address = adapter.computeAddress(addressSeed, devicePubkey);
1904
+ if (!await adapter.isDeployed(address)) {
1905
+ const func = adapter.buildDeploy(addressSeed, devicePubkey);
1906
+ await self.submitHostFunction(func, void 0);
1907
+ }
1908
+ await registry.register({ userId: identity.userId, address, initialSigner: devicePubkey });
1909
+ const isSigner = await adapter.isAuthorizedSigner(address, devicePubkey, readSource);
1910
+ return build(address, isSigner ? "ready" : "needs-device-approval");
1911
+ }
1912
+ /** Authorize an additional device signer (device-signed via `__check_auth`). */
1913
+ async addSigner(pubkey) {
1914
+ const func = this.adapter.buildAddSigner(this.address, pubkey);
1915
+ return this.submitHostFunction(func, this.address);
1916
+ }
1917
+ /**
1918
+ * Enroll a passkey as an approver (2FA-style step-up). Device-signed + gasless;
1919
+ * requires a ready device. Idempotent. Returns the passkey pubkey + tx hash.
1920
+ */
1921
+ async enrollPasskey(passkey, params) {
1922
+ const enrolled = await passkey.enroll(params);
1923
+ const { transactionHash } = await this.addApprover(enrolled.publicKey);
1924
+ return { publicKey: enrolled.publicKey, transactionHash };
1925
+ }
1926
+ /** Register an already-enrolled passkey pubkey as an approver (gasless).
1927
+ * Idempotent. Lets one passkey be registered across chains without re-prompting. */
1928
+ async addApprover(pubkey) {
1929
+ if (this.status !== "ready") {
1930
+ throw new Error("kit/stellar: addApprover requires a ready, authorized device");
1931
+ }
1932
+ const readSource = await this.resolveSource();
1933
+ if (await this.adapter.isApprover(this.address, pubkey, readSource)) return {};
1934
+ const func = this.adapter.buildAddApprover(this.address, pubkey);
1935
+ const transactionHash = await this.submitHostFunction(func, this.address);
1936
+ return { transactionHash };
1937
+ }
1938
+ /**
1939
+ * From a fresh browser (status `needs-device-approval`), approve adding THIS
1940
+ * device using the user's synced passkey. Gasless via the relayer — the call
1941
+ * carries the WebAuthn assertion, so no device signature is needed. Returns the
1942
+ * tx hash. No trip back to an already-authorized device.
1943
+ */
1944
+ async approveThisDeviceWithPasskey(passkey) {
1945
+ if (this.status === "ready") {
1946
+ throw new Error("kit/stellar: this device is already an authorized signer");
1947
+ }
1948
+ const { leaf, nonce } = await this.passkeyLeafForThisDevice();
1949
+ const leaves = [leaf];
1950
+ const assertion = await passkey.assert(batchChallenge(leaves));
1951
+ const { transactionHash } = await this.submitPasskeyApproval(assertion, leaves, 0, nonce);
1952
+ return transactionHash;
1953
+ }
1954
+ /** This device's leaf + passkey nonce for a (possibly multi-chain) batch. */
1955
+ async passkeyLeafForThisDevice() {
1956
+ const readSource = await this.resolveSource();
1957
+ const nonce = await this.adapter.passkeyNonce(this.address, readSource);
1958
+ return { leaf: this.adapter.passkeyLeaf(this.devicePubkey, nonce), nonce };
1959
+ }
1960
+ /** Submit `add_signer_via_passkey` given a shared assertion + batch position.
1961
+ * No device auth entry — authorized purely by the passkey assertion. */
1962
+ async submitPasskeyApproval(assertion, leaves, leafIndex, nonce) {
1963
+ const readSource = await this.resolveSource();
1964
+ const digest = webauthnDigest(assertion.authenticatorData, assertion.clientDataJSON);
1965
+ const candidates = recoverCandidatePublicKeys(assertion.r, assertion.s, digest);
1966
+ let approver = null;
1967
+ for (const cand of candidates) {
1968
+ if (await this.adapter.isApprover(this.address, cand.publicKey, readSource)) {
1969
+ approver = cand.publicKey;
1970
+ break;
1971
+ }
1972
+ }
1973
+ if (!approver) throw new Error("kit/stellar: this passkey is not a registered approver");
1974
+ const func = this.adapter.buildAddSignerViaPasskey(
1975
+ this.address,
1976
+ this.devicePubkey,
1977
+ approver,
1978
+ nonce,
1979
+ leaves,
1980
+ leafIndex,
1981
+ assertion
1982
+ );
1983
+ return { transactionHash: await this.submitHostFunction(func, void 0) };
1984
+ }
1985
+ /** Move `amount` stroops of native XLM to `destination` (device-signed). */
1986
+ async execute(amount, destination) {
1987
+ return this.executeTransfer(NATIVE_SAC_ID[this.network], amount, destination);
1988
+ }
1989
+ /** Read this account's balance of `tokenId` (defaults to native XLM), in stroops. */
1990
+ async balance(tokenId = NATIVE_SAC_ID[this.network]) {
1991
+ const readSource = await this.resolveSource();
1992
+ return this.adapter.readBalance(tokenId, this.address, readSource);
1993
+ }
1994
+ /** Transfer `amount` of any SEP-41 token out of the account (device-signed). */
1995
+ async executeTransfer(tokenId, amount, destination) {
1996
+ if (this.status !== "ready") {
1997
+ throw new Error("kit/stellar: this device is not yet an authorized signer of the wallet");
1998
+ }
1999
+ const func = this.adapter.buildTransfer(tokenId, this.address, destination, amount);
2000
+ return this.submitHostFunction(func, this.address);
2001
+ }
2002
+ /**
2003
+ * Register the backup signer derived from `code` as an authorized signer of
2004
+ * this account (device-signed). Idempotent. The code never leaves the device —
2005
+ * only the derived public key travels on-chain. Mirrors the other chains.
2006
+ */
2007
+ async setupRecovery(code) {
2008
+ if (this.status !== "ready") {
2009
+ throw new Error("kit/stellar: setupRecovery requires a ready, registered device");
2010
+ }
2011
+ const { publicKey: backupPubkey } = deriveBackupKey(code);
2012
+ const readSource = await this.resolveSource();
2013
+ if (await this.adapter.isAuthorizedSigner(this.address, backupPubkey, readSource)) return void 0;
2014
+ return this.addSigner(backupPubkey);
2015
+ }
2016
+ /**
2017
+ * Recover an account after losing every device signer: derive the backup key
2018
+ * from `code`, use it (not the new device) to authorize `add_signer(newDevice)`,
2019
+ * and return a ready handle bound to the new device. The address is unchanged.
2020
+ */
2021
+ static async recover(opts) {
2022
+ const signer = opts.createSigner ? await opts.createSigner(`${opts.identity.userId}:${opts.appSalt}`) : await WebCryptoSigner.loadOrCreate({ keyId: `${opts.identity.userId}:${opts.appSalt}` });
2023
+ const devicePubkey = await signer.getPublicKey();
2024
+ const backup = BackupSigner.fromCode(opts.code);
2025
+ const backupAdapter = new StellarAdapter({
2026
+ network: opts.network,
2027
+ rpcUrl: opts.rpcUrl,
2028
+ factoryId: opts.factoryId,
2029
+ signer: backup
2030
+ });
2031
+ const backendUrl = opts.backendUrl ?? "https://cavos.xyz";
2032
+ const registry = opts.registry ?? (opts.appId ? new HttpWalletRegistry({ baseUrl: backendUrl, appId: opts.appId, network: opts.network }) : defaultRegistry2);
2033
+ const existing = await registry.lookup(opts.identity.userId);
2034
+ if (!existing) {
2035
+ throw new Error("kit/stellar: no account found for this identity \u2014 nothing to recover");
2036
+ }
2037
+ const relayer = opts.relayer ?? (opts.appId ? new StellarRelayer({ baseUrl: backendUrl, appId: opts.appId, network: opts.network }) : void 0);
2038
+ const backupHandle = new _CavosStellar(
2039
+ opts.identity,
2040
+ existing.address,
2041
+ "ready",
2042
+ opts.network,
2043
+ backupAdapter,
2044
+ devicePubkey,
2045
+ relayer,
2046
+ opts.sourceKeypair
2047
+ );
2048
+ const readSource = await backupHandle.resolveSource();
2049
+ if (!await backupAdapter.isAuthorizedSigner(existing.address, devicePubkey, readSource)) {
2050
+ await backupHandle.addSigner(devicePubkey);
2051
+ }
2052
+ const adapter = new StellarAdapter({
2053
+ network: opts.network,
2054
+ rpcUrl: opts.rpcUrl,
2055
+ factoryId: opts.factoryId,
2056
+ signer
2057
+ });
2058
+ return new _CavosStellar(
2059
+ opts.identity,
2060
+ existing.address,
2061
+ "ready",
2062
+ opts.network,
2063
+ adapter,
2064
+ devicePubkey,
2065
+ relayer,
2066
+ opts.sourceKeypair
2067
+ );
2068
+ }
2069
+ /** The transaction source/fee-payer G-address (relayer or self-funded). */
2070
+ async resolveSource() {
2071
+ if (this.relayer) return this.relayer.getSource();
2072
+ if (this.sourceKeypair) return this.sourceKeypair.publicKey();
2073
+ throw new Error("kit/stellar: a relayer (appId) or sourceKeypair is required");
2074
+ }
2075
+ /**
2076
+ * Build → simulate → device-sign auth → assemble → submit an invoke-contract
2077
+ * host function. `authAccount` is the account whose `__check_auth` must sign the
2078
+ * operation's Soroban auth entry (undefined for a plain factory deploy).
2079
+ */
2080
+ async submitHostFunction(func, authAccount) {
2081
+ const server = this.adapter.server();
2082
+ const sourceAddr = await this.resolveSource();
2083
+ const simSource = new stellarSdk.Account(sourceAddr, "0");
2084
+ const unsignedOp = stellarSdk.Operation.invokeHostFunction({ func, auth: [] });
2085
+ const simTx = new stellarSdk.TransactionBuilder(simSource, {
2086
+ fee: stellarSdk.BASE_FEE,
2087
+ networkPassphrase: this.adapter.passphrase
2088
+ }).addOperation(unsignedOp).setTimeout(180).build();
2089
+ const sim = await server.simulateTransaction(simTx);
2090
+ if (stellarSdk.rpc.Api.isSimulationError(sim)) {
2091
+ throw new Error(`kit/stellar: simulation failed: ${sim.error}`);
2092
+ }
2093
+ const validUntil = (await server.getLatestLedger()).sequence + 100;
2094
+ const entries = sim.result?.auth ?? [];
2095
+ const signedAuth = [];
2096
+ for (const entry of entries) {
2097
+ if (authAccount && isAddressCredentialFor(entry, authAccount)) {
2098
+ signedAuth.push(await this.adapter.signAuthEntry(entry, validUntil));
2099
+ } else {
2100
+ signedAuth.push(entry);
2101
+ }
2102
+ }
2103
+ const account = await server.getAccount(sourceAddr);
2104
+ const finalOp = stellarSdk.Operation.invokeHostFunction({ func, auth: signedAuth });
2105
+ const built = new stellarSdk.TransactionBuilder(account, {
2106
+ fee: stellarSdk.BASE_FEE,
2107
+ networkPassphrase: this.adapter.passphrase
2108
+ }).addOperation(finalOp).setTimeout(180).build();
2109
+ const authSim = await server.simulateTransaction(built);
2110
+ if (stellarSdk.rpc.Api.isSimulationError(authSim)) {
2111
+ throw new Error(`kit/stellar: auth simulation failed: ${authSim.error}`);
2112
+ }
2113
+ const assembled = stellarSdk.rpc.assembleTransaction(built, authSim).build();
2114
+ if (this.relayer) {
2115
+ return this.relayer.submit(assembled.toXDR());
2116
+ }
2117
+ if (this.sourceKeypair) {
2118
+ assembled.sign(this.sourceKeypair);
2119
+ return this.sendAndConfirm(assembled);
2120
+ }
2121
+ throw new Error("kit/stellar: no relayer or sourceKeypair configured to submit");
2122
+ }
2123
+ /** Submit a signed tx via RPC and poll to confirmation. Returns the hash. */
2124
+ async sendAndConfirm(tx2) {
2125
+ const server = this.adapter.server();
2126
+ const sent = await server.sendTransaction(tx2);
2127
+ if (sent.status === "ERROR") {
2128
+ throw new Error(`kit/stellar: submit rejected: ${JSON.stringify(sent.errorResult)}`);
2129
+ }
2130
+ const hash6 = sent.hash;
2131
+ for (let i = 0; i < 30; i++) {
2132
+ const got = await server.getTransaction(hash6);
2133
+ if (got.status === stellarSdk.rpc.Api.GetTransactionStatus.SUCCESS) return hash6;
2134
+ if (got.status === stellarSdk.rpc.Api.GetTransactionStatus.FAILED) {
2135
+ throw new Error(`kit/stellar: tx ${hash6} failed`);
2136
+ }
2137
+ await new Promise((r) => setTimeout(r, 1e3));
2138
+ }
2139
+ throw new Error(`kit/stellar: tx ${hash6} not confirmed in time`);
2140
+ }
2141
+ };
2142
+ function isAddressCredentialFor(entry, accountAddress) {
2143
+ const creds = entry.credentials();
2144
+ if (creds.switch() !== stellarSdk.xdr.SorobanCredentialsType.sorobanCredentialsAddress()) return false;
2145
+ return stellarSdk.Address.fromScAddress(creds.address().address()).toString() === accountAddress;
2146
+ }
2147
+ var defaultRegistry2 = new InMemoryWalletRegistry();
2148
+
1203
2149
  // src/recovery/HttpRecoveryClient.ts
1204
2150
  function toHex2(n) {
1205
2151
  return "0x" + n.toString(16);
@@ -1281,14 +2227,19 @@ var SOLANA_ENV = {
1281
2227
  mainnet: "solana-mainnet",
1282
2228
  testnet: "solana-devnet"
1283
2229
  };
2230
+ var STELLAR_ENV = {
2231
+ mainnet: "stellar-mainnet",
2232
+ testnet: "stellar-testnet"
2233
+ };
1284
2234
  var Cavos = class _Cavos {
1285
- constructor(identity, address, status, account, adapter, devicePubkey) {
2235
+ constructor(identity, address, status, account, adapter, devicePubkey, paymaster) {
1286
2236
  this.identity = identity;
1287
2237
  this.address = address;
1288
2238
  this.status = status;
1289
2239
  this.account = account;
1290
2240
  this.adapter = adapter;
1291
2241
  this.devicePubkey = devicePubkey;
2242
+ this.paymaster = paymaster;
1292
2243
  /** Discriminant for the `CavosWallet` union — narrows `execute()` per chain. */
1293
2244
  this.chain = "starknet";
1294
2245
  /** Request id of the pending device-addition, when status is needs-device-approval. */
@@ -1321,6 +2272,22 @@ var Cavos = class _Cavos {
1321
2272
  ...opts.feePayer ? { feePayer: opts.feePayer } : {}
1322
2273
  });
1323
2274
  }
2275
+ if (opts.chain === "stellar") {
2276
+ return CavosStellar.connect({
2277
+ network: STELLAR_ENV[opts.network],
2278
+ ...opts.auth ? { auth: opts.auth } : {},
2279
+ ...opts.identity ? { identity: opts.identity } : {},
2280
+ appSalt: opts.appSalt,
2281
+ ...opts.appId ? { appId: opts.appId } : {},
2282
+ ...opts.backendUrl ? { backendUrl: opts.backendUrl } : {},
2283
+ ...opts.registry ? { registry: opts.registry } : {},
2284
+ ...opts.rpcUrl ? { rpcUrl: opts.rpcUrl } : {},
2285
+ ...opts.factoryId ? { factoryId: opts.factoryId } : {},
2286
+ ...opts.createSigner ? { createSigner: opts.createSigner } : {},
2287
+ ...opts.stellarRelayer ? { relayer: opts.stellarRelayer } : {},
2288
+ ...opts.stellarSourceKeypair ? { sourceKeypair: opts.stellarSourceKeypair } : {}
2289
+ });
2290
+ }
1324
2291
  if (!opts.paymasterApiKey) {
1325
2292
  throw new Error("kit: `paymasterApiKey` is required for Starknet connections");
1326
2293
  }
@@ -1348,8 +2315,10 @@ var Cavos = class _Cavos {
1348
2315
  const provider = new starknet.RpcProvider({
1349
2316
  nodeUrl: opts.rpcUrl ?? STARKNET_NETWORKS[opts.network].rpcUrl
1350
2317
  });
2318
+ const paymasterUrl = opts.paymasterUrl ?? CAVOS_PAYMASTER_URL[opts.network];
2319
+ const paymasterConfig = { url: paymasterUrl, apiKey: opts.paymasterApiKey };
1351
2320
  const paymaster = new starknet.PaymasterRpc({
1352
- nodeUrl: opts.paymasterUrl ?? CAVOS_PAYMASTER_URL[opts.network],
2321
+ nodeUrl: paymasterUrl,
1353
2322
  headers: { "x-paymaster-api-key": opts.paymasterApiKey }
1354
2323
  });
1355
2324
  const addressSeed = deriveAddressSeed({ userId: identity.userId, appSalt: opts.appSalt });
@@ -1364,7 +2333,7 @@ var Cavos = class _Cavos {
1364
2333
  cairoVersion: "1"
1365
2334
  });
1366
2335
  const backendUrl = opts.backendUrl ?? "https://cavos.xyz";
1367
- const registry = opts.registry ?? (opts.appId ? new HttpWalletRegistry({ baseUrl: backendUrl, appId: opts.appId, network: opts.network }) : defaultRegistry2);
2336
+ const registry = opts.registry ?? (opts.appId ? new HttpWalletRegistry({ baseUrl: backendUrl, appId: opts.appId, network: opts.network }) : defaultRegistry3);
1368
2337
  const recovery = opts.recovery ?? (opts.appId ? new HttpRecoveryClient({ baseUrl: backendUrl, appId: opts.appId }) : null);
1369
2338
  const existing = await registry.lookup(identity.userId);
1370
2339
  if (existing) {
@@ -1376,7 +2345,8 @@ var Cavos = class _Cavos {
1376
2345
  isSigner2 ? "ready" : "needs-device-approval",
1377
2346
  account2,
1378
2347
  adapter,
1379
- devicePubkey
2348
+ devicePubkey,
2349
+ paymasterConfig
1380
2350
  );
1381
2351
  if (!isSigner2 && recovery) {
1382
2352
  const dedup = lastDeviceRequest.get(identity.userId);
@@ -1435,7 +2405,8 @@ var Cavos = class _Cavos {
1435
2405
  isSigner ? "ready" : "needs-device-approval",
1436
2406
  account,
1437
2407
  adapter,
1438
- devicePubkey
2408
+ devicePubkey,
2409
+ paymasterConfig
1439
2410
  );
1440
2411
  }
1441
2412
  /** This device's public key (e.g. to request addition to an existing wallet). */
@@ -1456,6 +2427,92 @@ var Cavos = class _Cavos {
1456
2427
  async addSigner(pubkey) {
1457
2428
  return this.execute([this.adapter.buildAddSigner(this.address, pubkey)]);
1458
2429
  }
2430
+ /**
2431
+ * Enroll a passkey as an APPROVER so the user can later add devices from any
2432
+ * browser (2FA-style step-up). Requires a ready device (the enrollment call is
2433
+ * device-signed and gasless). Idempotent: a no-op if the passkey is already an
2434
+ * approver. Call this whenever the app decides to prompt "turn on device
2435
+ * approvals". Returns the passkey's public key + the enrollment tx hash.
2436
+ */
2437
+ async enrollPasskey(passkey, params) {
2438
+ const enrolled = await passkey.enroll(params);
2439
+ const { transactionHash } = await this.addApprover(enrolled.publicKey);
2440
+ return { publicKey: enrolled.publicKey, transactionHash };
2441
+ }
2442
+ /**
2443
+ * Register an ALREADY-enrolled passkey public key as an approver (gasless,
2444
+ * device-signed). Idempotent. Use this to register ONE passkey across multiple
2445
+ * chains without re-prompting `passkey.enroll()` on each: enroll once, then
2446
+ * call `addApprover(pubkey)` on each chain's wallet.
2447
+ */
2448
+ async addApprover(pubkey) {
2449
+ if (this.status !== "ready") {
2450
+ throw new Error("kit: addApprover requires a ready, authorized device");
2451
+ }
2452
+ if (await this.adapter.isApprover(this.address, pubkey)) return {};
2453
+ const { transactionHash } = await this.execute([
2454
+ this.adapter.buildAddApprover(this.address, pubkey)
2455
+ ]);
2456
+ return { transactionHash };
2457
+ }
2458
+ /**
2459
+ * From a brand-new browser (status `needs-device-approval`), use the user's
2460
+ * synced passkey to authorize adding THIS device — no trip back to an already-
2461
+ * authorized device.
2462
+ *
2463
+ * `add_signer_via_passkey` is a public external authorized by the embedded
2464
+ * WebAuthn assertion (no device signature), so by default we sponsor it through
2465
+ * the Cavos paymaster's `paymaster_executeDirectTransaction` (the forwarder's
2466
+ * `execute_sponsored` runs a generic call — it does NOT require SNIP-9). Pass a
2467
+ * custom `submit` to route it through your own relayer instead. Returns the tx.
2468
+ */
2469
+ async approveThisDeviceWithPasskey(opts) {
2470
+ if (this.status === "ready") {
2471
+ throw new Error("kit: this device is already an authorized signer");
2472
+ }
2473
+ const { leaf, nonce } = await this.passkeyLeafForThisDevice();
2474
+ const leaves = [leaf];
2475
+ const assertion = await opts.passkey.assert(batchChallenge(leaves));
2476
+ return this.submitPasskeyApproval(assertion, leaves, 0, nonce, opts.submit);
2477
+ }
2478
+ /** This device's leaf + the current passkey nonce, for a (possibly multi-chain)
2479
+ * passkey approval batch. See `approveDeviceEverywhere`. */
2480
+ async passkeyLeafForThisDevice() {
2481
+ const nonce = await this.adapter.getPasskeyNonce(this.address);
2482
+ return { leaf: this.adapter.passkeyLeaf(this.devicePubkey, nonce), nonce };
2483
+ }
2484
+ /** Submit `add_signer_via_passkey` given a (shared) assertion + this chain's
2485
+ * position in the batch. The assertion doesn't carry the passkey pubkey, so we
2486
+ * recover both candidates and pick the enrolled approver via the on-chain view
2487
+ * (no backend). Defaults to sponsoring through the paymaster. */
2488
+ async submitPasskeyApproval(assertion, leaves, leafIndex, nonce, submit) {
2489
+ const digest = webauthnDigest(assertion.authenticatorData, assertion.clientDataJSON);
2490
+ const candidates = recoverCandidatePublicKeys(assertion.r, assertion.s, digest);
2491
+ let yParity = null;
2492
+ for (const cand of candidates) {
2493
+ if (await this.adapter.isApprover(this.address, cand.publicKey)) {
2494
+ yParity = cand.yParity;
2495
+ break;
2496
+ }
2497
+ }
2498
+ if (yParity === null) {
2499
+ throw new Error("kit: this passkey is not a registered approver of the wallet");
2500
+ }
2501
+ const call = this.adapter.buildAddSignerViaPasskey(
2502
+ this.address,
2503
+ this.devicePubkey,
2504
+ nonce,
2505
+ leaves,
2506
+ leafIndex,
2507
+ assertion,
2508
+ yParity
2509
+ );
2510
+ if (submit) return submit(call);
2511
+ if (!this.paymaster) {
2512
+ throw new Error("kit: no paymaster configured \u2014 pass a `submit` relayer to approveThisDeviceWithPasskey");
2513
+ }
2514
+ return paymasterExecuteDirect(this.paymaster, this.address, call);
2515
+ }
1459
2516
  /**
1460
2517
  * Register a self-custodial backup signer derived from `code`, so the account
1461
2518
  * can be recovered after the user loses every device. Idempotent: if the
@@ -1497,7 +2554,7 @@ var Cavos = class _Cavos {
1497
2554
  const backup = BackupSigner.fromCode(opts.code);
1498
2555
  const backupAdapter = new StarknetAdapter({ classHash, signer: backup, provider });
1499
2556
  const backendUrl = opts.backendUrl ?? "https://cavos.xyz";
1500
- const registry = opts.registry ?? (opts.appId ? new HttpWalletRegistry({ baseUrl: backendUrl, appId: opts.appId, network }) : defaultRegistry2);
2557
+ const registry = opts.registry ?? (opts.appId ? new HttpWalletRegistry({ baseUrl: backendUrl, appId: opts.appId, network }) : defaultRegistry3);
1501
2558
  const existing = await registry.lookup(opts.identity.userId);
1502
2559
  if (!existing) {
1503
2560
  throw new Error("kit: no account found for this identity \u2014 nothing to recover");
@@ -1532,7 +2589,7 @@ var Cavos = class _Cavos {
1532
2589
  return new _Cavos(opts.identity, existing.address, "ready", account, adapter, devicePubkey);
1533
2590
  }
1534
2591
  };
1535
- var defaultRegistry2 = new InMemoryWalletRegistry();
2592
+ var defaultRegistry3 = new InMemoryWalletRegistry();
1536
2593
  var DEVICE_REQUEST_DEDUP_MS = 5 * 60 * 1e3;
1537
2594
  var lastDeviceRequest = /* @__PURE__ */ new Map();
1538
2595
  async function isDeployed(provider, address) {
@@ -1543,6 +2600,58 @@ async function isDeployed(provider, address) {
1543
2600
  return false;
1544
2601
  }
1545
2602
  }
2603
+ async function approveDeviceEverywhere(wallets, passkey) {
2604
+ const targets = wallets.filter((w) => w.status === "needs-device-approval");
2605
+ if (targets.length === 0) return [];
2606
+ const infos = await Promise.all(targets.map((w) => w.passkeyLeafForThisDevice()));
2607
+ const leaves = infos.map((i) => i.leaf);
2608
+ const assertion = await passkey.assert(batchChallenge(leaves));
2609
+ const out = [];
2610
+ for (let i = 0; i < targets.length; i++) {
2611
+ const { transactionHash } = await targets[i].submitPasskeyApproval(
2612
+ assertion,
2613
+ leaves,
2614
+ i,
2615
+ infos[i].nonce
2616
+ );
2617
+ out.push({ chain: targets[i].chain, transactionHash });
2618
+ }
2619
+ return out;
2620
+ }
2621
+ async function paymasterExecuteDirect(paymaster, userAddress, call) {
2622
+ const body = {
2623
+ jsonrpc: "2.0",
2624
+ id: 1,
2625
+ method: "paymaster_executeDirectTransaction",
2626
+ params: {
2627
+ transaction: {
2628
+ type: "invoke",
2629
+ invoke: {
2630
+ user_address: userAddress,
2631
+ execute_from_outside_call: {
2632
+ to: call.contractAddress,
2633
+ selector: starknet.hash.getSelectorFromName(call.entrypoint),
2634
+ calldata: call.calldata.map((c) => starknet.num.toHex(c))
2635
+ }
2636
+ }
2637
+ },
2638
+ parameters: { version: "0x1", fee_mode: { mode: "sponsored" } }
2639
+ }
2640
+ };
2641
+ const res = await fetch(paymaster.url, {
2642
+ method: "POST",
2643
+ headers: {
2644
+ "Content-Type": "application/json",
2645
+ ...paymaster.apiKey ? { "x-paymaster-api-key": paymaster.apiKey } : {}
2646
+ },
2647
+ body: JSON.stringify(body)
2648
+ });
2649
+ const json = await res.json();
2650
+ if (json.error) {
2651
+ throw new Error(`kit: paymaster passkey approval failed: ${JSON.stringify(json.error)}`);
2652
+ }
2653
+ return { transactionHash: json.result?.transaction_hash ?? json.result?.tracking_id };
2654
+ }
1546
2655
 
1547
2656
  // src/auth/AuthProvider.ts
1548
2657
  var StaticIdentity = class {
@@ -1690,27 +2799,122 @@ function bytesToChunks(bytes) {
1690
2799
  for (const b of bytes.subarray(0, 31)) w = w << 8n | BigInt(b);
1691
2800
  return w;
1692
2801
  }
2802
+ var PasskeySigner = class {
2803
+ constructor(opts = {}) {
2804
+ if (typeof window === "undefined" || !navigator.credentials) {
2805
+ throw new Error("kit/passkey: WebAuthn is only available in a browser");
2806
+ }
2807
+ this.rpId = opts.rpId ?? window.location.hostname;
2808
+ this.rpName = opts.rpName ?? this.rpId;
2809
+ if (isIpAddress(this.rpId)) {
2810
+ throw new Error(
2811
+ `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.)`
2812
+ );
2813
+ }
2814
+ }
2815
+ /** True if this platform advertises a usable passkey (platform authenticator). */
2816
+ static async isSupported() {
2817
+ if (typeof window === "undefined" || !window.PublicKeyCredential) return false;
2818
+ try {
2819
+ return await window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
2820
+ } catch {
2821
+ return false;
2822
+ }
2823
+ }
2824
+ /** Create a new synced passkey and return its P-256 public key. */
2825
+ async enroll(params) {
2826
+ const challenge = crypto.getRandomValues(new Uint8Array(32));
2827
+ const cred = await navigator.credentials.create({
2828
+ publicKey: {
2829
+ challenge: buf(challenge),
2830
+ rp: { id: this.rpId, name: this.rpName },
2831
+ user: {
2832
+ id: buf(userHandle(params.userId)),
2833
+ name: params.userName,
2834
+ displayName: params.displayName ?? params.userName
2835
+ },
2836
+ pubKeyCredParams: [{ type: "public-key", alg: -7 }],
2837
+ // ES256 (P-256)
2838
+ authenticatorSelection: {
2839
+ residentKey: "required",
2840
+ requireResidentKey: true,
2841
+ userVerification: "preferred"
2842
+ },
2843
+ attestation: "none"
2844
+ }
2845
+ });
2846
+ if (!cred) throw new Error("kit/passkey: enrollment cancelled");
2847
+ const response = cred.response;
2848
+ const spki = new Uint8Array(response.getPublicKey());
2849
+ return { publicKey: spkiToPublicKey(spki), credentialId: new Uint8Array(cred.rawId) };
2850
+ }
2851
+ /**
2852
+ * Produce a WebAuthn assertion over `challenge` (a 32-byte value the caller
2853
+ * derives from the signer being added + the on-chain nonce). Uses discoverable
2854
+ * credentials — no `allowCredentials` — so it works on a brand-new browser.
2855
+ */
2856
+ async assert(challenge) {
2857
+ const cred = await navigator.credentials.get({
2858
+ publicKey: {
2859
+ challenge: buf(challenge),
2860
+ rpId: this.rpId,
2861
+ allowCredentials: [],
2862
+ userVerification: "preferred"
2863
+ }
2864
+ });
2865
+ if (!cred) throw new Error("kit/passkey: assertion cancelled");
2866
+ const response = cred.response;
2867
+ const authenticatorData = new Uint8Array(response.authenticatorData);
2868
+ const clientDataJSON = new Uint8Array(response.clientDataJSON);
2869
+ const { r, s } = derToRs(new Uint8Array(response.signature));
2870
+ const challengeOffset = challengeOffsetOf(clientDataJSON, base64urlEncode(challenge));
2871
+ return { authenticatorData, clientDataJSON, r, s, challengeOffset };
2872
+ }
2873
+ };
2874
+ function isIpAddress(host) {
2875
+ if (/^\d{1,3}(\.\d{1,3}){3}$/.test(host)) return true;
2876
+ if (host.includes(":")) return true;
2877
+ return false;
2878
+ }
2879
+ function userHandle(userId) {
2880
+ const bytes = new TextEncoder().encode(userId);
2881
+ return bytes.length <= 64 ? bytes : sha256.sha256(bytes);
2882
+ }
2883
+ function buf(bytes) {
2884
+ return bytes.slice();
2885
+ }
1693
2886
 
1694
2887
  exports.BackupSigner = BackupSigner;
1695
2888
  exports.Cavos = Cavos;
1696
2889
  exports.CavosAuth = CavosAuth;
1697
2890
  exports.CavosSolana = CavosSolana;
2891
+ exports.CavosStellar = CavosStellar;
1698
2892
  exports.DEVICE_ACCOUNT_CLASS_HASH = DEVICE_ACCOUNT_CLASS_HASH;
1699
2893
  exports.DEVICE_ACCOUNT_PROGRAM_ID = DEVICE_ACCOUNT_PROGRAM_ID;
2894
+ exports.DEVICE_ACCOUNT_WASM_HASH = DEVICE_ACCOUNT_WASM_HASH;
2895
+ exports.FACTORY_CONTRACT_ID = FACTORY_CONTRACT_ID;
1700
2896
  exports.HttpRecoveryClient = HttpRecoveryClient;
1701
2897
  exports.HttpWalletRegistry = HttpWalletRegistry;
1702
2898
  exports.InMemoryWalletRegistry = InMemoryWalletRegistry;
2899
+ exports.NATIVE_SAC_ID = NATIVE_SAC_ID;
2900
+ exports.PasskeySigner = PasskeySigner;
1703
2901
  exports.SECP256R1_PROGRAM_ID = SECP256R1_PROGRAM_ID;
1704
2902
  exports.SOLANA_NETWORKS = SOLANA_NETWORKS;
1705
2903
  exports.STARKNET_NETWORKS = STARKNET_NETWORKS;
2904
+ exports.STELLAR_NETWORKS = STELLAR_NETWORKS;
1706
2905
  exports.SolanaAdapter = SolanaAdapter;
1707
2906
  exports.SolanaRelayer = SolanaRelayer;
1708
2907
  exports.StarknetAdapter = StarknetAdapter;
1709
2908
  exports.StarknetDeviceSigner = StarknetDeviceSigner;
1710
2909
  exports.StaticIdentity = StaticIdentity;
2910
+ exports.StellarAdapter = StellarAdapter;
2911
+ exports.StellarRelayer = StellarRelayer;
1711
2912
  exports.UDC_ADDRESS = UDC_ADDRESS;
1712
2913
  exports.WebCryptoSigner = WebCryptoSigner;
1713
2914
  exports.anchorDiscriminator = anchorDiscriminator;
2915
+ exports.approveDeviceEverywhere = approveDeviceEverywhere;
2916
+ exports.base64urlEncode = base64urlEncode;
2917
+ exports.batchChallenge = batchChallenge;
1714
2918
  exports.bigIntTo32Bytes = bigIntTo32Bytes;
1715
2919
  exports.buildSecp256r1Instruction = buildSecp256r1Instruction;
1716
2920
  exports.bytesToBigInt = bytesToBigInt;
@@ -1718,13 +2922,20 @@ exports.bytesToHex = bytesToHex;
1718
2922
  exports.compressedPubkey = compressedPubkey;
1719
2923
  exports.deriveAddressSeed = deriveAddressSeed;
1720
2924
  exports.deriveAddressSeedSolana = deriveAddressSeedSolana;
2925
+ exports.deriveAddressSeedStellar = deriveAddressSeedStellar;
1721
2926
  exports.deriveBackupKey = deriveBackupKey;
2927
+ exports.deviceSignatureScVal = deviceSignatureScVal;
1722
2928
  exports.encodeLowSSignature = encodeLowSSignature;
2929
+ exports.encodeStellarLowSSignature = encodeLowSSignature2;
1723
2930
  exports.generateRecoveryCode = generateRecoveryCode;
1724
2931
  exports.hexToBytes = hexToBytes;
2932
+ exports.lowS = lowS;
2933
+ exports.recoverCandidatePublicKeys = recoverCandidatePublicKeys;
1725
2934
  exports.recoverYParity = recoverYParity;
2935
+ exports.sec1Pubkey = sec1Pubkey;
1726
2936
  exports.serializeInstructions = serializeInstructions;
1727
2937
  exports.signatureToFelts = signatureToFelts;
1728
2938
  exports.u256ToFelts = u256ToFelts;
2939
+ exports.webauthnDigest = webauthnDigest;
1729
2940
  //# sourceMappingURL=index.js.map
1730
2941
  //# sourceMappingURL=index.js.map