@cavos/kit 0.0.4 → 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/index.js CHANGED
@@ -27,8 +27,8 @@ function bytesToHex(bytes) {
27
27
  return s;
28
28
  }
29
29
  function hexToBytes(hex) {
30
- const clean = hex.startsWith("0x") ? hex.slice(2) : hex;
31
- const padded = clean.length % 2 ? "0" + clean : clean;
30
+ const clean2 = hex.startsWith("0x") ? hex.slice(2) : hex;
31
+ const padded = clean2.length % 2 ? "0" + clean2 : clean2;
32
32
  const out = new Uint8Array(padded.length / 2);
33
33
  for (let i = 0; i < out.length; i++) out[i] = parseInt(padded.slice(i * 2, i * 2 + 2), 16);
34
34
  return out;
@@ -182,7 +182,7 @@ var CAVOS_PAYMASTER_URL = {
182
182
  mainnet: "https://paymaster.cavos.xyz"
183
183
  };
184
184
  var DEVICE_ACCOUNT_CLASS_HASH = {
185
- sepolia: "0x25cbc5423e8ee895febb0ef2c3945b408da44d0039d915fbdd681fe6b6ba66b",
185
+ sepolia: "0x765f000b7021577aa0d6c206fb3d8ac73571686b3e76b9dc9b6d59a372e8c2c",
186
186
  mainnet: "0x1840aded59e8a0d2b440a134cb9079a7fc11b06c77f58ed189ab436a034ca6a"
187
187
  };
188
188
 
@@ -192,21 +192,34 @@ var StarknetAdapter = class {
192
192
  this.opts = opts;
193
193
  this.chain = "starknet";
194
194
  }
195
- computeAddress({ addressSeed, initialSigner, salt }) {
195
+ /**
196
+ * Deterministic address = f(addressSeed) ONLY. The device pubkey is NOT
197
+ * part of the derivation — anti-squatting is the integrator's responsibility
198
+ * (keep `appSalt` secret; deploy on first login). This makes the address
199
+ * recomputable by the user from (userId, appSalt) alone, even after losing
200
+ * every device.
201
+ *
202
+ * `initialSigner` in `ComputeAddressParams` is IGNORED on Starknet (kept in
203
+ * the shared type for Solana/Stellar, which still include it).
204
+ */
205
+ computeAddress({ addressSeed, salt }) {
196
206
  return starknet.hash.calculateContractAddressFromHash(
197
207
  starknet.num.toHex(salt ?? addressSeed),
198
208
  this.opts.classHash,
199
- this.constructorCalldata(addressSeed, initialSigner),
209
+ this.constructorCalldata(addressSeed),
200
210
  0
201
211
  // deployerAddress 0 => deterministic counterfactual address
202
212
  );
203
213
  }
204
- /** Single UDC deploy; the constructor registers the first device signer, so
205
- * the account is ready the moment it is deployed (fits the paymaster's
206
- * deploy + execute_from_outside bundle). */
214
+ /**
215
+ * UDC deploy call. The constructor takes ONLY the seed no device pubkey
216
+ * so the account is born with no signers. The caller MUST follow up with
217
+ * `buildInitialize` in the same multicall (or a separate tx) to register the
218
+ * first device signer; otherwise the account is unusable.
219
+ */
207
220
  buildDeploy(params) {
208
221
  const salt = params.salt ?? params.addressSeed;
209
- const calldata = this.constructorCalldata(params.addressSeed, params.initialSigner);
222
+ const calldata = this.constructorCalldata(params.addressSeed);
210
223
  return [
211
224
  {
212
225
  contractAddress: UDC_ADDRESS,
@@ -222,9 +235,22 @@ var StarknetAdapter = class {
222
235
  }
223
236
  ];
224
237
  }
225
- /** Constructor calldata: [address_seed, pub_x_low, pub_x_high, pub_y_low, pub_y_high]. */
226
- constructorCalldata(addressSeed, initialSigner) {
227
- return [starknet.num.toHex(addressSeed), ...pubkeyCalldata(initialSigner)];
238
+ /** Constructor calldata: [address_seed]. Device pubkey is registered post-deploy via initialize. */
239
+ constructorCalldata(addressSeed) {
240
+ return [starknet.num.toHex(addressSeed)];
241
+ }
242
+ /**
243
+ * `initialize` call: registers the first device signer. Callable only while
244
+ * the account has no signers (one-shot). In production this is bundled with
245
+ * the UDC deploy in a single sponsored multicall — see `connectStarknet`.
246
+ * Anti-squatting is NOT enforced on-chain.
247
+ */
248
+ buildInitialize(accountAddress, devicePubkey) {
249
+ return {
250
+ contractAddress: accountAddress,
251
+ entrypoint: "initialize",
252
+ calldata: pubkeyCalldata(devicePubkey)
253
+ };
228
254
  }
229
255
  buildAddSigner(accountAddress, signer) {
230
256
  return { contractAddress: accountAddress, entrypoint: "add_signer", calldata: pubkeyCalldata(signer) };
@@ -460,26 +486,30 @@ var SolanaAdapter = class {
460
486
  this.chain = "solana";
461
487
  this.programId = new web3_js.PublicKey(opts.programId ?? DEVICE_ACCOUNT_PROGRAM_ID);
462
488
  }
463
- /** Deterministic account address: PDA of [seed, address_seed, initial_signer_x]. */
464
- computeAddress(addressSeed, initialSigner) {
465
- return this.pda(addressSeed, compressedPubkey(initialSigner)).toBase58();
489
+ /**
490
+ * Deterministic account address: PDA of [ACCOUNT_SEED, addressSeed] — the
491
+ * device pubkey is NOT part of the seeds, so the address is recomputable
492
+ * from (userId, appSalt) alone. Anti-squatting is the integrator's
493
+ * responsibility (keep `appSalt` secret; deploy on first login).
494
+ */
495
+ computeAddress(addressSeed) {
496
+ return this.pda(addressSeed).toBase58();
466
497
  }
467
- pda(addressSeed, initialCompressed) {
498
+ pda(addressSeed) {
468
499
  const [pda] = web3_js.PublicKey.findProgramAddressSync(
469
- [
470
- Buffer.from(ACCOUNT_SEED),
471
- Buffer.from(addressSeed),
472
- Buffer.from(initialCompressed.slice(1, 33))
473
- // x-coordinate
474
- ],
500
+ [Buffer.from(ACCOUNT_SEED), Buffer.from(addressSeed)],
475
501
  this.programId
476
502
  );
477
503
  return pda;
478
504
  }
479
- /** `initialize` instruction creating the account with its first device signer. */
505
+ /**
506
+ * `initialize` instruction: creates the account PDA and registers the first
507
+ * device signer. No attestation is required — anti-squatting is NOT enforced
508
+ * on-chain.
509
+ */
480
510
  buildInitialize(addressSeed, payer, initialSigner) {
481
511
  const initialCompressed = compressedPubkey(initialSigner);
482
- const account = this.pda(addressSeed, initialCompressed);
512
+ const account = this.pda(addressSeed);
483
513
  const data = Buffer.concat([
484
514
  anchorDiscriminator("initialize"),
485
515
  Buffer.from(addressSeed),
@@ -487,7 +517,7 @@ var SolanaAdapter = class {
487
517
  Buffer.from(initialCompressed)
488
518
  // [u8;33]
489
519
  ]);
490
- return new web3_js.TransactionInstruction({
520
+ const programIx = new web3_js.TransactionInstruction({
491
521
  programId: this.programId,
492
522
  keys: [
493
523
  { pubkey: account, isSigner: false, isWritable: true },
@@ -496,6 +526,7 @@ var SolanaAdapter = class {
496
526
  ],
497
527
  data
498
528
  });
529
+ return [programIx];
499
530
  }
500
531
  /** `[precompile, add_signer]` bundle, authorized by an existing device signer. */
501
532
  async buildAddSigner(account, newSigner) {
@@ -834,8 +865,8 @@ function u64le(n) {
834
865
  new DataView(b.buffer, b.byteOffset, 8).setBigUint64(0, BigInt(n), true);
835
866
  return b;
836
867
  }
837
- function readU64le(buf2, offset) {
838
- return new DataView(buf2.buffer, buf2.byteOffset, buf2.length).getBigUint64(
868
+ function readU64le(buf3, offset) {
869
+ return new DataView(buf3.buffer, buf3.byteOffset, buf3.length).getBigUint64(
839
870
  offset,
840
871
  true
841
872
  );
@@ -894,11 +925,11 @@ var SolanaRelayer = class {
894
925
  async send(instructions) {
895
926
  const feePayer = await this.getFeePayer();
896
927
  const { blockhash } = await this.opts.connection.getLatestBlockhash("confirmed");
897
- const tx2 = new web3_js.Transaction();
898
- tx2.feePayer = feePayer;
899
- tx2.recentBlockhash = blockhash;
900
- tx2.add(...instructions);
901
- const serialized = tx2.serialize({ requireAllSignatures: false, verifySignatures: false }).toString("base64");
928
+ const tx3 = new web3_js.Transaction();
929
+ tx3.feePayer = feePayer;
930
+ tx3.recentBlockhash = blockhash;
931
+ tx3.add(...instructions);
932
+ const serialized = tx3.serialize({ requireAllSignatures: false, verifySignatures: false }).toString("base64");
902
933
  const res = await fetch(`${this.opts.baseUrl}/api/solana/relay`, {
903
934
  method: "POST",
904
935
  headers: { "Content-Type": "application/json" },
@@ -1344,16 +1375,16 @@ var CavosSolana = class _CavosSolana {
1344
1375
  opts.feePayer
1345
1376
  );
1346
1377
  }
1347
- const address = adapter.computeAddress(addressSeed, devicePubkey);
1378
+ const address = adapter.computeAddress(addressSeed);
1348
1379
  const deployed = await connection.getAccountInfo(new web3_js.PublicKey(address)) !== null;
1349
1380
  if (!deployed) {
1350
1381
  if (relayer) {
1351
1382
  const payer = await relayer.getFeePayer();
1352
- const ix = adapter.buildInitialize(addressSeed, payer.toBase58(), devicePubkey);
1353
- await relayer.send([ix]);
1383
+ const ixs = adapter.buildInitialize(addressSeed, payer.toBase58(), devicePubkey);
1384
+ await relayer.send(ixs);
1354
1385
  } else if (opts.feePayer) {
1355
- const ix = adapter.buildInitialize(addressSeed, opts.feePayer.publicKey.toBase58(), devicePubkey);
1356
- await web3_js.sendAndConfirmTransaction(connection, new web3_js.Transaction().add(ix), [opts.feePayer]);
1386
+ const ixs = adapter.buildInitialize(addressSeed, opts.feePayer.publicKey.toBase58(), devicePubkey);
1387
+ await web3_js.sendAndConfirmTransaction(connection, new web3_js.Transaction().add(...ixs), [opts.feePayer]);
1357
1388
  } else {
1358
1389
  throw new Error("kit/solana: a relayer (appId) or feePayer is required to initialize a new account");
1359
1390
  }
@@ -1452,12 +1483,12 @@ var CavosSolana = class _CavosSolana {
1452
1483
  return { transactionHash: await this.send(ixs) };
1453
1484
  }
1454
1485
  /** Move `amount` lamports out of the account to `destination` (device-signed). */
1455
- async execute(amount, destination) {
1486
+ async execute(amount, destination, opts) {
1456
1487
  if (this.status !== "ready") {
1457
1488
  throw new Error("kit/solana: this device is not yet an authorized signer of the wallet");
1458
1489
  }
1459
1490
  const ixs = await this.adapter.buildExecuteTransfer(this.address, destination, amount);
1460
- return this.send(ixs);
1491
+ return this.send(ixs, opts);
1461
1492
  }
1462
1493
  /**
1463
1494
  * Run arbitrary CPI `instructions` with the account PDA as signer (device-
@@ -1467,14 +1498,16 @@ var CavosSolana = class _CavosSolana {
1467
1498
  *
1468
1499
  * What the relayer will sponsor is constrained by the app's Solana program
1469
1500
  * allowlist (configured in the dashboard) — programs outside the allowlist are
1470
- * rejected before co-signing.
1501
+ * rejected before co-signing. Pass `{ sponsored: false }` to bypass the relayer
1502
+ * and pay the fee from a configured `feePayer` (e.g. for allowlisted programs
1503
+ * the relayer rejects, or to test the device signature end-to-end).
1471
1504
  */
1472
- async executeInstructions(instructions) {
1505
+ async executeInstructions(instructions, opts) {
1473
1506
  if (this.status !== "ready") {
1474
1507
  throw new Error("kit/solana: this device is not yet an authorized signer of the wallet");
1475
1508
  }
1476
1509
  const ixs = await this.adapter.buildExecute(this.address, instructions);
1477
- return this.send(ixs);
1510
+ return this.send(ixs, opts);
1478
1511
  }
1479
1512
  /**
1480
1513
  * Register the backup signer derived from `code` as an authorized signer of this
@@ -1553,323 +1586,984 @@ var CavosSolana = class _CavosSolana {
1553
1586
  opts.feePayer
1554
1587
  );
1555
1588
  }
1556
- async send(ixs) {
1557
- if (this.relayer) return this.relayer.send(ixs);
1589
+ /**
1590
+ * Submit a built instruction bundle. Sponsored by default (relayer pays the
1591
+ * fee); pass `{ sponsored: false }` to self-fund via the configured `feePayer`.
1592
+ * The device signature is embedded inside the secp256r1 precompile instruction,
1593
+ * NOT applied as a Solana tx signature — so switching only changes who pays,
1594
+ * never the signing identity.
1595
+ */
1596
+ async send(ixs, opts) {
1597
+ const sponsored = opts?.sponsored !== false;
1598
+ if (sponsored && this.relayer) return this.relayer.send(ixs);
1558
1599
  if (this.feePayer) {
1559
1600
  return web3_js.sendAndConfirmTransaction(this.connection, new web3_js.Transaction().add(...ixs), [this.feePayer]);
1560
1601
  }
1561
- throw new Error("kit/solana: no relayer or feePayer configured to submit transactions");
1602
+ throw new Error(
1603
+ `kit/solana: cannot ${sponsored ? "sponsor" : "self-fund"} \u2014 no ${sponsored ? "relayer" : "feePayer"} configured`
1604
+ );
1562
1605
  }
1563
1606
  };
1564
1607
  var defaultRegistry = new InMemoryWalletRegistry();
1565
1608
 
1566
1609
  // src/chains/stellar/constants.ts
1567
- var FACTORY_CONTRACT_ID = {
1568
- // Re-deployed 2026-07-01 with the passkey-approval device-account wasm (batched
1569
- // multi-chain challenge). The factory pins the wasm hash immutably, so a new
1570
- // wasm needs a new factory → new account addresses; testnet has no prod wallets.
1571
- "stellar-testnet": "CBCJIODXIEBOXXD66KCUCF7ZDYJARKI4ZIVQOVWPULOBH5XGNCDP6W3I",
1572
- // Set once the factory is deployed to mainnet (its address differs — network id
1573
- // is part of contract-address derivation).
1574
- "stellar-mainnet": ""
1575
- };
1576
- var DEVICE_ACCOUNT_WASM_HASH = {
1577
- "stellar-testnet": "2671b085578e59a385ef5a5664e42f0450322fe3249539f588e1263ed5a31dce",
1578
- "stellar-mainnet": ""
1579
- };
1580
1610
  var STELLAR_NETWORKS = {
1581
1611
  "stellar-testnet": {
1582
- rpcUrl: "https://soroban-testnet.stellar.org",
1583
1612
  passphrase: "Test SDF Network ; September 2015"
1584
1613
  },
1585
1614
  "stellar-mainnet": {
1586
- rpcUrl: "https://soroban-rpc.mainnet.stellar.gateway.fm",
1587
1615
  passphrase: "Public Global Stellar Network ; September 2015"
1588
1616
  }
1589
1617
  };
1590
- var NATIVE_SAC_ID = {
1591
- "stellar-testnet": "CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC",
1592
- "stellar-mainnet": "CAS3J7GYLGXMF6TDJBBYYSE3HQ6BBSMLNUQ34T6TZMYMW2EVH34XOWMA"
1618
+ var HORIZON_URL = {
1619
+ "stellar-testnet": "https://horizon-testnet.stellar.org",
1620
+ "stellar-mainnet": "https://horizon.stellar.org"
1621
+ };
1622
+ var XLM_DECIMALS = 7;
1623
+
1624
+ // node_modules/@noble/ciphers/esm/utils.js
1625
+ function isBytes(a) {
1626
+ return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
1627
+ }
1628
+ function abytes(b, ...lengths) {
1629
+ if (!isBytes(b))
1630
+ throw new Error("Uint8Array expected");
1631
+ if (lengths.length > 0 && !lengths.includes(b.length))
1632
+ throw new Error("Uint8Array expected of length " + lengths + ", got length=" + b.length);
1633
+ }
1634
+ function aexists(instance, checkFinished = true) {
1635
+ if (instance.destroyed)
1636
+ throw new Error("Hash instance has been destroyed");
1637
+ if (checkFinished && instance.finished)
1638
+ throw new Error("Hash#digest() has already been called");
1639
+ }
1640
+ function aoutput(out, instance) {
1641
+ abytes(out);
1642
+ const min = instance.outputLen;
1643
+ if (out.length < min) {
1644
+ throw new Error("digestInto() expects output buffer of length at least " + min);
1645
+ }
1646
+ }
1647
+ function u8(arr) {
1648
+ return new Uint8Array(arr.buffer, arr.byteOffset, arr.byteLength);
1649
+ }
1650
+ function u32(arr) {
1651
+ return new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
1652
+ }
1653
+ function clean(...arrays) {
1654
+ for (let i = 0; i < arrays.length; i++) {
1655
+ arrays[i].fill(0);
1656
+ }
1657
+ }
1658
+ function createView(arr) {
1659
+ return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
1660
+ }
1661
+ var isLE = /* @__PURE__ */ (() => new Uint8Array(new Uint32Array([287454020]).buffer)[0] === 68)();
1662
+ function utf8ToBytes(str) {
1663
+ if (typeof str !== "string")
1664
+ throw new Error("string expected");
1665
+ return new Uint8Array(new TextEncoder().encode(str));
1666
+ }
1667
+ function toBytes(data) {
1668
+ if (typeof data === "string")
1669
+ data = utf8ToBytes(data);
1670
+ else if (isBytes(data))
1671
+ data = copyBytes(data);
1672
+ else
1673
+ throw new Error("Uint8Array expected, got " + typeof data);
1674
+ return data;
1675
+ }
1676
+ function equalBytes(a, b) {
1677
+ if (a.length !== b.length)
1678
+ return false;
1679
+ let diff = 0;
1680
+ for (let i = 0; i < a.length; i++)
1681
+ diff |= a[i] ^ b[i];
1682
+ return diff === 0;
1683
+ }
1684
+ var wrapCipher = /* @__NO_SIDE_EFFECTS__ */ (params, constructor) => {
1685
+ function wrappedCipher(key, ...args) {
1686
+ abytes(key);
1687
+ if (!isLE)
1688
+ throw new Error("Non little-endian hardware is not yet supported");
1689
+ if (params.nonceLength !== void 0) {
1690
+ const nonce = args[0];
1691
+ if (!nonce)
1692
+ throw new Error("nonce / iv required");
1693
+ if (params.varSizeNonce)
1694
+ abytes(nonce);
1695
+ else
1696
+ abytes(nonce, params.nonceLength);
1697
+ }
1698
+ const tagl = params.tagLength;
1699
+ if (tagl && args[1] !== void 0) {
1700
+ abytes(args[1]);
1701
+ }
1702
+ const cipher = constructor(key, ...args);
1703
+ const checkOutput = (fnLength, output) => {
1704
+ if (output !== void 0) {
1705
+ if (fnLength !== 2)
1706
+ throw new Error("cipher output not supported");
1707
+ abytes(output);
1708
+ }
1709
+ };
1710
+ let called = false;
1711
+ const wrCipher = {
1712
+ encrypt(data, output) {
1713
+ if (called)
1714
+ throw new Error("cannot encrypt() twice with same key + nonce");
1715
+ called = true;
1716
+ abytes(data);
1717
+ checkOutput(cipher.encrypt.length, output);
1718
+ return cipher.encrypt(data, output);
1719
+ },
1720
+ decrypt(data, output) {
1721
+ abytes(data);
1722
+ if (tagl && data.length < tagl)
1723
+ throw new Error("invalid ciphertext length: smaller than tagLength=" + tagl);
1724
+ checkOutput(cipher.decrypt.length, output);
1725
+ return cipher.decrypt(data, output);
1726
+ }
1727
+ };
1728
+ return wrCipher;
1729
+ }
1730
+ Object.assign(wrappedCipher, params);
1731
+ return wrappedCipher;
1732
+ };
1733
+ function getOutput(expectedLength, out, onlyAligned = true) {
1734
+ if (out === void 0)
1735
+ return new Uint8Array(expectedLength);
1736
+ if (out.length !== expectedLength)
1737
+ throw new Error("invalid output length, expected " + expectedLength + ", got: " + out.length);
1738
+ if (onlyAligned && !isAligned32(out))
1739
+ throw new Error("invalid output, must be aligned");
1740
+ return out;
1741
+ }
1742
+ function setBigUint64(view, byteOffset, value, isLE2) {
1743
+ if (typeof view.setBigUint64 === "function")
1744
+ return view.setBigUint64(byteOffset, value, isLE2);
1745
+ const _32n = BigInt(32);
1746
+ const _u32_max = BigInt(4294967295);
1747
+ const wh = Number(value >> _32n & _u32_max);
1748
+ const wl = Number(value & _u32_max);
1749
+ const h = 0;
1750
+ const l = 4;
1751
+ view.setUint32(byteOffset + h, wh, isLE2);
1752
+ view.setUint32(byteOffset + l, wl, isLE2);
1753
+ }
1754
+ function u64Lengths(dataLength, aadLength, isLE2) {
1755
+ const num5 = new Uint8Array(16);
1756
+ const view = createView(num5);
1757
+ setBigUint64(view, 0, BigInt(aadLength), isLE2);
1758
+ setBigUint64(view, 8, BigInt(dataLength), isLE2);
1759
+ return num5;
1760
+ }
1761
+ function isAligned32(bytes) {
1762
+ return bytes.byteOffset % 4 === 0;
1763
+ }
1764
+ function copyBytes(bytes) {
1765
+ return Uint8Array.from(bytes);
1766
+ }
1767
+
1768
+ // node_modules/@noble/ciphers/esm/_polyval.js
1769
+ var BLOCK_SIZE = 16;
1770
+ var ZEROS16 = /* @__PURE__ */ new Uint8Array(16);
1771
+ var ZEROS32 = u32(ZEROS16);
1772
+ var POLY = 225;
1773
+ var mul2 = (s0, s1, s2, s3) => {
1774
+ const hiBit = s3 & 1;
1775
+ return {
1776
+ s3: s2 << 31 | s3 >>> 1,
1777
+ s2: s1 << 31 | s2 >>> 1,
1778
+ s1: s0 << 31 | s1 >>> 1,
1779
+ s0: s0 >>> 1 ^ POLY << 24 & -(hiBit & 1)
1780
+ // reduce % poly
1781
+ };
1782
+ };
1783
+ var swapLE = (n) => (n >>> 0 & 255) << 24 | (n >>> 8 & 255) << 16 | (n >>> 16 & 255) << 8 | n >>> 24 & 255 | 0;
1784
+ function _toGHASHKey(k) {
1785
+ k.reverse();
1786
+ const hiBit = k[15] & 1;
1787
+ let carry = 0;
1788
+ for (let i = 0; i < k.length; i++) {
1789
+ const t = k[i];
1790
+ k[i] = t >>> 1 | carry;
1791
+ carry = (t & 1) << 7;
1792
+ }
1793
+ k[0] ^= -hiBit & 225;
1794
+ return k;
1795
+ }
1796
+ var estimateWindow = (bytes) => {
1797
+ if (bytes > 64 * 1024)
1798
+ return 8;
1799
+ if (bytes > 1024)
1800
+ return 4;
1801
+ return 2;
1802
+ };
1803
+ var GHASH = class {
1804
+ // We select bits per window adaptively based on expectedLength
1805
+ constructor(key, expectedLength) {
1806
+ this.blockLen = BLOCK_SIZE;
1807
+ this.outputLen = BLOCK_SIZE;
1808
+ this.s0 = 0;
1809
+ this.s1 = 0;
1810
+ this.s2 = 0;
1811
+ this.s3 = 0;
1812
+ this.finished = false;
1813
+ key = toBytes(key);
1814
+ abytes(key, 16);
1815
+ const kView = createView(key);
1816
+ let k0 = kView.getUint32(0, false);
1817
+ let k1 = kView.getUint32(4, false);
1818
+ let k2 = kView.getUint32(8, false);
1819
+ let k3 = kView.getUint32(12, false);
1820
+ const doubles = [];
1821
+ for (let i = 0; i < 128; i++) {
1822
+ doubles.push({ s0: swapLE(k0), s1: swapLE(k1), s2: swapLE(k2), s3: swapLE(k3) });
1823
+ ({ s0: k0, s1: k1, s2: k2, s3: k3 } = mul2(k0, k1, k2, k3));
1824
+ }
1825
+ const W = estimateWindow(expectedLength || 1024);
1826
+ if (![1, 2, 4, 8].includes(W))
1827
+ throw new Error("ghash: invalid window size, expected 2, 4 or 8");
1828
+ this.W = W;
1829
+ const bits = 128;
1830
+ const windows = bits / W;
1831
+ const windowSize = this.windowSize = 2 ** W;
1832
+ const items = [];
1833
+ for (let w = 0; w < windows; w++) {
1834
+ for (let byte = 0; byte < windowSize; byte++) {
1835
+ let s0 = 0, s1 = 0, s2 = 0, s3 = 0;
1836
+ for (let j = 0; j < W; j++) {
1837
+ const bit = byte >>> W - j - 1 & 1;
1838
+ if (!bit)
1839
+ continue;
1840
+ const { s0: d0, s1: d1, s2: d2, s3: d3 } = doubles[W * w + j];
1841
+ s0 ^= d0, s1 ^= d1, s2 ^= d2, s3 ^= d3;
1842
+ }
1843
+ items.push({ s0, s1, s2, s3 });
1844
+ }
1845
+ }
1846
+ this.t = items;
1847
+ }
1848
+ _updateBlock(s0, s1, s2, s3) {
1849
+ s0 ^= this.s0, s1 ^= this.s1, s2 ^= this.s2, s3 ^= this.s3;
1850
+ const { W, t, windowSize } = this;
1851
+ let o0 = 0, o1 = 0, o2 = 0, o3 = 0;
1852
+ const mask = (1 << W) - 1;
1853
+ let w = 0;
1854
+ for (const num5 of [s0, s1, s2, s3]) {
1855
+ for (let bytePos = 0; bytePos < 4; bytePos++) {
1856
+ const byte = num5 >>> 8 * bytePos & 255;
1857
+ for (let bitPos = 8 / W - 1; bitPos >= 0; bitPos--) {
1858
+ const bit = byte >>> W * bitPos & mask;
1859
+ const { s0: e0, s1: e1, s2: e2, s3: e3 } = t[w * windowSize + bit];
1860
+ o0 ^= e0, o1 ^= e1, o2 ^= e2, o3 ^= e3;
1861
+ w += 1;
1862
+ }
1863
+ }
1864
+ }
1865
+ this.s0 = o0;
1866
+ this.s1 = o1;
1867
+ this.s2 = o2;
1868
+ this.s3 = o3;
1869
+ }
1870
+ update(data) {
1871
+ aexists(this);
1872
+ data = toBytes(data);
1873
+ abytes(data);
1874
+ const b32 = u32(data);
1875
+ const blocks = Math.floor(data.length / BLOCK_SIZE);
1876
+ const left = data.length % BLOCK_SIZE;
1877
+ for (let i = 0; i < blocks; i++) {
1878
+ this._updateBlock(b32[i * 4 + 0], b32[i * 4 + 1], b32[i * 4 + 2], b32[i * 4 + 3]);
1879
+ }
1880
+ if (left) {
1881
+ ZEROS16.set(data.subarray(blocks * BLOCK_SIZE));
1882
+ this._updateBlock(ZEROS32[0], ZEROS32[1], ZEROS32[2], ZEROS32[3]);
1883
+ clean(ZEROS32);
1884
+ }
1885
+ return this;
1886
+ }
1887
+ destroy() {
1888
+ const { t } = this;
1889
+ for (const elm of t) {
1890
+ elm.s0 = 0, elm.s1 = 0, elm.s2 = 0, elm.s3 = 0;
1891
+ }
1892
+ }
1893
+ digestInto(out) {
1894
+ aexists(this);
1895
+ aoutput(out, this);
1896
+ this.finished = true;
1897
+ const { s0, s1, s2, s3 } = this;
1898
+ const o32 = u32(out);
1899
+ o32[0] = s0;
1900
+ o32[1] = s1;
1901
+ o32[2] = s2;
1902
+ o32[3] = s3;
1903
+ return out;
1904
+ }
1905
+ digest() {
1906
+ const res = new Uint8Array(BLOCK_SIZE);
1907
+ this.digestInto(res);
1908
+ this.destroy();
1909
+ return res;
1910
+ }
1911
+ };
1912
+ var Polyval = class extends GHASH {
1913
+ constructor(key, expectedLength) {
1914
+ key = toBytes(key);
1915
+ abytes(key);
1916
+ const ghKey = _toGHASHKey(copyBytes(key));
1917
+ super(ghKey, expectedLength);
1918
+ clean(ghKey);
1919
+ }
1920
+ update(data) {
1921
+ data = toBytes(data);
1922
+ aexists(this);
1923
+ const b32 = u32(data);
1924
+ const left = data.length % BLOCK_SIZE;
1925
+ const blocks = Math.floor(data.length / BLOCK_SIZE);
1926
+ for (let i = 0; i < blocks; i++) {
1927
+ this._updateBlock(swapLE(b32[i * 4 + 3]), swapLE(b32[i * 4 + 2]), swapLE(b32[i * 4 + 1]), swapLE(b32[i * 4 + 0]));
1928
+ }
1929
+ if (left) {
1930
+ ZEROS16.set(data.subarray(blocks * BLOCK_SIZE));
1931
+ this._updateBlock(swapLE(ZEROS32[3]), swapLE(ZEROS32[2]), swapLE(ZEROS32[1]), swapLE(ZEROS32[0]));
1932
+ clean(ZEROS32);
1933
+ }
1934
+ return this;
1935
+ }
1936
+ digestInto(out) {
1937
+ aexists(this);
1938
+ aoutput(out, this);
1939
+ this.finished = true;
1940
+ const { s0, s1, s2, s3 } = this;
1941
+ const o32 = u32(out);
1942
+ o32[0] = s0;
1943
+ o32[1] = s1;
1944
+ o32[2] = s2;
1945
+ o32[3] = s3;
1946
+ return out.reverse();
1947
+ }
1593
1948
  };
1949
+ function wrapConstructorWithKey(hashCons) {
1950
+ const hashC = (msg, key) => hashCons(key, msg.length).update(toBytes(msg)).digest();
1951
+ const tmp = hashCons(new Uint8Array(16), 0);
1952
+ hashC.outputLen = tmp.outputLen;
1953
+ hashC.blockLen = tmp.blockLen;
1954
+ hashC.create = (key, expectedLength) => hashCons(key, expectedLength);
1955
+ return hashC;
1956
+ }
1957
+ var ghash = wrapConstructorWithKey((key, expectedLength) => new GHASH(key, expectedLength));
1958
+ wrapConstructorWithKey((key, expectedLength) => new Polyval(key, expectedLength));
1959
+
1960
+ // node_modules/@noble/ciphers/esm/aes.js
1961
+ var BLOCK_SIZE2 = 16;
1962
+ var BLOCK_SIZE32 = 4;
1963
+ var EMPTY_BLOCK = /* @__PURE__ */ new Uint8Array(BLOCK_SIZE2);
1964
+ var POLY2 = 283;
1965
+ function mul22(n) {
1966
+ return n << 1 ^ POLY2 & -(n >> 7);
1967
+ }
1968
+ function mul(a, b) {
1969
+ let res = 0;
1970
+ for (; b > 0; b >>= 1) {
1971
+ res ^= a & -(b & 1);
1972
+ a = mul22(a);
1973
+ }
1974
+ return res;
1975
+ }
1976
+ var sbox = /* @__PURE__ */ (() => {
1977
+ const t = new Uint8Array(256);
1978
+ for (let i = 0, x = 1; i < 256; i++, x ^= mul22(x))
1979
+ t[i] = x;
1980
+ const box = new Uint8Array(256);
1981
+ box[0] = 99;
1982
+ for (let i = 0; i < 255; i++) {
1983
+ let x = t[255 - i];
1984
+ x |= x << 8;
1985
+ box[t[i]] = (x ^ x >> 4 ^ x >> 5 ^ x >> 6 ^ x >> 7 ^ 99) & 255;
1986
+ }
1987
+ clean(t);
1988
+ return box;
1989
+ })();
1990
+ var rotr32_8 = (n) => n << 24 | n >>> 8;
1991
+ var rotl32_8 = (n) => n << 8 | n >>> 24;
1992
+ function genTtable(sbox2, fn) {
1993
+ if (sbox2.length !== 256)
1994
+ throw new Error("Wrong sbox length");
1995
+ const T0 = new Uint32Array(256).map((_, j) => fn(sbox2[j]));
1996
+ const T1 = T0.map(rotl32_8);
1997
+ const T2 = T1.map(rotl32_8);
1998
+ const T3 = T2.map(rotl32_8);
1999
+ const T01 = new Uint32Array(256 * 256);
2000
+ const T23 = new Uint32Array(256 * 256);
2001
+ const sbox22 = new Uint16Array(256 * 256);
2002
+ for (let i = 0; i < 256; i++) {
2003
+ for (let j = 0; j < 256; j++) {
2004
+ const idx = i * 256 + j;
2005
+ T01[idx] = T0[i] ^ T1[j];
2006
+ T23[idx] = T2[i] ^ T3[j];
2007
+ sbox22[idx] = sbox2[i] << 8 | sbox2[j];
2008
+ }
2009
+ }
2010
+ return { sbox: sbox2, sbox2: sbox22, T0, T1, T2, T3, T01, T23 };
2011
+ }
2012
+ var tableEncoding = /* @__PURE__ */ genTtable(sbox, (s) => mul(s, 3) << 24 | s << 16 | s << 8 | mul(s, 2));
2013
+ var xPowers = /* @__PURE__ */ (() => {
2014
+ const p = new Uint8Array(16);
2015
+ for (let i = 0, x = 1; i < 16; i++, x = mul22(x))
2016
+ p[i] = x;
2017
+ return p;
2018
+ })();
2019
+ function expandKeyLE(key) {
2020
+ abytes(key);
2021
+ const len = key.length;
2022
+ if (![16, 24, 32].includes(len))
2023
+ throw new Error("aes: invalid key size, should be 16, 24 or 32, got " + len);
2024
+ const { sbox2 } = tableEncoding;
2025
+ const toClean = [];
2026
+ if (!isAligned32(key))
2027
+ toClean.push(key = copyBytes(key));
2028
+ const k32 = u32(key);
2029
+ const Nk = k32.length;
2030
+ const subByte = (n) => applySbox(sbox2, n, n, n, n);
2031
+ const xk = new Uint32Array(len + 28);
2032
+ xk.set(k32);
2033
+ for (let i = Nk; i < xk.length; i++) {
2034
+ let t = xk[i - 1];
2035
+ if (i % Nk === 0)
2036
+ t = subByte(rotr32_8(t)) ^ xPowers[i / Nk - 1];
2037
+ else if (Nk > 6 && i % Nk === 4)
2038
+ t = subByte(t);
2039
+ xk[i] = xk[i - Nk] ^ t;
2040
+ }
2041
+ clean(...toClean);
2042
+ return xk;
2043
+ }
2044
+ function apply0123(T01, T23, s0, s1, s2, s3) {
2045
+ return T01[s0 << 8 & 65280 | s1 >>> 8 & 255] ^ T23[s2 >>> 8 & 65280 | s3 >>> 24 & 255];
2046
+ }
2047
+ function applySbox(sbox2, s0, s1, s2, s3) {
2048
+ return sbox2[s0 & 255 | s1 & 65280] | sbox2[s2 >>> 16 & 255 | s3 >>> 16 & 65280] << 16;
2049
+ }
2050
+ function encrypt(xk, s0, s1, s2, s3) {
2051
+ const { sbox2, T01, T23 } = tableEncoding;
2052
+ let k = 0;
2053
+ s0 ^= xk[k++], s1 ^= xk[k++], s2 ^= xk[k++], s3 ^= xk[k++];
2054
+ const rounds = xk.length / 4 - 2;
2055
+ for (let i = 0; i < rounds; i++) {
2056
+ const t02 = xk[k++] ^ apply0123(T01, T23, s0, s1, s2, s3);
2057
+ const t12 = xk[k++] ^ apply0123(T01, T23, s1, s2, s3, s0);
2058
+ const t22 = xk[k++] ^ apply0123(T01, T23, s2, s3, s0, s1);
2059
+ const t32 = xk[k++] ^ apply0123(T01, T23, s3, s0, s1, s2);
2060
+ s0 = t02, s1 = t12, s2 = t22, s3 = t32;
2061
+ }
2062
+ const t0 = xk[k++] ^ applySbox(sbox2, s0, s1, s2, s3);
2063
+ const t1 = xk[k++] ^ applySbox(sbox2, s1, s2, s3, s0);
2064
+ const t2 = xk[k++] ^ applySbox(sbox2, s2, s3, s0, s1);
2065
+ const t3 = xk[k++] ^ applySbox(sbox2, s3, s0, s1, s2);
2066
+ return { s0: t0, s1: t1, s2: t2, s3: t3 };
2067
+ }
2068
+ function ctr32(xk, isLE2, nonce, src, dst) {
2069
+ abytes(nonce, BLOCK_SIZE2);
2070
+ abytes(src);
2071
+ dst = getOutput(src.length, dst);
2072
+ const ctr = nonce;
2073
+ const c32 = u32(ctr);
2074
+ const view = createView(ctr);
2075
+ const src32 = u32(src);
2076
+ const dst32 = u32(dst);
2077
+ const ctrPos = 12;
2078
+ const srcLen = src.length;
2079
+ let ctrNum = view.getUint32(ctrPos, isLE2);
2080
+ let { s0, s1, s2, s3 } = encrypt(xk, c32[0], c32[1], c32[2], c32[3]);
2081
+ for (let i = 0; i + 4 <= src32.length; i += 4) {
2082
+ dst32[i + 0] = src32[i + 0] ^ s0;
2083
+ dst32[i + 1] = src32[i + 1] ^ s1;
2084
+ dst32[i + 2] = src32[i + 2] ^ s2;
2085
+ dst32[i + 3] = src32[i + 3] ^ s3;
2086
+ ctrNum = ctrNum + 1 >>> 0;
2087
+ view.setUint32(ctrPos, ctrNum, isLE2);
2088
+ ({ s0, s1, s2, s3 } = encrypt(xk, c32[0], c32[1], c32[2], c32[3]));
2089
+ }
2090
+ const start = BLOCK_SIZE2 * Math.floor(src32.length / BLOCK_SIZE32);
2091
+ if (start < srcLen) {
2092
+ const b32 = new Uint32Array([s0, s1, s2, s3]);
2093
+ const buf3 = u8(b32);
2094
+ for (let i = start, pos = 0; i < srcLen; i++, pos++)
2095
+ dst[i] = src[i] ^ buf3[pos];
2096
+ clean(b32);
2097
+ }
2098
+ return dst;
2099
+ }
2100
+ function computeTag(fn, isLE2, key, data, AAD) {
2101
+ const aadLength = AAD ? AAD.length : 0;
2102
+ const h = fn.create(key, data.length + aadLength);
2103
+ if (AAD)
2104
+ h.update(AAD);
2105
+ const num5 = u64Lengths(8 * data.length, 8 * aadLength, isLE2);
2106
+ h.update(data);
2107
+ h.update(num5);
2108
+ const res = h.digest();
2109
+ clean(num5);
2110
+ return res;
2111
+ }
2112
+ var gcm = /* @__PURE__ */ wrapCipher({ blockSize: 16, nonceLength: 12, tagLength: 16, varSizeNonce: true }, function aesgcm(key, nonce, AAD) {
2113
+ if (nonce.length < 8)
2114
+ throw new Error("aes/gcm: invalid nonce length");
2115
+ const tagLength = 16;
2116
+ function _computeTag(authKey, tagMask, data) {
2117
+ const tag = computeTag(ghash, false, authKey, data, AAD);
2118
+ for (let i = 0; i < tagMask.length; i++)
2119
+ tag[i] ^= tagMask[i];
2120
+ return tag;
2121
+ }
2122
+ function deriveKeys() {
2123
+ const xk = expandKeyLE(key);
2124
+ const authKey = EMPTY_BLOCK.slice();
2125
+ const counter = EMPTY_BLOCK.slice();
2126
+ ctr32(xk, false, counter, counter, authKey);
2127
+ if (nonce.length === 12) {
2128
+ counter.set(nonce);
2129
+ } else {
2130
+ const nonceLen = EMPTY_BLOCK.slice();
2131
+ const view = createView(nonceLen);
2132
+ setBigUint64(view, 8, BigInt(nonce.length * 8), false);
2133
+ const g = ghash.create(authKey).update(nonce).update(nonceLen);
2134
+ g.digestInto(counter);
2135
+ g.destroy();
2136
+ }
2137
+ const tagMask = ctr32(xk, false, counter, EMPTY_BLOCK);
2138
+ return { xk, authKey, counter, tagMask };
2139
+ }
2140
+ return {
2141
+ encrypt(plaintext) {
2142
+ const { xk, authKey, counter, tagMask } = deriveKeys();
2143
+ const out = new Uint8Array(plaintext.length + tagLength);
2144
+ const toClean = [xk, authKey, counter, tagMask];
2145
+ if (!isAligned32(plaintext))
2146
+ toClean.push(plaintext = copyBytes(plaintext));
2147
+ ctr32(xk, false, counter, plaintext, out.subarray(0, plaintext.length));
2148
+ const tag = _computeTag(authKey, tagMask, out.subarray(0, out.length - tagLength));
2149
+ toClean.push(tag);
2150
+ out.set(tag, plaintext.length);
2151
+ clean(...toClean);
2152
+ return out;
2153
+ },
2154
+ decrypt(ciphertext) {
2155
+ const { xk, authKey, counter, tagMask } = deriveKeys();
2156
+ const toClean = [xk, authKey, tagMask, counter];
2157
+ if (!isAligned32(ciphertext))
2158
+ toClean.push(ciphertext = copyBytes(ciphertext));
2159
+ const data = ciphertext.subarray(0, -tagLength);
2160
+ const passedTag = ciphertext.subarray(-tagLength);
2161
+ const tag = _computeTag(authKey, tagMask, data);
2162
+ toClean.push(tag);
2163
+ if (!equalBytes(tag, passedTag))
2164
+ throw new Error("aes/gcm: invalid ghash tag");
2165
+ const out = ctr32(xk, false, counter, data);
2166
+ clean(...toClean);
2167
+ return out;
2168
+ }
2169
+ };
2170
+ });
2171
+ var NONCE_LEN = 12;
2172
+ var RECOVERY_INFO = "cavos-stellar-dek-recovery";
2173
+ var PASSKEY_INFO = "cavos-stellar-dek-passkey";
2174
+ var ECIES_INFO = "cavos-stellar-dek-ecies";
2175
+ var RECOVERY_PBKDF2_ITERATIONS = 21e4;
2176
+ var RECOVERY_KDF_SALT = "cavos-stellar-recovery-v1";
2177
+ function generateDEK() {
2178
+ return utils.randomBytes(32);
2179
+ }
2180
+ function sealControlSeed(controlSeed, dek) {
2181
+ const nonce = utils.randomBytes(NONCE_LEN);
2182
+ const ct = gcm(dek, nonce).encrypt(controlSeed);
2183
+ return concat(nonce, ct);
2184
+ }
2185
+ function openControlSeed(sealed, dek) {
2186
+ const { nonce, ct } = splitNonce(sealed);
2187
+ return gcm(dek, nonce).decrypt(ct);
2188
+ }
2189
+ function wrapDEK(dek, kek) {
2190
+ const nonce = utils.randomBytes(NONCE_LEN);
2191
+ return concat(nonce, gcm(kek, nonce).encrypt(dek));
2192
+ }
2193
+ function unwrapDEK(wrapped, kek) {
2194
+ const { nonce, ct } = splitNonce(wrapped);
2195
+ return gcm(kek, nonce).decrypt(ct);
2196
+ }
2197
+ function deriveRecoveryKEK(code) {
2198
+ const normalised = code.trim().replace(/\s+/g, " ").toLowerCase();
2199
+ if (!normalised) throw new Error("kit/stellar: recovery code is empty");
2200
+ const stretched = pbkdf2.pbkdf2(sha256.sha256, new TextEncoder().encode(normalised), new TextEncoder().encode(RECOVERY_KDF_SALT), {
2201
+ c: RECOVERY_PBKDF2_ITERATIONS,
2202
+ dkLen: 32
2203
+ });
2204
+ return hkdf.hkdf(sha256.sha256, stretched, void 0, RECOVERY_INFO, 32);
2205
+ }
2206
+ function derivePasskeyKEK(prfOutput) {
2207
+ if (prfOutput.length < 32) throw new Error("kit/stellar: passkey PRF output too short");
2208
+ return hkdf.hkdf(sha256.sha256, prfOutput, void 0, PASSKEY_INFO, 32);
2209
+ }
2210
+ function eciesWrapDEK(dek, recipientPubSec1) {
2211
+ const ephPriv = p256.p256.utils.randomPrivateKey();
2212
+ const ephPubCompressed = p256.p256.getPublicKey(ephPriv, true);
2213
+ const kek = eciesKEK(p256.p256.getSharedSecret(ephPriv, recipientPubSec1, false), ephPubCompressed);
2214
+ const wrapped = wrapDEK(dek, kek);
2215
+ return concat(ephPubCompressed, wrapped);
2216
+ }
2217
+ function eciesUnwrapDEK(blob, recipientPrivScalar) {
2218
+ const ephPubCompressed = blob.subarray(0, 33);
2219
+ const wrapped = blob.subarray(33);
2220
+ const kek = eciesKEK(p256.p256.getSharedSecret(recipientPrivScalar, ephPubCompressed, false), ephPubCompressed);
2221
+ return unwrapDEK(wrapped, kek);
2222
+ }
2223
+ function eciesKEK(sharedUncompressed, ephPubCompressed) {
2224
+ return eciesKEKFromX(sharedUncompressed.subarray(1, 33), ephPubCompressed);
2225
+ }
2226
+ function eciesKEKFromX(sharedX, ephPubCompressed) {
2227
+ return hkdf.hkdf(sha256.sha256, sharedX, ephPubCompressed, ECIES_INFO, 32);
2228
+ }
2229
+ var DATA_ENTRY_MAX = 64;
2230
+ function chunkTo64(blob) {
2231
+ if (blob.length <= DATA_ENTRY_MAX) return [blob];
2232
+ const chunks = [];
2233
+ for (let i = 0; i < blob.length; i += DATA_ENTRY_MAX) {
2234
+ chunks.push(blob.subarray(i, i + DATA_ENTRY_MAX));
2235
+ }
2236
+ return chunks;
2237
+ }
2238
+ function unchunk(chunks) {
2239
+ const total = chunks.reduce((n, c) => n + c.length, 0);
2240
+ const out = new Uint8Array(total);
2241
+ let off = 0;
2242
+ for (const c of chunks) {
2243
+ out.set(c, off);
2244
+ off += c.length;
2245
+ }
2246
+ return out;
2247
+ }
2248
+ function concat(a, b) {
2249
+ const out = new Uint8Array(a.length + b.length);
2250
+ out.set(a, 0);
2251
+ out.set(b, a.length);
2252
+ return out;
2253
+ }
2254
+ function splitNonce(blob) {
2255
+ return { nonce: blob.subarray(0, NONCE_LEN), ct: blob.subarray(NONCE_LEN) };
2256
+ }
2257
+
2258
+ // src/chains/stellar/datamap.ts
2259
+ var CT_BASE = "cv:ct";
2260
+ var PASSKEY_BASE = "cv:wp";
2261
+ var RECOVERY_BASE = "cv:wr";
2262
+ var VERSION_KEY = "cv:v";
2263
+ var ENVELOPE_VERSION = 1;
2264
+ function deviceBase(slot) {
2265
+ return `cv:wd:${slot}`;
2266
+ }
2267
+ function toDataEntries(env) {
2268
+ const out = {};
2269
+ writeChunked(out, CT_BASE, env.ct);
2270
+ for (const [slot, blob] of Object.entries(env.deviceWraps)) {
2271
+ writeChunked(out, deviceBase(slot), blob);
2272
+ }
2273
+ if (env.passkeyWrap) writeChunked(out, PASSKEY_BASE, env.passkeyWrap);
2274
+ if (env.recoveryWrap) writeChunked(out, RECOVERY_BASE, env.recoveryWrap);
2275
+ out[VERSION_KEY] = Uint8Array.of(ENVELOPE_VERSION);
2276
+ return out;
2277
+ }
2278
+ function fromDataEntries(entries) {
2279
+ const ct = readChunked(entries, CT_BASE);
2280
+ if (!ct) throw new Error("kit/stellar: account has no control-seed ciphertext (cv:ct)");
2281
+ const deviceWraps = {};
2282
+ const seenSlots = /* @__PURE__ */ new Set();
2283
+ for (const key of Object.keys(entries)) {
2284
+ const m = key.match(/^cv:wd:([^/]+)\/\d+$/);
2285
+ if (m) seenSlots.add(m[1]);
2286
+ }
2287
+ for (const slot of seenSlots) {
2288
+ const blob = readChunked(entries, deviceBase(slot));
2289
+ if (blob) deviceWraps[slot] = blob;
2290
+ }
2291
+ return {
2292
+ ct,
2293
+ deviceWraps,
2294
+ passkeyWrap: readChunked(entries, PASSKEY_BASE),
2295
+ recoveryWrap: readChunked(entries, RECOVERY_BASE)
2296
+ };
2297
+ }
2298
+ function deviceWrapEntries(slot, blob) {
2299
+ const out = {};
2300
+ writeChunked(out, deviceBase(slot), blob);
2301
+ return out;
2302
+ }
2303
+ function writeChunked(out, base, blob) {
2304
+ const chunks = chunkTo64(blob);
2305
+ chunks.forEach((chunk, i) => {
2306
+ out[`${base}/${i}`] = chunk;
2307
+ });
2308
+ }
2309
+ function readChunked(entries, base) {
2310
+ const chunks = [];
2311
+ for (let i = 0; ; i++) {
2312
+ const chunk = entries[`${base}/${i}`];
2313
+ if (!chunk) break;
2314
+ chunks.push(chunk);
2315
+ }
2316
+ return chunks.length ? unchunk(chunks) : void 0;
2317
+ }
1594
2318
 
1595
2319
  // src/chains/stellar/StellarAdapter.ts
1596
- var SECP256R1_N2 = 0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551n;
2320
+ var TX_TIMEOUT = 180;
1597
2321
  var StellarAdapter = class {
1598
2322
  constructor(opts) {
1599
2323
  this.chain = "stellar";
1600
2324
  this.network = opts.network;
1601
2325
  this.passphrase = STELLAR_NETWORKS[opts.network].passphrase;
1602
- this.rpcUrl = opts.rpcUrl ?? STELLAR_NETWORKS[opts.network].rpcUrl;
1603
- this.factoryId = opts.factoryId ?? FACTORY_CONTRACT_ID[opts.network];
1604
- if (!this.factoryId) {
1605
- throw new Error(`kit/stellar: no factory contract id configured for ${opts.network}`);
1606
- }
1607
- this.signer = opts.signer;
2326
+ this.horizonUrl = opts.horizonUrl ?? HORIZON_URL[opts.network];
1608
2327
  }
1609
2328
  server() {
1610
2329
  if (!this._server) {
1611
- this._server = new stellarSdk.rpc.Server(this.rpcUrl, {
1612
- allowHttp: this.rpcUrl.startsWith("http://")
2330
+ this._server = new stellarSdk.Horizon.Server(this.horizonUrl, {
2331
+ allowHttp: this.horizonUrl.startsWith("http://")
1613
2332
  });
1614
2333
  }
1615
2334
  return this._server;
1616
2335
  }
1617
- networkId() {
1618
- return stellarSdk.hash(Buffer.from(this.passphrase));
2336
+ /** Whether the classic account already exists on-chain. */
2337
+ async isDeployed(address) {
2338
+ try {
2339
+ await this.server().loadAccount(address);
2340
+ return true;
2341
+ } catch (e) {
2342
+ if (isNotFound(e)) return false;
2343
+ throw e;
2344
+ }
2345
+ }
2346
+ /** Read the account's `MANAGE_DATA` entries as raw bytes (name → value). */
2347
+ async loadDataEntries(address) {
2348
+ const account = await this.server().loadAccount(address);
2349
+ const out = {};
2350
+ for (const [name, b64] of Object.entries(account.data_attr ?? {})) {
2351
+ out[name] = new Uint8Array(Buffer.from(b64, "base64"));
2352
+ }
2353
+ return out;
2354
+ }
2355
+ /** Native XLM balance in stroops. Returns 0n if the account doesn't exist. */
2356
+ async balance(address) {
2357
+ try {
2358
+ const account = await this.server().loadAccount(address);
2359
+ const native = account.balances.find((b) => b.asset_type === "native");
2360
+ return native ? toStroops(native.balance) : 0n;
2361
+ } catch (e) {
2362
+ if (isNotFound(e)) return 0n;
2363
+ throw e;
2364
+ }
1619
2365
  }
1620
2366
  /**
1621
- * Deterministic account address for `(addressSeed, initialSigner)` computed
1622
- * off-chain, byte-identical to the factory's on-chain `account_address`.
1623
- * `contractId = sha256(HashIdPreimage(networkId, factory, salt))` with
1624
- * `salt = sha256(addressSeed || sec1(initialSigner))`.
2367
+ * Build the account-creation transaction (source = funder, the relayer or a
2368
+ * self-funded keypair):
2369
+ * 1. `createAccount` funds the deterministic `G…` master address,
2370
+ * 2. `manageData` writes the control-key envelope entries (authorized by the
2371
+ * still-weight-1 master),
2372
+ * 3. `setOptions` adds the control signer (weight 1), sets all thresholds to
2373
+ * 1, and zeroes the master weight — after this the master can never sign.
2374
+ *
2375
+ * The returned tx must be signed by BOTH the master keypair (for the account's
2376
+ * own ops) and the funder (source + fee). Sponsorship of reserves is layered on
2377
+ * in Phase 3.
1625
2378
  */
1626
- computeAddress(addressSeed, initialSigner) {
1627
- const salt = this.accountSalt(addressSeed, initialSigner);
1628
- const preimage = stellarSdk.xdr.HashIdPreimage.envelopeTypeContractId(
1629
- new stellarSdk.xdr.HashIdPreimageContractId({
1630
- networkId: this.networkId(),
1631
- contractIdPreimage: stellarSdk.xdr.ContractIdPreimage.contractIdPreimageFromAddress(
1632
- new stellarSdk.xdr.ContractIdPreimageFromAddress({
1633
- address: new stellarSdk.Address(this.factoryId).toScAddress(),
1634
- salt
1635
- })
1636
- )
2379
+ async buildCreateTx(params) {
2380
+ const funderAccount = await this.server().loadAccount(params.funder);
2381
+ const builder = new stellarSdk.TransactionBuilder(funderAccount, {
2382
+ fee: stellarSdk.BASE_FEE,
2383
+ networkPassphrase: this.passphrase
2384
+ });
2385
+ builder.addOperation(
2386
+ stellarSdk.Operation.createAccount({
2387
+ destination: params.masterAddress,
2388
+ startingBalance: fromStroops(params.startingBalance)
1637
2389
  })
1638
2390
  );
1639
- return stellarSdk.StrKey.encodeContract(stellarSdk.hash(preimage.toXDR()));
1640
- }
1641
- /** `salt = sha256(addressSeed(32) || sec1(initialSigner)(65))` — matches the factory. */
1642
- accountSalt(addressSeed, initialSigner) {
1643
- return stellarSdk.hash(Buffer.concat([Buffer.from(addressSeed), Buffer.from(sec1Pubkey(initialSigner))]));
1644
- }
1645
- /** Host function: `factory.deploy(address_seed, initial_signer)`. */
1646
- buildDeploy(addressSeed, initialSigner) {
1647
- return invokeFunc(this.factoryId, "deploy", [
1648
- bytesScVal(addressSeed),
1649
- bytesScVal(sec1Pubkey(initialSigner))
1650
- ]);
1651
- }
1652
- /** Host function: `account.add_signer(new_signer)` (requires device auth). */
1653
- buildAddSigner(accountAddress, signer) {
1654
- return invokeFunc(accountAddress, "add_signer", [bytesScVal(sec1Pubkey(signer))]);
1655
- }
1656
- /** Host function: `account.remove_signer(signer)` (requires device auth). */
1657
- buildRemoveSigner(accountAddress, signer) {
1658
- return invokeFunc(accountAddress, "remove_signer", [bytesScVal(sec1Pubkey(signer))]);
1659
- }
1660
- /** Host function: `account.add_approver(passkey)` (requires device auth). */
1661
- buildAddApprover(accountAddress, passkey) {
1662
- return invokeFunc(accountAddress, "add_approver", [bytesScVal(sec1Pubkey(passkey))]);
1663
- }
1664
- /** Host function: `account.remove_approver(passkey)` (requires device auth). */
1665
- buildRemoveApprover(accountAddress, passkey) {
1666
- return invokeFunc(accountAddress, "remove_approver", [bytesScVal(sec1Pubkey(passkey))]);
1667
- }
1668
- /** This chain's leaf for approving `add_signer(newSigner)` at `nonce`:
1669
- * `sha256(sec1(new_signer) || nonce_be8)`. The batch challenge the passkey signs
1670
- * is `sha256(concat(leaves))` across chains. */
1671
- passkeyLeaf(newSigner, nonce) {
1672
- const msg = new Uint8Array(65 + 8);
1673
- msg.set(sec1Pubkey(newSigner), 0);
1674
- const n = new Uint8Array(8);
1675
- let v = nonce;
1676
- for (let i = 7; i >= 0; i--) {
1677
- n[i] = Number(v & 0xffn);
1678
- v >>= 8n;
1679
- }
1680
- msg.set(n, 65);
1681
- return sha256.sha256(msg);
1682
- }
1683
- /** Host function: passkey-authorized `add_signer_via_passkey` (no device auth —
1684
- * authorized by the embedded WebAuthn assertion, so any relayer can submit).
1685
- * `leaves`/`leafIndex` place this chain's leaf in the multi-chain batch. */
1686
- buildAddSignerViaPasskey(accountAddress, newSigner, passkey, nonce, leaves, leafIndex, assertion) {
1687
- const sig = encodeLowSSignature2({ r: assertion.r, s: assertion.s});
1688
- const leavesScVal = stellarSdk.xdr.ScVal.scvVec(leaves.map((l) => bytesScVal(l)));
1689
- return invokeFunc(accountAddress, "add_signer_via_passkey", [
1690
- bytesScVal(sec1Pubkey(newSigner)),
1691
- bytesScVal(sec1Pubkey(passkey)),
1692
- stellarSdk.nativeToScVal(nonce, { type: "u64" }),
1693
- leavesScVal,
1694
- stellarSdk.nativeToScVal(leafIndex, { type: "u32" }),
1695
- bytesScVal(assertion.authenticatorData),
1696
- bytesScVal(assertion.clientDataJSON),
1697
- stellarSdk.nativeToScVal(assertion.challengeOffset, { type: "u32" }),
1698
- bytesScVal(sig)
1699
- ]);
1700
- }
1701
- /** Read whether `passkey` is a registered approver (read-only simulation). */
1702
- /** True if the account has at least one passkey registered as an approver.
1703
- * Reads the contract's `approvers` view and checks the list is non-empty. */
1704
- async hasPasskeyApprover(accountAddress, readSource) {
1705
- if (!await this.isDeployed(accountAddress)) return false;
1706
- const { Account: Account3, TransactionBuilder: TransactionBuilder2, BASE_FEE: BASE_FEE2 } = await import('@stellar/stellar-sdk');
1707
- const src = new Account3(readSource, "0");
1708
- const op = stellarSdk.Operation.invokeHostFunction({
1709
- func: invokeFunc(accountAddress, "approvers", []),
1710
- auth: []
1711
- });
1712
- const tx2 = new TransactionBuilder2(src, { fee: BASE_FEE2, networkPassphrase: this.passphrase }).addOperation(op).setTimeout(30).build();
1713
- const sim = await this.server().simulateTransaction(tx2);
1714
- if (stellarSdk.rpc.Api.isSimulationError(sim)) {
1715
- throw new Error(`kit/stellar: approvers simulation failed: ${sim.error}`);
1716
- }
1717
- if (!sim.result?.retval) return false;
1718
- const list = stellarSdk.scValToNative(sim.result.retval);
1719
- return Array.isArray(list) && list.length > 0;
1720
- }
1721
- async isApprover(accountAddress, passkey, readSource) {
1722
- if (!await this.isDeployed(accountAddress)) return false;
1723
- const { Account: Account3, TransactionBuilder: TransactionBuilder2, BASE_FEE: BASE_FEE2 } = await import('@stellar/stellar-sdk');
1724
- const src = new Account3(readSource, "0");
1725
- const op = stellarSdk.Operation.invokeHostFunction({
1726
- func: invokeFunc(accountAddress, "is_approver", [bytesScVal(sec1Pubkey(passkey))]),
1727
- auth: []
1728
- });
1729
- const tx2 = new TransactionBuilder2(src, { fee: BASE_FEE2, networkPassphrase: this.passphrase }).addOperation(op).setTimeout(30).build();
1730
- const sim = await this.server().simulateTransaction(tx2);
1731
- if (stellarSdk.rpc.Api.isSimulationError(sim)) {
1732
- throw new Error(`kit/stellar: is_approver simulation failed: ${sim.error}`);
1733
- }
1734
- if (!sim.result?.retval) return false;
1735
- return stellarSdk.scValToNative(sim.result.retval) === true;
1736
- }
1737
- /** Read the current passkey-approval nonce (read-only simulation). */
1738
- async passkeyNonce(accountAddress, readSource) {
1739
- if (!await this.isDeployed(accountAddress)) return 0n;
1740
- const { Account: Account3, TransactionBuilder: TransactionBuilder2, BASE_FEE: BASE_FEE2 } = await import('@stellar/stellar-sdk');
1741
- const src = new Account3(readSource, "0");
1742
- const op = stellarSdk.Operation.invokeHostFunction({
1743
- func: invokeFunc(accountAddress, "passkey_nonce", []),
1744
- auth: []
1745
- });
1746
- const tx2 = new TransactionBuilder2(src, { fee: BASE_FEE2, networkPassphrase: this.passphrase }).addOperation(op).setTimeout(30).build();
1747
- const sim = await this.server().simulateTransaction(tx2);
1748
- if (stellarSdk.rpc.Api.isSimulationError(sim)) {
1749
- throw new Error(`kit/stellar: passkey_nonce simulation failed: ${sim.error}`);
1750
- }
1751
- if (!sim.result?.retval) return 0n;
1752
- return BigInt(stellarSdk.scValToNative(sim.result.retval));
1753
- }
1754
- /** Host function: SEP-41 `token.transfer(from=account, to, amount)` (device auth). */
1755
- buildTransfer(tokenId, accountAddress, destination, amount) {
1756
- return invokeFunc(tokenId, "transfer", [
1757
- new stellarSdk.Address(accountAddress).toScVal(),
1758
- new stellarSdk.Address(destination).toScVal(),
1759
- stellarSdk.nativeToScVal(amount, { type: "i128" })
1760
- ]);
2391
+ for (const [name, value] of Object.entries(toDataEntries(params.envelope))) {
2392
+ builder.addOperation(
2393
+ stellarSdk.Operation.manageData({ name, value: Buffer.from(value), source: params.masterAddress })
2394
+ );
2395
+ }
2396
+ builder.addOperation(
2397
+ stellarSdk.Operation.setOptions({
2398
+ source: params.masterAddress,
2399
+ masterWeight: 0,
2400
+ lowThreshold: 1,
2401
+ medThreshold: 1,
2402
+ highThreshold: 1,
2403
+ signer: { ed25519PublicKey: params.controlAddress, weight: 1 }
2404
+ })
2405
+ );
2406
+ return builder.setTimeout(TX_TIMEOUT).build();
1761
2407
  }
1762
2408
  /**
1763
- * Sign a Soroban authorization entry with the silent device key, producing the
1764
- * `Vec<DeviceSignature>` the account's `__check_auth` verifies. The device
1765
- * signs `sha256(preimage)` (WebCrypto hashes once more internally), which is
1766
- * exactly what the contract recomputes. Mutates + returns the entry.
2409
+ * Build a **sponsored** account-creation transaction whose source is the
2410
+ * relayer. Wraps the account setup in `beginSponsoringFutureReserves` /
2411
+ * `endSponsoringFutureReserves`, so the relayer (not the user) pays every
2412
+ * reserve the account is created with a 0 starting balance and holds no
2413
+ * locked XLM of the user's. Ops:
2414
+ * 0. beginSponsoringFutureReserves(G) source = relayer
2415
+ * 1. createAccount(G, 0) source = relayer
2416
+ * 2. manageData(cv:… envelope) source = G (master-signed)
2417
+ * 3. setOptions(control signer, master → 0) source = G
2418
+ * 4. endSponsoringFutureReserves() source = G
2419
+ *
2420
+ * Signed by the master (for the G ops, while it's still weight 1); the relayer
2421
+ * co-signs (source + fee + sponsorship) before submitting.
1767
2422
  */
1768
- async signAuthEntry(entry, validUntilLedger) {
1769
- const addrCreds = entry.credentials().address();
1770
- addrCreds.signatureExpirationLedger(validUntilLedger);
1771
- const preimage = stellarSdk.xdr.HashIdPreimage.envelopeTypeSorobanAuthorization(
1772
- new stellarSdk.xdr.HashIdPreimageSorobanAuthorization({
1773
- networkId: this.networkId(),
1774
- nonce: addrCreds.nonce(),
1775
- signatureExpirationLedger: validUntilLedger,
1776
- invocation: entry.rootInvocation()
2423
+ async buildSponsoredCreateTx(params) {
2424
+ const relayerAccount = await this.server().loadAccount(params.relayer);
2425
+ const builder = new stellarSdk.TransactionBuilder(relayerAccount, {
2426
+ fee: stellarSdk.BASE_FEE,
2427
+ networkPassphrase: this.passphrase
2428
+ });
2429
+ builder.addOperation(
2430
+ stellarSdk.Operation.beginSponsoringFutureReserves({ sponsoredId: params.masterAddress, source: params.relayer })
2431
+ );
2432
+ builder.addOperation(
2433
+ stellarSdk.Operation.createAccount({ destination: params.masterAddress, startingBalance: "0", source: params.relayer })
2434
+ );
2435
+ for (const [name, value] of Object.entries(toDataEntries(params.envelope))) {
2436
+ builder.addOperation(
2437
+ stellarSdk.Operation.manageData({ name, value: Buffer.from(value), source: params.masterAddress })
2438
+ );
2439
+ }
2440
+ builder.addOperation(
2441
+ stellarSdk.Operation.setOptions({
2442
+ source: params.masterAddress,
2443
+ masterWeight: 0,
2444
+ lowThreshold: 1,
2445
+ medThreshold: 1,
2446
+ highThreshold: 1,
2447
+ signer: { ed25519PublicKey: params.controlAddress, weight: 1 }
1777
2448
  })
1778
2449
  );
1779
- const payload = stellarSdk.hash(preimage.toXDR());
1780
- const sig = await this.signer.sign(new Uint8Array(payload));
1781
- const pubkey = await this.signer.getPublicKey();
1782
- addrCreds.signature(deviceSignatureScVal(pubkey, sig));
1783
- return entry;
2450
+ builder.addOperation(stellarSdk.Operation.endSponsoringFutureReserves({ source: params.masterAddress }));
2451
+ return builder.setTimeout(TX_TIMEOUT).build();
1784
2452
  }
1785
2453
  /**
1786
- * Read a SEP-41 token balance of `account` via a read-only simulation of
1787
- * `token.balance(account)`. Returns 0 when the account isn't deployed or holds
1788
- * none. `readSource` is any funded G-account (used only for the simulation).
2454
+ * Build a classic native-XLM payment as an *inner* transaction whose source is
2455
+ * the account itself (`G…`), signed by the control key. Wrap it in a fee-bump
2456
+ * (see `wrapFeeBump`) so the relayer pays the fee gasless.
1789
2457
  */
1790
- async readBalance(tokenId, account, readSource) {
1791
- if (!await this.isDeployed(account)) return 0n;
1792
- const { Account: Account3, TransactionBuilder: TransactionBuilder2, BASE_FEE: BASE_FEE2 } = await import('@stellar/stellar-sdk');
1793
- const src = new Account3(readSource, "0");
1794
- const op = stellarSdk.Operation.invokeHostFunction({
1795
- func: invokeFunc(tokenId, "balance", [new stellarSdk.Address(account).toScVal()]),
1796
- auth: []
1797
- });
1798
- const tx2 = new TransactionBuilder2(src, { fee: BASE_FEE2, networkPassphrase: this.passphrase }).addOperation(op).setTimeout(30).build();
1799
- const sim = await this.server().simulateTransaction(tx2);
1800
- if (stellarSdk.rpc.Api.isSimulationError(sim) || !sim.result?.retval) return 0n;
1801
- return BigInt(stellarSdk.scValToNative(sim.result.retval));
2458
+ async buildPaymentTx(params) {
2459
+ const source = await this.server().loadAccount(params.from);
2460
+ return new stellarSdk.TransactionBuilder(source, { fee: stellarSdk.BASE_FEE, networkPassphrase: this.passphrase }).addOperation(
2461
+ stellarSdk.Operation.payment({
2462
+ destination: params.to,
2463
+ asset: stellarSdk.Asset.native(),
2464
+ amount: fromStroops(params.amount)
2465
+ })
2466
+ ).setTimeout(TX_TIMEOUT).build();
1802
2467
  }
1803
- /** Whether the account contract instance exists on-chain (is deployed). */
1804
- async isDeployed(accountAddress) {
1805
- try {
1806
- const res = await this.server().getContractData(
1807
- accountAddress,
1808
- stellarSdk.xdr.ScVal.scvLedgerKeyContractInstance(),
1809
- stellarSdk.rpc.Durability.Persistent
1810
- );
1811
- return !!res;
1812
- } catch {
1813
- return false;
2468
+ /**
2469
+ * Build a data-entry write (e.g. re-wrapping the DEK for a newly approved
2470
+ * device) as an inner tx sourced by the account, signed by the control key.
2471
+ */
2472
+ async buildDataTx(params) {
2473
+ const source = await this.server().loadAccount(params.account);
2474
+ const builder = new stellarSdk.TransactionBuilder(source, { fee: stellarSdk.BASE_FEE, networkPassphrase: this.passphrase });
2475
+ for (const [name, value] of Object.entries(params.entries)) {
2476
+ builder.addOperation(stellarSdk.Operation.manageData({ name, value: Buffer.from(value) }));
1814
2477
  }
2478
+ return builder.setTimeout(TX_TIMEOUT).build();
1815
2479
  }
1816
2480
  /**
1817
- * Read whether `signer` is a currently-authorized signer of the account, via a
1818
- * read-only simulation of `account.is_authorized(signer)`. `readSource` is any
1819
- * funded G-account (used only for the simulation's source/sequence).
2481
+ * Build a **sponsored** data-entry write whose source is the relayer, so the
2482
+ * relayer (not the 0-balance account) pays the reserve of any NEW subentry.
2483
+ * Adding a factor (passkey / recovery) or a device slot creates a data entry
2484
+ * that needs ~0.5 XLM of reserve; a sponsored account holds no XLM, so — like
2485
+ * account creation — the write must be wrapped in begin/endSponsoringFuture
2486
+ * reserves with the relayer as sponsor. Ops:
2487
+ * 0. beginSponsoringFutureReserves(account) source = relayer
2488
+ * 1..n. manageData(cv:…) source = account (control-signed)
2489
+ * last. endSponsoringFutureReserves() source = account
2490
+ *
2491
+ * Signed by the control key (account ops) + the relayer (source + fee + sponsor).
1820
2492
  */
1821
- async isAuthorizedSigner(accountAddress, signer, readSource) {
1822
- if (!await this.isDeployed(accountAddress)) return false;
1823
- const { Account: Account3, TransactionBuilder: TransactionBuilder2, BASE_FEE: BASE_FEE2 } = await import('@stellar/stellar-sdk');
1824
- const src = new Account3(readSource, "0");
1825
- const op = stellarSdk.Operation.invokeHostFunction({
1826
- func: invokeFunc(accountAddress, "is_authorized", [bytesScVal(sec1Pubkey(signer))]),
1827
- auth: []
2493
+ async buildSponsoredDataTx(params) {
2494
+ const relayerAccount = await this.server().loadAccount(params.relayer);
2495
+ const builder = new stellarSdk.TransactionBuilder(relayerAccount, {
2496
+ fee: stellarSdk.BASE_FEE,
2497
+ networkPassphrase: this.passphrase
1828
2498
  });
1829
- const tx2 = new TransactionBuilder2(src, { fee: BASE_FEE2, networkPassphrase: this.passphrase }).addOperation(op).setTimeout(30).build();
1830
- const sim = await this.server().simulateTransaction(tx2);
1831
- if (stellarSdk.rpc.Api.isSimulationError(sim)) {
1832
- throw new Error(`kit/stellar: is_authorized simulation failed: ${sim.error}`);
2499
+ builder.addOperation(
2500
+ stellarSdk.Operation.beginSponsoringFutureReserves({ sponsoredId: params.account, source: params.relayer })
2501
+ );
2502
+ for (const [name, value] of Object.entries(params.entries)) {
2503
+ builder.addOperation(stellarSdk.Operation.manageData({ name, value: Buffer.from(value), source: params.account }));
2504
+ }
2505
+ builder.addOperation(stellarSdk.Operation.endSponsoringFutureReserves({ source: params.account }));
2506
+ return builder.setTimeout(TX_TIMEOUT).build();
2507
+ }
2508
+ /** Wrap a control-signed inner tx in a fee-bump whose fee source is `feeSource`
2509
+ * (the relayer). The inner tx pays nothing; the relayer pays all fees. */
2510
+ wrapFeeBump(inner, feeSource) {
2511
+ return stellarSdk.TransactionBuilder.buildFeeBumpTransaction(feeSource, stellarSdk.BASE_FEE, inner, this.passphrase);
2512
+ }
2513
+ /** Submit a fully-signed classic transaction and return its hash. Throws with
2514
+ * the Horizon result codes on failure. */
2515
+ async submit(tx3) {
2516
+ try {
2517
+ const res = await this.server().submitTransaction(tx3);
2518
+ return res.hash;
2519
+ } catch (e) {
2520
+ throw new Error(`kit/stellar: submit failed: ${horizonError(e)}`);
1833
2521
  }
1834
- if (!sim.result?.retval) return false;
1835
- return stellarSdk.scValToNative(sim.result.retval) === true;
2522
+ }
2523
+ /** Build an `Account` handle for a known address + sequence (avoids a Horizon
2524
+ * round-trip when the caller already has the sequence). */
2525
+ accountAt(address, sequence) {
2526
+ return new stellarSdk.Account(address, sequence);
1836
2527
  }
1837
2528
  };
1838
- function sec1Pubkey(pk) {
1839
- const out = new Uint8Array(65);
1840
- out[0] = 4;
1841
- out.set(bigIntTo32Bytes(pk.x), 1);
1842
- out.set(bigIntTo32Bytes(pk.y), 33);
1843
- return out;
2529
+ function isNotFound(e) {
2530
+ const status = e?.response?.status ?? e?.status;
2531
+ return status === 404;
1844
2532
  }
1845
- function encodeLowSSignature2(sig) {
1846
- const lowS2 = sig.s > SECP256R1_N2 / 2n ? SECP256R1_N2 - sig.s : sig.s;
1847
- const out = new Uint8Array(64);
1848
- out.set(bigIntTo32Bytes(sig.r), 0);
1849
- out.set(bigIntTo32Bytes(lowS2), 32);
1850
- return out;
2533
+ function horizonError(e) {
2534
+ const codes = e?.response?.data?.extras?.result_codes;
2535
+ return codes ? JSON.stringify(codes) : String(e?.message ?? e);
1851
2536
  }
1852
- function deviceSignatureScVal(pubkey, sig) {
1853
- const element = stellarSdk.nativeToScVal(
1854
- {
1855
- public_key: Buffer.from(sec1Pubkey(pubkey)),
1856
- signature: Buffer.from(encodeLowSSignature2(sig))
1857
- },
1858
- { type: { public_key: ["symbol", "bytes"], signature: ["symbol", "bytes"] } }
1859
- );
1860
- return stellarSdk.xdr.ScVal.scvVec([element]);
1861
- }
1862
- function invokeFunc(contractId, method, args) {
1863
- return stellarSdk.xdr.HostFunction.hostFunctionTypeInvokeContract(
1864
- new stellarSdk.xdr.InvokeContractArgs({
1865
- contractAddress: new stellarSdk.Address(contractId).toScAddress(),
1866
- functionName: method,
1867
- args
1868
- })
1869
- );
2537
+ function toStroops(amount) {
2538
+ const [whole, frac = ""] = amount.split(".");
2539
+ const fracPadded = (frac + "0000000").slice(0, 7);
2540
+ return BigInt(whole) * 10000000n + BigInt(fracPadded);
2541
+ }
2542
+ function fromStroops(stroops) {
2543
+ const neg = stroops < 0n;
2544
+ const abs = neg ? -stroops : stroops;
2545
+ const whole = abs / 10000000n;
2546
+ const frac = (abs % 10000000n).toString().padStart(7, "0");
2547
+ return `${neg ? "-" : ""}${whole}.${frac}`;
2548
+ }
2549
+ var MASTER_HKDF_INFO = "cavos-stellar-master";
2550
+ function deriveStellarMasterSeed(identity) {
2551
+ const ikm = deriveAddressSeedStellar(identity);
2552
+ return hkdf.hkdf(sha256.sha256, ikm, void 0, MASTER_HKDF_INFO, 32);
1870
2553
  }
1871
- function bytesScVal(bytes) {
1872
- return stellarSdk.xdr.ScVal.scvBytes(Buffer.from(bytes));
2554
+ function deriveStellarMasterKeypair(identity) {
2555
+ return stellarSdk.Keypair.fromRawEd25519Seed(Buffer.from(deriveStellarMasterSeed(identity)));
2556
+ }
2557
+ function deriveStellarAddress(identity) {
2558
+ return deriveStellarMasterKeypair(identity).publicKey();
2559
+ }
2560
+ function generateControlKey() {
2561
+ const seed = utils.randomBytes(32);
2562
+ return { keypair: stellarSdk.Keypair.fromRawEd25519Seed(Buffer.from(seed)), seed };
2563
+ }
2564
+ function controlKeypairFromSeed(seed) {
2565
+ if (seed.length !== 32) throw new Error("kit/stellar: control seed must be 32 bytes");
2566
+ return stellarSdk.Keypair.fromRawEd25519Seed(Buffer.from(seed));
1873
2567
  }
1874
2568
 
1875
2569
  // src/chains/stellar/StellarRelayer.ts
@@ -1877,7 +2571,7 @@ var StellarRelayer = class {
1877
2571
  constructor(opts) {
1878
2572
  this.opts = opts;
1879
2573
  }
1880
- /** The relayer's source/fee-payer G-account (fetched + cached from the backend). */
2574
+ /** The relayer's source/fee-payer/sponsor G-account (fetched + cached). */
1881
2575
  async getSource() {
1882
2576
  if (this.source) return this.source;
1883
2577
  const res = await fetch(`${this.opts.baseUrl}/api/stellar/relay?network=${this.opts.network}`);
@@ -1886,17 +2580,16 @@ var StellarRelayer = class {
1886
2580
  this.source = fee_payer;
1887
2581
  return this.source;
1888
2582
  }
1889
- /**
1890
- * POST the assembled, device-authorized transaction XDR to the relayer to sign
1891
- * the envelope + submit. Returns the confirmed transaction hash.
1892
- */
1893
- async submit(transactionXdr) {
2583
+ /** POST a (partially) signed transaction XDR for the relayer to co-sign + submit.
2584
+ * `kind` selects the validation gate. Returns the confirmed transaction hash. */
2585
+ async submit(kind, transactionXdr) {
1894
2586
  const res = await fetch(`${this.opts.baseUrl}/api/stellar/relay`, {
1895
2587
  method: "POST",
1896
2588
  headers: { "Content-Type": "application/json" },
1897
2589
  body: JSON.stringify({
1898
2590
  app_id: this.opts.appId,
1899
2591
  network: this.opts.network,
2592
+ kind,
1900
2593
  transaction: transactionXdr
1901
2594
  })
1902
2595
  });
@@ -1904,313 +2597,415 @@ var StellarRelayer = class {
1904
2597
  const detail = await res.text().catch(() => "");
1905
2598
  throw new Error(`kit/stellar: relay failed (${res.status}) ${detail}`);
1906
2599
  }
1907
- const { hash: hash6 } = await res.json();
1908
- return hash6;
2600
+ const { hash: hash5 } = await res.json();
2601
+ return hash5;
1909
2602
  }
1910
2603
  };
1911
2604
 
1912
2605
  // src/chains/stellar/CavosStellar.ts
2606
+ var DEFAULT_STARTING_BALANCE = 50000000n;
1913
2607
  var CavosStellar = class _CavosStellar {
1914
- constructor(identity, address, status, network, adapter, devicePubkey, relayer, sourceKeypair) {
2608
+ constructor(identity, address, status, network, adapter, deviceKey, control, dek, relayer) {
1915
2609
  this.identity = identity;
1916
2610
  this.address = address;
1917
- this.status = status;
1918
2611
  this.network = network;
1919
2612
  this.adapter = adapter;
1920
- this.devicePubkey = devicePubkey;
2613
+ this.deviceKey = deviceKey;
2614
+ this.control = control;
2615
+ this.dek = dek;
1921
2616
  this.relayer = relayer;
1922
- this.sourceKeypair = sourceKeypair;
1923
- /** Discriminant for the `CavosWallet` union narrows `execute()` per chain. */
2617
+ // Discriminant for the `CavosWallet` union. Classic `G…` IS the Stellar chain
2618
+ // now (the Soroban `C…` path was removed), so this is "stellar".
1924
2619
  this.chain = "stellar";
1925
- /** True when this connect just created a brand-new account (first sign-up). */
1926
2620
  this.isNewAccount = false;
2621
+ this.statusValue = status;
1927
2622
  }
1928
- get publicKey() {
1929
- return this.devicePubkey;
2623
+ get status() {
2624
+ return this.statusValue;
1930
2625
  }
1931
2626
  static async connect(opts) {
1932
2627
  const identity = opts.identity ?? await opts.auth?.authenticate();
1933
2628
  if (!identity) throw new Error("kit/stellar: connect requires `identity` or `auth`");
1934
- const signer = opts.createSigner ? await opts.createSigner(`${identity.userId}:${opts.appSalt}`) : await WebCryptoSigner.loadOrCreate({ keyId: `${identity.userId}:${opts.appSalt}` });
1935
- const devicePubkey = await signer.getPublicKey();
1936
- const adapter = new StellarAdapter({
1937
- network: opts.network,
1938
- rpcUrl: opts.rpcUrl,
1939
- factoryId: opts.factoryId,
1940
- signer
1941
- });
1942
- const addressSeed = deriveAddressSeedStellar({ userId: identity.userId, appSalt: opts.appSalt });
2629
+ const adapter = new StellarAdapter({ network: opts.network, horizonUrl: opts.horizonUrl });
2630
+ const master = deriveStellarMasterKeypair({ userId: identity.userId, appSalt: opts.appSalt });
2631
+ const address = master.publicKey();
2632
+ const startingBalance = opts.startingBalance ?? DEFAULT_STARTING_BALANCE;
1943
2633
  const backendUrl = opts.backendUrl ?? "https://cavos.xyz";
1944
- const registry = opts.registry ?? (opts.appId ? new HttpWalletRegistry({ baseUrl: backendUrl, appId: opts.appId, network: opts.network }) : defaultRegistry2);
1945
2634
  const relayer = opts.relayer ?? (opts.appId ? new StellarRelayer({ baseUrl: backendUrl, appId: opts.appId, network: opts.network }) : void 0);
1946
- const build = (address2, status) => new _CavosStellar(identity, address2, status, opts.network, adapter, devicePubkey, relayer, opts.sourceKeypair);
1947
- const self = build("", "needs-device-approval");
1948
- const readSource = await self.resolveSource();
1949
- const existing = await registry.lookup(identity.userId);
1950
- if (existing) {
1951
- const isSigner2 = await adapter.isAuthorizedSigner(existing.address, devicePubkey, readSource);
1952
- return build(existing.address, isSigner2 ? "ready" : "needs-device-approval");
1953
- }
1954
- const address = adapter.computeAddress(addressSeed, devicePubkey);
1955
- const wasDeployed = await adapter.isDeployed(address);
1956
- if (!wasDeployed) {
1957
- const func = adapter.buildDeploy(addressSeed, devicePubkey);
1958
- await self.submitHostFunction(func, void 0);
2635
+ const build = (status, unlocked) => new _CavosStellar(
2636
+ identity,
2637
+ address,
2638
+ status,
2639
+ opts.network,
2640
+ adapter,
2641
+ opts.deviceKey,
2642
+ unlocked?.control,
2643
+ unlocked?.dek,
2644
+ relayer
2645
+ );
2646
+ if (await adapter.isDeployed(address)) {
2647
+ const unlocked = await unlockViaDevice(adapter, address, opts.deviceKey);
2648
+ return build(unlocked ? "ready" : "needs-device-approval", unlocked ?? void 0);
2649
+ }
2650
+ if (!relayer && !opts.sourceKeypair) {
2651
+ throw new Error("kit/stellar: a relayer (appId) or sourceKeypair is required to create the account");
2652
+ }
2653
+ const { keypair: control, seed: controlSeed } = generateControlKey();
2654
+ const dek = generateDEK();
2655
+ const envelope = {
2656
+ ct: sealControlSeed(controlSeed, dek),
2657
+ deviceWraps: { [opts.deviceKey.slotId()]: eciesWrapDEK(dek, opts.deviceKey.publicKeySec1()) }
2658
+ };
2659
+ if (relayer) {
2660
+ const relayerSource = await relayer.getSource();
2661
+ const tx3 = await adapter.buildSponsoredCreateTx({
2662
+ relayer: relayerSource,
2663
+ masterAddress: address,
2664
+ controlAddress: control.publicKey(),
2665
+ envelope
2666
+ });
2667
+ tx3.sign(master);
2668
+ await relayer.submit("create", tx3.toXDR());
2669
+ } else {
2670
+ const funder = opts.sourceKeypair;
2671
+ const tx3 = await adapter.buildCreateTx({
2672
+ funder: funder.publicKey(),
2673
+ masterAddress: address,
2674
+ controlAddress: control.publicKey(),
2675
+ envelope,
2676
+ startingBalance
2677
+ });
2678
+ tx3.sign(master, funder);
2679
+ await adapter.submit(tx3);
1959
2680
  }
1960
- await registry.register({ userId: identity.userId, address, initialSigner: devicePubkey });
1961
- const isSigner = await adapter.isAuthorizedSigner(address, devicePubkey, readSource);
1962
- const wallet = build(address, isSigner ? "ready" : "needs-device-approval");
1963
- wallet.isNewAccount = !wasDeployed && isSigner;
2681
+ const wallet = build("ready", { control, dek });
2682
+ wallet.isNewAccount = true;
1964
2683
  return wallet;
1965
2684
  }
1966
- /** Authorize an additional device signer (device-signed via `__check_auth`). */
1967
- async addSigner(pubkey) {
1968
- const func = this.adapter.buildAddSigner(this.address, pubkey);
1969
- return this.submitHostFunction(func, this.address);
2685
+ /** Native XLM balance of the account, in stroops. */
2686
+ async balance() {
2687
+ return this.adapter.balance(this.address);
1970
2688
  }
1971
- /**
1972
- * Enroll a passkey as an approver (2FA-style step-up). Device-signed + gasless;
1973
- * requires a ready device. Idempotent. Returns the passkey pubkey + tx hash.
1974
- */
1975
- async enrollPasskey(passkey, params) {
1976
- const enrolled = await passkey.enroll(params);
1977
- const { transactionHash } = await this.addApprover(enrolled.publicKey);
1978
- return { publicKey: enrolled.publicKey, transactionHash };
1979
- }
1980
- /** Register an already-enrolled passkey pubkey as an approver (gasless).
1981
- * Idempotent. Lets one passkey be registered across chains without re-prompting. */
1982
- async addApprover(pubkey) {
1983
- if (this.status !== "ready") {
1984
- throw new Error("kit/stellar: addApprover requires a ready, authorized device");
1985
- }
1986
- const readSource = await this.resolveSource();
1987
- if (await this.adapter.isApprover(this.address, pubkey, readSource)) return {};
1988
- const func = this.adapter.buildAddApprover(this.address, pubkey);
1989
- const transactionHash = await this.submitHostFunction(func, this.address);
1990
- return { transactionHash };
1991
- }
1992
- /** True if this account already has a passkey enrolled as an approver, so a
1993
- * new device can be approved with the passkey instead of the email flow. */
2689
+ /** True if the account has a passkey factor enrolled (`cv:wp`), so a new device
2690
+ * can be approved with the passkey instead of a recovery code. Mirrors the
2691
+ * other chains' `hasPasskey()` for the React provider. */
1994
2692
  async hasPasskey() {
1995
- const readSource = await this.resolveSource();
1996
- return this.adapter.hasPasskeyApprover(this.address, readSource);
2693
+ try {
2694
+ const env = fromDataEntries(await this.adapter.loadDataEntries(this.address));
2695
+ return !!env.passkeyWrap;
2696
+ } catch {
2697
+ return false;
2698
+ }
1997
2699
  }
1998
- /** Re-read (from chain) whether THIS device is now an authorized signer.
1999
- * Used to poll for readiness after a passkey approval before it's indexed. */
2700
+ /** Whether the control key is unlocked on this device (status ready). Classic
2701
+ * approvals land synchronously via Horizon, so this reflects state immediately
2702
+ * (no indexing delay to poll for). */
2000
2703
  async isReady() {
2001
- const readSource = await this.resolveSource();
2002
- return this.adapter.isAuthorizedSigner(this.address, this.devicePubkey, readSource);
2704
+ return this.statusValue === "ready";
2003
2705
  }
2004
2706
  /**
2005
- * From a fresh browser (status `needs-device-approval`), approve adding THIS
2006
- * device using the user's synced passkey. Gasless via the relayer — the call
2007
- * carries the WebAuthn assertion, so no device signature is needed. Returns the
2008
- * tx hash. No trip back to an already-authorized device.
2707
+ * Move `amount` stroops of native XLM to `destination`, signed by the control
2708
+ * key. Sponsored by default (the relayer fee-bumps and pays the fee); pass
2709
+ * `{ sponsored: false }` to submit directly the account pays its own (tiny)
2710
+ * fee from its XLM balance. The control key signs identically in both modes;
2711
+ * only the fee payer differs.
2009
2712
  */
2010
- async approveThisDeviceWithPasskey(passkey) {
2011
- if (this.status === "ready") {
2012
- throw new Error("kit/stellar: this device is already an authorized signer");
2013
- }
2014
- const { leaf, nonce } = await this.passkeyLeafForThisDevice();
2015
- const leaves = [leaf];
2016
- const assertion = await passkey.assert(batchChallenge(leaves));
2017
- const { transactionHash } = await this.submitPasskeyApproval(assertion, leaves, 0, nonce);
2018
- return transactionHash;
2713
+ async execute(amount, destination, opts) {
2714
+ const control = this.requireControl();
2715
+ const inner = await this.adapter.buildPaymentTx({ from: this.address, to: destination, amount });
2716
+ return this.submitInner(inner, control, opts);
2019
2717
  }
2020
- /** This device's leaf + passkey nonce for a (possibly multi-chain) batch. */
2021
- async passkeyLeafForThisDevice() {
2022
- const readSource = await this.resolveSource();
2023
- const nonce = await this.adapter.passkeyNonce(this.address, readSource);
2024
- return { leaf: this.adapter.passkeyLeaf(this.devicePubkey, nonce), nonce };
2718
+ /**
2719
+ * Enroll a passkey as an unlock factor: wrap the DEK under the passkey's PRF
2720
+ * output and write the `cv:wp` entry. This is the synced anchor used to approve
2721
+ * a new device or recover — it survives device loss. Idempotent-ish: writing it
2722
+ * again just overwrites the wrap of the same DEK. Requires a ready device.
2723
+ */
2724
+ async enrollPasskey(prfOutput) {
2725
+ const { control, dek } = this.requireUnlocked();
2726
+ const wrap = wrapDEK(dek, derivePasskeyKEK(prfOutput));
2727
+ return this.writeFactor(PASSKEY_BASE, wrap, control);
2025
2728
  }
2026
- /** Submit `add_signer_via_passkey` given a shared assertion + batch position.
2027
- * No device auth entry authorized purely by the passkey assertion. */
2028
- async submitPasskeyApproval(assertion, leaves, leafIndex, nonce) {
2029
- const readSource = await this.resolveSource();
2030
- const digest = webauthnDigest(assertion.authenticatorData, assertion.clientDataJSON);
2031
- const candidates = recoverCandidatePublicKeys(assertion.r, assertion.s, digest);
2032
- let approver = null;
2033
- for (const cand of candidates) {
2034
- if (await this.adapter.isApprover(this.address, cand.publicKey, readSource)) {
2035
- approver = cand.publicKey;
2036
- break;
2037
- }
2038
- }
2039
- if (!approver) throw new Error("kit/stellar: this passkey is not a registered approver");
2040
- const func = this.adapter.buildAddSignerViaPasskey(
2041
- this.address,
2042
- this.devicePubkey,
2043
- approver,
2044
- nonce,
2045
- leaves,
2046
- leafIndex,
2047
- assertion
2048
- );
2049
- return { transactionHash: await this.submitHostFunction(func, void 0) };
2729
+ /**
2730
+ * Set up a recovery code as an unlock factor: wrap the DEK under the code's KEK
2731
+ * and write the `cv:wr` entry. Optional in v1 — the integrating app decides when
2732
+ * to surface it. The code never leaves the device; only the wrap goes on-chain.
2733
+ * Requires a ready device.
2734
+ */
2735
+ async setupRecovery(code) {
2736
+ const { control, dek } = this.requireUnlocked();
2737
+ const wrap = wrapDEK(dek, deriveRecoveryKEK(code));
2738
+ return this.writeFactor(RECOVERY_BASE, wrap, control);
2050
2739
  }
2051
- /** Move `amount` stroops of native XLM to `destination` (device-signed). */
2052
- async execute(amount, destination) {
2053
- return this.executeTransfer(NATIVE_SAC_ID[this.network], amount, destination);
2740
+ /**
2741
+ * From a new browser/device (`needs-device-approval`), approve THIS device using
2742
+ * the user's synced passkey: unlock the DEK via the passkey factor, then wrap it
2743
+ * to this device's slot so future sessions unlock silently. Flips status to
2744
+ * `ready`. No trip back to an already-authorized device.
2745
+ */
2746
+ async approveThisDeviceWithPasskey(prfOutput) {
2747
+ return this.approveThisDevice(
2748
+ await unlockViaPasskey(this.adapter, this.address, prfOutput),
2749
+ "passkey"
2750
+ );
2054
2751
  }
2055
- /** Read this account's balance of `tokenId` (defaults to native XLM), in stroops. */
2056
- async balance(tokenId = NATIVE_SAC_ID[this.network]) {
2057
- const readSource = await this.resolveSource();
2058
- return this.adapter.readBalance(tokenId, this.address, readSource);
2752
+ /** Approve THIS device using the recovery code (same as the passkey path, for
2753
+ * the backup factor). */
2754
+ async approveThisDeviceWithRecovery(code) {
2755
+ return this.approveThisDevice(
2756
+ await unlockViaRecovery(this.adapter, this.address, code),
2757
+ "recovery code"
2758
+ );
2059
2759
  }
2060
- /** Transfer `amount` of any SEP-41 token out of the account (device-signed). */
2061
- async executeTransfer(tokenId, amount, destination) {
2062
- if (this.status !== "ready") {
2063
- throw new Error("kit/stellar: this device is not yet an authorized signer of the wallet");
2064
- }
2065
- const func = this.adapter.buildTransfer(tokenId, this.address, destination, amount);
2066
- return this.submitHostFunction(func, this.address);
2760
+ /** The control key's public G address (the weight-1 real signer), for display. */
2761
+ get controlAddress() {
2762
+ return this.control?.publicKey();
2763
+ }
2764
+ // --- internals ----------------------------------------------------------
2765
+ async approveThisDevice(unlocked, factor) {
2766
+ if (this.statusValue === "ready") {
2767
+ throw new Error("kit/stellar: this device is already authorized");
2768
+ }
2769
+ if (!unlocked) {
2770
+ throw new Error(`kit/stellar: could not unlock the account with the ${factor} \u2014 wrong factor or not enrolled`);
2771
+ }
2772
+ const slot = this.deviceKey.slotId();
2773
+ const wrap = eciesWrapDEK(unlocked.dek, this.deviceKey.publicKeySec1());
2774
+ const hash5 = await this.submitDataWrite(deviceWrapEntries(slot, wrap), unlocked.control);
2775
+ this.control = unlocked.control;
2776
+ this.dek = unlocked.dek;
2777
+ this.statusValue = "ready";
2778
+ return hash5;
2779
+ }
2780
+ /** Write a single-factor wrap (passkey/recovery) into the account data entries,
2781
+ * signed by the control key. Overwrites cleanly if the base already existed and
2782
+ * the new blob has the same chunk count. */
2783
+ async writeFactor(base, wrap, control) {
2784
+ const entries = {};
2785
+ chunkTo64(wrap).forEach((chunk, i) => {
2786
+ entries[`${base}/${i}`] = chunk;
2787
+ });
2788
+ return this.submitDataWrite(entries, control);
2067
2789
  }
2068
2790
  /**
2069
- * Register the backup signer derived from `code` as an authorized signer of
2070
- * this account (device-signed). Idempotent. The code never leaves the device
2071
- * only the derived public key travels on-chain. Mirrors the other chains.
2791
+ * Sign an inner (account-sourced) payment tx with the control key and submit it:
2792
+ * - sponsored (default) with a relayer, wrap in a fee-bump (relayer pays
2793
+ * the fee) and POST; falls back to self-funded if no relayer;
2794
+ * - `{ sponsored: false }` → submit directly (the account pays its own fee).
2795
+ * Payments add no subentries, so no reserve sponsorship is needed here.
2072
2796
  */
2073
- async setupRecovery(code) {
2074
- if (this.status !== "ready") {
2075
- throw new Error("kit/stellar: setupRecovery requires a ready, registered device");
2797
+ async submitInner(inner, control, opts) {
2798
+ inner.sign(control);
2799
+ const sponsored = opts?.sponsored !== false;
2800
+ if (sponsored && this.relayer) {
2801
+ const feeSource = await this.relayer.getSource();
2802
+ const bump = this.adapter.wrapFeeBump(inner, feeSource);
2803
+ return this.relayer.submit("fee-bump", bump.toXDR());
2076
2804
  }
2077
- const { publicKey: backupPubkey } = deriveBackupKey(code);
2078
- const readSource = await this.resolveSource();
2079
- if (await this.adapter.isAuthorizedSigner(this.address, backupPubkey, readSource)) return void 0;
2080
- return this.addSigner(backupPubkey);
2805
+ return this.adapter.submit(inner);
2081
2806
  }
2082
2807
  /**
2083
- * Recover an account after losing every device signer: derive the backup key
2084
- * from `code`, use it (not the new device) to authorize `add_signer(newDevice)`,
2085
- * and return a ready handle bound to the new device. The address is unchanged.
2808
+ * Write data entries (add a factor / device slot) which create NEW subentries
2809
+ * that each need ~0.5 XLM of reserve. A relayer-sponsored account holds no XLM,
2810
+ * so the write must be sponsored by the relayer (source + sponsor), exactly like
2811
+ * account creation — a plain fee-bump would fail with `op_low_reserve`.
2812
+ * - sponsored (default) → with a relayer, build a sponsored write (relayer
2813
+ * source + begin/end sponsoring), control-sign the account ops, relay
2814
+ * co-signs + submits; falls back to self-funded if no relayer;
2815
+ * - `{ sponsored: false }` → the account writes directly (it must hold its
2816
+ * own reserve for the new subentries).
2086
2817
  */
2087
- static async recover(opts) {
2088
- const signer = opts.createSigner ? await opts.createSigner(`${opts.identity.userId}:${opts.appSalt}`) : await WebCryptoSigner.loadOrCreate({ keyId: `${opts.identity.userId}:${opts.appSalt}` });
2089
- const devicePubkey = await signer.getPublicKey();
2090
- const backup = BackupSigner.fromCode(opts.code);
2091
- const backupAdapter = new StellarAdapter({
2092
- network: opts.network,
2093
- rpcUrl: opts.rpcUrl,
2094
- factoryId: opts.factoryId,
2095
- signer: backup
2096
- });
2097
- const backendUrl = opts.backendUrl ?? "https://cavos.xyz";
2098
- const registry = opts.registry ?? (opts.appId ? new HttpWalletRegistry({ baseUrl: backendUrl, appId: opts.appId, network: opts.network }) : defaultRegistry2);
2099
- const existing = await registry.lookup(opts.identity.userId);
2100
- if (!existing) {
2101
- throw new Error("kit/stellar: no account found for this identity \u2014 nothing to recover");
2818
+ async submitDataWrite(entries, control, opts) {
2819
+ const sponsored = opts?.sponsored !== false;
2820
+ if (sponsored && this.relayer) {
2821
+ const relayerSource = await this.relayer.getSource();
2822
+ const tx4 = await this.adapter.buildSponsoredDataTx({
2823
+ relayer: relayerSource,
2824
+ account: this.address,
2825
+ entries
2826
+ });
2827
+ tx4.sign(control);
2828
+ return this.relayer.submit("sponsored-data", tx4.toXDR());
2102
2829
  }
2103
- const relayer = opts.relayer ?? (opts.appId ? new StellarRelayer({ baseUrl: backendUrl, appId: opts.appId, network: opts.network }) : void 0);
2104
- const backupHandle = new _CavosStellar(
2105
- opts.identity,
2106
- existing.address,
2107
- "ready",
2108
- opts.network,
2109
- backupAdapter,
2110
- devicePubkey,
2111
- relayer,
2112
- opts.sourceKeypair
2113
- );
2114
- const readSource = await backupHandle.resolveSource();
2115
- if (!await backupAdapter.isAuthorizedSigner(existing.address, devicePubkey, readSource)) {
2116
- await backupHandle.addSigner(devicePubkey);
2830
+ const tx3 = await this.adapter.buildDataTx({ account: this.address, entries });
2831
+ tx3.sign(control);
2832
+ return this.adapter.submit(tx3);
2833
+ }
2834
+ requireControl() {
2835
+ if (this.statusValue !== "ready" || !this.control) {
2836
+ throw new Error("kit/stellar: control key not unlocked on this device (needs approval)");
2117
2837
  }
2118
- const adapter = new StellarAdapter({
2119
- network: opts.network,
2120
- rpcUrl: opts.rpcUrl,
2121
- factoryId: opts.factoryId,
2122
- signer
2123
- });
2124
- return new _CavosStellar(
2125
- opts.identity,
2126
- existing.address,
2127
- "ready",
2128
- opts.network,
2129
- adapter,
2130
- devicePubkey,
2131
- relayer,
2132
- opts.sourceKeypair
2838
+ return this.control;
2839
+ }
2840
+ requireUnlocked() {
2841
+ const control = this.requireControl();
2842
+ if (!this.dek) throw new Error("kit/stellar: DEK unavailable on this device");
2843
+ return { control, dek: this.dek };
2844
+ }
2845
+ };
2846
+ async function unlockViaDevice(adapter, address, deviceKey) {
2847
+ const env = await loadEnvelope(adapter, address);
2848
+ const wrap = env.deviceWraps[deviceKey.slotId()];
2849
+ if (!wrap) return null;
2850
+ try {
2851
+ const dek = await deviceKey.unwrap(wrap);
2852
+ return openControl(env, dek);
2853
+ } catch {
2854
+ return null;
2855
+ }
2856
+ }
2857
+ async function unlockViaPasskey(adapter, address, prfOutput) {
2858
+ const env = await loadEnvelope(adapter, address);
2859
+ if (!env.passkeyWrap) return null;
2860
+ try {
2861
+ const dek = unwrapDEK(env.passkeyWrap, derivePasskeyKEK(prfOutput));
2862
+ return openControl(env, dek);
2863
+ } catch {
2864
+ return null;
2865
+ }
2866
+ }
2867
+ async function unlockViaRecovery(adapter, address, code) {
2868
+ const env = await loadEnvelope(adapter, address);
2869
+ if (!env.recoveryWrap) return null;
2870
+ try {
2871
+ const dek = unwrapDEK(env.recoveryWrap, deriveRecoveryKEK(code));
2872
+ return openControl(env, dek);
2873
+ } catch {
2874
+ return null;
2875
+ }
2876
+ }
2877
+ async function loadEnvelope(adapter, address) {
2878
+ return fromDataEntries(await adapter.loadDataEntries(address));
2879
+ }
2880
+ function openControl(env, dek) {
2881
+ const controlSeed = openControlSeed(env.ct, dek);
2882
+ return { control: controlKeypairFromSeed(controlSeed), dek };
2883
+ }
2884
+ var LocalDeviceUnwrapKey = class _LocalDeviceUnwrapKey {
2885
+ constructor(scalar) {
2886
+ this.scalar = scalar;
2887
+ }
2888
+ /** Generate a fresh device unwrap key. */
2889
+ static generate() {
2890
+ return new _LocalDeviceUnwrapKey(p256.p256.utils.randomPrivateKey());
2891
+ }
2892
+ /** Rebuild from a persisted 32-byte scalar. */
2893
+ static fromScalar(scalar) {
2894
+ if (scalar.length !== 32) throw new Error("kit/stellar: device unwrap scalar must be 32 bytes");
2895
+ return new _LocalDeviceUnwrapKey(scalar);
2896
+ }
2897
+ /** The raw scalar, for the caller to persist in secure storage. */
2898
+ export() {
2899
+ return this.scalar;
2900
+ }
2901
+ publicKeySec1() {
2902
+ return p256.p256.getPublicKey(this.scalar, false);
2903
+ }
2904
+ slotId() {
2905
+ return deviceSlotId(this.publicKeySec1());
2906
+ }
2907
+ async unwrap(blob) {
2908
+ return eciesUnwrapDEK(blob, this.scalar);
2909
+ }
2910
+ };
2911
+ function deviceSlotId(publicKeySec1) {
2912
+ const h = sha256.sha256(publicKeySec1);
2913
+ let s = "";
2914
+ for (const b of h.subarray(0, 4)) s += b.toString(16).padStart(2, "0");
2915
+ return s;
2916
+ }
2917
+
2918
+ // src/chains/stellar/WebCryptoDeviceUnwrapKey.ts
2919
+ var IDB_NAME2 = "cavos-kit-stellar";
2920
+ var IDB_STORE2 = "unwrap-keys";
2921
+ var WebCryptoDeviceUnwrapKey = class _WebCryptoDeviceUnwrapKey {
2922
+ constructor(privateKey, publicRaw, keyId) {
2923
+ this.privateKey = privateKey;
2924
+ this.publicRaw = publicRaw;
2925
+ this.keyId = keyId;
2926
+ }
2927
+ /** Create a fresh device unwrap key and persist it. */
2928
+ static async create(opts) {
2929
+ assertSecureContext2();
2930
+ const pair = await crypto.subtle.generateKey(
2931
+ { name: "ECDH", namedCurve: "P-256" },
2932
+ false,
2933
+ // private key is NON-extractable
2934
+ ["deriveBits"]
2133
2935
  );
2936
+ const publicRaw = new Uint8Array(await crypto.subtle.exportKey("raw", pair.publicKey));
2937
+ await idbPut2(opts.keyId, { privateKey: pair.privateKey, publicRaw });
2938
+ return new _WebCryptoDeviceUnwrapKey(pair.privateKey, publicRaw, opts.keyId);
2134
2939
  }
2135
- /** The transaction source/fee-payer G-address (relayer or self-funded). */
2136
- async resolveSource() {
2137
- if (this.relayer) return this.relayer.getSource();
2138
- if (this.sourceKeypair) return this.sourceKeypair.publicKey();
2139
- throw new Error("kit/stellar: a relayer (appId) or sourceKeypair is required");
2940
+ /** Load an existing device unwrap key, or null if none exists yet. */
2941
+ static async load(opts) {
2942
+ const rec = await idbGet2(opts.keyId);
2943
+ if (!rec) return null;
2944
+ return new _WebCryptoDeviceUnwrapKey(rec.privateKey, rec.publicRaw, opts.keyId);
2140
2945
  }
2141
- /**
2142
- * Build simulate → device-sign auth → assemble → submit an invoke-contract
2143
- * host function. `authAccount` is the account whose `__check_auth` must sign the
2144
- * operation's Soroban auth entry (undefined for a plain factory deploy).
2145
- */
2146
- async submitHostFunction(func, authAccount) {
2147
- const server = this.adapter.server();
2148
- const sourceAddr = await this.resolveSource();
2149
- const simSource = new stellarSdk.Account(sourceAddr, "0");
2150
- const unsignedOp = stellarSdk.Operation.invokeHostFunction({ func, auth: [] });
2151
- const simTx = new stellarSdk.TransactionBuilder(simSource, {
2152
- fee: stellarSdk.BASE_FEE,
2153
- networkPassphrase: this.adapter.passphrase
2154
- }).addOperation(unsignedOp).setTimeout(180).build();
2155
- const sim = await server.simulateTransaction(simTx);
2156
- if (stellarSdk.rpc.Api.isSimulationError(sim)) {
2157
- throw new Error(`kit/stellar: simulation failed: ${sim.error}`);
2158
- }
2159
- const validUntil = (await server.getLatestLedger()).sequence + 100;
2160
- const entries = sim.result?.auth ?? [];
2161
- const signedAuth = [];
2162
- for (const entry of entries) {
2163
- if (authAccount && isAddressCredentialFor(entry, authAccount)) {
2164
- signedAuth.push(await this.adapter.signAuthEntry(entry, validUntil));
2165
- } else {
2166
- signedAuth.push(entry);
2167
- }
2168
- }
2169
- const account = await server.getAccount(sourceAddr);
2170
- const finalOp = stellarSdk.Operation.invokeHostFunction({ func, auth: signedAuth });
2171
- const built = new stellarSdk.TransactionBuilder(account, {
2172
- fee: stellarSdk.BASE_FEE,
2173
- networkPassphrase: this.adapter.passphrase
2174
- }).addOperation(finalOp).setTimeout(180).build();
2175
- const authSim = await server.simulateTransaction(built);
2176
- if (stellarSdk.rpc.Api.isSimulationError(authSim)) {
2177
- throw new Error(`kit/stellar: auth simulation failed: ${authSim.error}`);
2178
- }
2179
- const assembled = stellarSdk.rpc.assembleTransaction(built, authSim).build();
2180
- if (this.relayer) {
2181
- return this.relayer.submit(assembled.toXDR());
2182
- }
2183
- if (this.sourceKeypair) {
2184
- assembled.sign(this.sourceKeypair);
2185
- return this.sendAndConfirm(assembled);
2186
- }
2187
- throw new Error("kit/stellar: no relayer or sourceKeypair configured to submit");
2188
- }
2189
- /** Submit a signed tx via RPC and poll to confirmation. Returns the hash. */
2190
- async sendAndConfirm(tx2) {
2191
- const server = this.adapter.server();
2192
- const sent = await server.sendTransaction(tx2);
2193
- if (sent.status === "ERROR") {
2194
- throw new Error(`kit/stellar: submit rejected: ${JSON.stringify(sent.errorResult)}`);
2195
- }
2196
- const hash6 = sent.hash;
2197
- for (let i = 0; i < 30; i++) {
2198
- const got = await server.getTransaction(hash6);
2199
- if (got.status === stellarSdk.rpc.Api.GetTransactionStatus.SUCCESS) return hash6;
2200
- if (got.status === stellarSdk.rpc.Api.GetTransactionStatus.FAILED) {
2201
- throw new Error(`kit/stellar: tx ${hash6} failed`);
2202
- }
2203
- await new Promise((r) => setTimeout(r, 1e3));
2204
- }
2205
- throw new Error(`kit/stellar: tx ${hash6} not confirmed in time`);
2946
+ /** Load the device unwrap key, creating one on first use. */
2947
+ static async loadOrCreate(opts) {
2948
+ return await _WebCryptoDeviceUnwrapKey.load(opts) ?? await _WebCryptoDeviceUnwrapKey.create(opts);
2949
+ }
2950
+ publicKeySec1() {
2951
+ return this.publicRaw;
2952
+ }
2953
+ slotId() {
2954
+ return deviceSlotId(this.publicRaw);
2955
+ }
2956
+ async unwrap(blob) {
2957
+ const ephPubCompressed = blob.subarray(0, 33);
2958
+ const wrapped = blob.subarray(33);
2959
+ const ephUncompressed = p256.p256.ProjectivePoint.fromHex(ephPubCompressed).toRawBytes(false);
2960
+ const ephKey = await crypto.subtle.importKey(
2961
+ "raw",
2962
+ ephUncompressed,
2963
+ { name: "ECDH", namedCurve: "P-256" },
2964
+ false,
2965
+ []
2966
+ );
2967
+ const sharedX = new Uint8Array(
2968
+ await crypto.subtle.deriveBits({ name: "ECDH", public: ephKey }, this.privateKey, 256)
2969
+ );
2970
+ const kek = eciesKEKFromX(sharedX, ephPubCompressed);
2971
+ return unwrapDEK(wrapped, kek);
2206
2972
  }
2207
2973
  };
2208
- function isAddressCredentialFor(entry, accountAddress) {
2209
- const creds = entry.credentials();
2210
- if (creds.switch() !== stellarSdk.xdr.SorobanCredentialsType.sorobanCredentialsAddress()) return false;
2211
- return stellarSdk.Address.fromScAddress(creds.address().address()).toString() === accountAddress;
2974
+ function assertSecureContext2() {
2975
+ const ok = typeof crypto !== "undefined" && typeof crypto.subtle !== "undefined" && (typeof window === "undefined" || window.isSecureContext);
2976
+ if (!ok) {
2977
+ throw new Error(
2978
+ "Cavos: WebCrypto is unavailable. Device keys require a secure context \u2014 use HTTPS, or http://localhost."
2979
+ );
2980
+ }
2981
+ }
2982
+ function openDb2() {
2983
+ return new Promise((resolve, reject) => {
2984
+ const req = indexedDB.open(IDB_NAME2, 1);
2985
+ req.onupgradeneeded = () => req.result.createObjectStore(IDB_STORE2);
2986
+ req.onsuccess = () => resolve(req.result);
2987
+ req.onerror = () => reject(req.error);
2988
+ });
2989
+ }
2990
+ async function idbPut2(keyId, value) {
2991
+ const db = await openDb2();
2992
+ await tx2(db, "readwrite", (store) => store.put(value, keyId));
2993
+ db.close();
2994
+ }
2995
+ async function idbGet2(keyId) {
2996
+ const db = await openDb2();
2997
+ const result = await tx2(db, "readonly", (store) => store.get(keyId));
2998
+ db.close();
2999
+ return result ?? null;
3000
+ }
3001
+ function tx2(db, mode, run) {
3002
+ return new Promise((resolve, reject) => {
3003
+ const store = db.transaction(IDB_STORE2, mode).objectStore(IDB_STORE2);
3004
+ const req = run(store);
3005
+ req.onsuccess = () => resolve(req.result);
3006
+ req.onerror = () => reject(req.error);
3007
+ });
2212
3008
  }
2213
- var defaultRegistry2 = new InMemoryWalletRegistry();
2214
3009
 
2215
3010
  // src/recovery/HttpRecoveryClient.ts
2216
3011
  function toHex2(n) {
@@ -2342,17 +3137,16 @@ var Cavos = class _Cavos {
2342
3137
  });
2343
3138
  }
2344
3139
  if (opts.chain === "stellar") {
3140
+ const identity = opts.identity ?? (opts.auth ? await opts.auth.authenticate() : void 0);
3141
+ if (!identity) throw new Error("kit: Stellar connect requires `identity` or `auth`");
3142
+ const deviceKey = opts.stellarDeviceKey ?? await WebCryptoDeviceUnwrapKey.loadOrCreate({ keyId: `${identity.userId}:${opts.appSalt}` });
2345
3143
  return CavosStellar.connect({
2346
3144
  network: STELLAR_ENV[opts.network],
2347
- ...opts.auth ? { auth: opts.auth } : {},
2348
- ...opts.identity ? { identity: opts.identity } : {},
3145
+ identity,
2349
3146
  appSalt: opts.appSalt,
3147
+ deviceKey,
2350
3148
  ...opts.appId ? { appId: opts.appId } : {},
2351
3149
  ...opts.backendUrl ? { backendUrl: opts.backendUrl } : {},
2352
- ...opts.registry ? { registry: opts.registry } : {},
2353
- ...opts.rpcUrl ? { rpcUrl: opts.rpcUrl } : {},
2354
- ...opts.factoryId ? { factoryId: opts.factoryId } : {},
2355
- ...opts.createSigner ? { createSigner: opts.createSigner } : {},
2356
3150
  ...opts.stellarRelayer ? { relayer: opts.stellarRelayer } : {},
2357
3151
  ...opts.stellarSourceKeypair ? { sourceKeypair: opts.stellarSourceKeypair } : {}
2358
3152
  });
@@ -2402,7 +3196,7 @@ var Cavos = class _Cavos {
2402
3196
  cairoVersion: "1"
2403
3197
  });
2404
3198
  const backendUrl = opts.backendUrl ?? "https://cavos.xyz";
2405
- const registry = opts.registry ?? (opts.appId ? new HttpWalletRegistry({ baseUrl: backendUrl, appId: opts.appId, network: opts.network }) : defaultRegistry3);
3199
+ const registry = opts.registry ?? (opts.appId ? new HttpWalletRegistry({ baseUrl: backendUrl, appId: opts.appId, network: opts.network }) : defaultRegistry2);
2406
3200
  const recovery = opts.recovery ?? (opts.appId ? new HttpRecoveryClient({ baseUrl: backendUrl, appId: opts.appId }) : null);
2407
3201
  const existing = await registry.lookup(identity.userId);
2408
3202
  if (existing) {
@@ -2439,7 +3233,7 @@ var Cavos = class _Cavos {
2439
3233
  }
2440
3234
  return cavos2;
2441
3235
  }
2442
- const address = adapter.computeAddress({ addressSeed, initialSigner: devicePubkey });
3236
+ const address = adapter.computeAddress({ addressSeed });
2443
3237
  const account = makeAccount(address);
2444
3238
  const alreadyDeployed = await isDeployed(provider, address);
2445
3239
  if (!alreadyDeployed) {
@@ -2447,10 +3241,11 @@ var Cavos = class _Cavos {
2447
3241
  address,
2448
3242
  class_hash: classHash,
2449
3243
  salt: starknet.num.toHex(addressSeed),
2450
- calldata: adapter.constructorCalldata(addressSeed, devicePubkey),
3244
+ calldata: adapter.constructorCalldata(addressSeed),
2451
3245
  version: 1
2452
3246
  };
2453
- const deployRes = await account.executePaymasterTransaction([], {
3247
+ const initCall = adapter.buildInitialize(address, devicePubkey);
3248
+ const deployRes = await account.executePaymasterTransaction([initCall], {
2454
3249
  feeMode: { mode: "sponsored" },
2455
3250
  deploymentData
2456
3251
  });
@@ -2485,18 +3280,25 @@ var Cavos = class _Cavos {
2485
3280
  return this.devicePubkey;
2486
3281
  }
2487
3282
  /** Execute a sponsored (gasless) multicall, signed silently by the device. */
2488
- async execute(calls) {
3283
+ async execute(calls, opts) {
2489
3284
  if (this.status !== "ready") {
2490
3285
  throw new Error("kit: this device is not yet an authorized signer of the wallet");
2491
3286
  }
3287
+ if (opts?.sponsored === false) {
3288
+ const res2 = await this.account.execute(calls);
3289
+ return { transactionHash: res2.transaction_hash };
3290
+ }
2492
3291
  const res = await this.account.executePaymasterTransaction(calls, {
2493
3292
  feeMode: { mode: "sponsored" }
2494
3293
  });
2495
3294
  return { transactionHash: res.transaction_hash };
2496
3295
  }
2497
- /** Authorize an additional device signer (sponsored). Self-submitted. */
2498
- async addSigner(pubkey) {
2499
- return this.execute([this.adapter.buildAddSigner(this.address, pubkey)]);
3296
+ /**
3297
+ * Authorize an additional device signer. Sponsored by default; pass
3298
+ * `{ sponsored: false }` to pay the fee from the account's own ETH balance.
3299
+ */
3300
+ async addSigner(pubkey, opts) {
3301
+ return this.execute([this.adapter.buildAddSigner(this.address, pubkey)], opts);
2500
3302
  }
2501
3303
  /**
2502
3304
  * Enroll a passkey as an APPROVER so the user can later add devices from any
@@ -2505,25 +3307,27 @@ var Cavos = class _Cavos {
2505
3307
  * approver. Call this whenever the app decides to prompt "turn on device
2506
3308
  * approvals". Returns the passkey's public key + the enrollment tx hash.
2507
3309
  */
2508
- async enrollPasskey(passkey, params) {
3310
+ async enrollPasskey(passkey, params, opts) {
2509
3311
  const enrolled = await passkey.enroll(params);
2510
- const { transactionHash } = await this.addApprover(enrolled.publicKey);
3312
+ const { transactionHash } = await this.addApprover(enrolled.publicKey, opts);
2511
3313
  return { publicKey: enrolled.publicKey, transactionHash };
2512
3314
  }
2513
3315
  /**
2514
- * Register an ALREADY-enrolled passkey public key as an approver (gasless,
2515
- * device-signed). Idempotent. Use this to register ONE passkey across multiple
2516
- * chains without re-prompting `passkey.enroll()` on each: enroll once, then
2517
- * call `addApprover(pubkey)` on each chain's wallet.
3316
+ * Register an ALREADY-enrolled passkey public key as an approver (gasless by
3317
+ * default, device-signed). Idempotent. Use this to register ONE passkey across
3318
+ * multiple chains without re-prompting `passkey.enroll()` on each: enroll once,
3319
+ * then call `addApprover(pubkey)` on each chain's wallet. Pass
3320
+ * `{ sponsored: false }` to pay the fee from the account's own balance.
2518
3321
  */
2519
- async addApprover(pubkey) {
3322
+ async addApprover(pubkey, opts) {
2520
3323
  if (this.status !== "ready") {
2521
3324
  throw new Error("kit: addApprover requires a ready, authorized device");
2522
3325
  }
2523
3326
  if (await this.adapter.isApprover(this.address, pubkey)) return {};
2524
- const { transactionHash } = await this.execute([
2525
- this.adapter.buildAddApprover(this.address, pubkey)
2526
- ]);
3327
+ const { transactionHash } = await this.execute(
3328
+ [this.adapter.buildAddApprover(this.address, pubkey)],
3329
+ opts
3330
+ );
2527
3331
  try {
2528
3332
  await this.account.waitForTransaction(transactionHash);
2529
3333
  } catch (e) {
@@ -2610,11 +3414,11 @@ var Cavos = class _Cavos {
2610
3414
  * add_signer (gasless). Returns the transaction hash (or undefined when the
2611
3415
  * backup was already set up).
2612
3416
  */
2613
- async setupRecovery(code) {
3417
+ async setupRecovery(code, opts) {
2614
3418
  const { publicKey: backupPubkey } = deriveBackupKey(code);
2615
3419
  const already = await this.adapter.isAuthorizedSigner(this.address, backupPubkey);
2616
3420
  if (already) return void 0;
2617
- return this.addSigner(backupPubkey);
3421
+ return this.addSigner(backupPubkey, opts);
2618
3422
  }
2619
3423
  /**
2620
3424
  * Recover an account after losing every device signer. Derives the backup key
@@ -2641,11 +3445,11 @@ var Cavos = class _Cavos {
2641
3445
  const backup = BackupSigner.fromCode(opts.code);
2642
3446
  const backupAdapter = new StarknetAdapter({ classHash, signer: backup, provider });
2643
3447
  const backendUrl = opts.backendUrl ?? "https://cavos.xyz";
2644
- const registry = opts.registry ?? (opts.appId ? new HttpWalletRegistry({ baseUrl: backendUrl, appId: opts.appId, network }) : defaultRegistry3);
2645
- const existing = await registry.lookup(opts.identity.userId);
2646
- if (!existing) {
3448
+ const address = opts.address ?? await lookupAddress(opts, backendUrl, network);
3449
+ if (!address) {
2647
3450
  throw new Error("kit: no account found for this identity \u2014 nothing to recover");
2648
3451
  }
3452
+ const existing = { address };
2649
3453
  const backupAccount = new starknet.Account({
2650
3454
  provider,
2651
3455
  address: existing.address,
@@ -2676,7 +3480,7 @@ var Cavos = class _Cavos {
2676
3480
  return new _Cavos(opts.identity, existing.address, "ready", account, adapter, devicePubkey);
2677
3481
  }
2678
3482
  };
2679
- var defaultRegistry3 = new InMemoryWalletRegistry();
3483
+ var defaultRegistry2 = new InMemoryWalletRegistry();
2680
3484
  var DEVICE_REQUEST_DEDUP_MS = 5 * 60 * 1e3;
2681
3485
  var lastDeviceRequest = /* @__PURE__ */ new Map();
2682
3486
  async function isDeployed(provider, address) {
@@ -2687,6 +3491,11 @@ async function isDeployed(provider, address) {
2687
3491
  return false;
2688
3492
  }
2689
3493
  }
3494
+ async function lookupAddress(opts, backendUrl, network) {
3495
+ const registry = opts.registry ?? (opts.appId ? new HttpWalletRegistry({ baseUrl: backendUrl, appId: opts.appId, network }) : defaultRegistry2);
3496
+ const existing = await registry.lookup(opts.identity.userId);
3497
+ return existing?.address ?? null;
3498
+ }
2690
3499
  async function approveDeviceEverywhere(wallets, passkey) {
2691
3500
  const targets = wallets.filter((w) => w.status === "needs-device-approval");
2692
3501
  if (targets.length === 0) return [];
@@ -2884,6 +3693,101 @@ function bytesToChunks(bytes) {
2884
3693
  for (const b of bytes.subarray(0, 31)) w = w << 8n | BigInt(b);
2885
3694
  return w;
2886
3695
  }
3696
+ var PRF_SALT = sha256.sha256(new TextEncoder().encode("cavos-stellar-prf-v1"));
3697
+ var PasskeyPrf = class {
3698
+ constructor(opts = {}) {
3699
+ if (typeof window === "undefined" || !navigator.credentials) {
3700
+ throw new Error("kit/passkey-prf: WebAuthn is only available in a browser");
3701
+ }
3702
+ this.rpId = opts.rpId ?? window.location.hostname;
3703
+ this.rpName = opts.rpName ?? this.rpId;
3704
+ if (isIpAddress(this.rpId)) {
3705
+ throw new Error(
3706
+ `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\`.`
3707
+ );
3708
+ }
3709
+ }
3710
+ /** True if this platform advertises a usable passkey (platform authenticator). */
3711
+ static async isSupported() {
3712
+ if (typeof window === "undefined" || !window.PublicKeyCredential) return false;
3713
+ try {
3714
+ return await window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
3715
+ } catch {
3716
+ return false;
3717
+ }
3718
+ }
3719
+ /**
3720
+ * Create a new synced, discoverable passkey with the PRF extension enabled, then
3721
+ * immediately read its PRF secret. Returns the credential id and the 32-byte
3722
+ * secret so the caller can enroll the passkey factor in one step. Some
3723
+ * authenticators don't return PRF results on create — in that case `secret` is
3724
+ * undefined and the caller should follow up with `getSecret()`.
3725
+ */
3726
+ async enroll(params) {
3727
+ const cred = await navigator.credentials.create({
3728
+ publicKey: {
3729
+ challenge: buf(crypto.getRandomValues(new Uint8Array(32))),
3730
+ rp: { id: this.rpId, name: this.rpName },
3731
+ user: {
3732
+ id: buf(userHandle(params.userId)),
3733
+ name: params.userName,
3734
+ displayName: params.displayName ?? params.userName
3735
+ },
3736
+ pubKeyCredParams: [{ type: "public-key", alg: -7 }],
3737
+ // ES256 (P-256)
3738
+ authenticatorSelection: {
3739
+ residentKey: "required",
3740
+ requireResidentKey: true,
3741
+ userVerification: "required"
3742
+ },
3743
+ attestation: "none",
3744
+ extensions: { prf: { eval: { first: buf(PRF_SALT) } } }
3745
+ }
3746
+ });
3747
+ if (!cred) throw new Error("kit/passkey-prf: enrollment cancelled");
3748
+ return { credentialId: new Uint8Array(cred.rawId), secret: readPrf(cred) };
3749
+ }
3750
+ /**
3751
+ * Get the passkey's 32-byte PRF secret. Uses discoverable credentials (no
3752
+ * `allowCredentials`), so it works from a brand-new browser — the OS shows the
3753
+ * synced passkey picker. Throws if the authenticator doesn't support PRF.
3754
+ */
3755
+ async getSecret() {
3756
+ const cred = await navigator.credentials.get({
3757
+ publicKey: {
3758
+ challenge: buf(crypto.getRandomValues(new Uint8Array(32))),
3759
+ rpId: this.rpId,
3760
+ allowCredentials: [],
3761
+ userVerification: "required",
3762
+ extensions: { prf: { eval: { first: buf(PRF_SALT) } } }
3763
+ }
3764
+ });
3765
+ if (!cred) throw new Error("kit/passkey-prf: assertion cancelled");
3766
+ const secret = readPrf(cred);
3767
+ if (!secret) {
3768
+ throw new Error(
3769
+ "kit/passkey-prf: this authenticator did not return a PRF result \u2014 PRF is unsupported here"
3770
+ );
3771
+ }
3772
+ return secret;
3773
+ }
3774
+ };
3775
+ function readPrf(cred) {
3776
+ const results = cred.getClientExtensionResults().prf?.results?.first;
3777
+ return results ? new Uint8Array(results) : void 0;
3778
+ }
3779
+ function isIpAddress(host) {
3780
+ if (/^\d{1,3}(\.\d{1,3}){3}$/.test(host)) return true;
3781
+ if (host.includes(":")) return true;
3782
+ return false;
3783
+ }
3784
+ function userHandle(userId) {
3785
+ const bytes = new TextEncoder().encode(userId);
3786
+ return bytes.length <= 64 ? bytes : sha256.sha256(bytes);
3787
+ }
3788
+ function buf(bytes) {
3789
+ return bytes.slice();
3790
+ }
2887
3791
  var PasskeySigner = class {
2888
3792
  constructor(opts = {}) {
2889
3793
  if (typeof window === "undefined" || !navigator.credentials) {
@@ -2891,7 +3795,7 @@ var PasskeySigner = class {
2891
3795
  }
2892
3796
  this.rpId = opts.rpId ?? window.location.hostname;
2893
3797
  this.rpName = opts.rpName ?? this.rpId;
2894
- if (isIpAddress(this.rpId)) {
3798
+ if (isIpAddress2(this.rpId)) {
2895
3799
  throw new Error(
2896
3800
  `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.)`
2897
3801
  );
@@ -2911,10 +3815,10 @@ var PasskeySigner = class {
2911
3815
  const challenge = crypto.getRandomValues(new Uint8Array(32));
2912
3816
  const cred = await navigator.credentials.create({
2913
3817
  publicKey: {
2914
- challenge: buf(challenge),
3818
+ challenge: buf2(challenge),
2915
3819
  rp: { id: this.rpId, name: this.rpName },
2916
3820
  user: {
2917
- id: buf(userHandle(params.userId)),
3821
+ id: buf2(userHandle2(params.userId)),
2918
3822
  name: params.userName,
2919
3823
  displayName: params.displayName ?? params.userName
2920
3824
  },
@@ -2941,7 +3845,7 @@ var PasskeySigner = class {
2941
3845
  async assert(challenge) {
2942
3846
  const cred = await navigator.credentials.get({
2943
3847
  publicKey: {
2944
- challenge: buf(challenge),
3848
+ challenge: buf2(challenge),
2945
3849
  rpId: this.rpId,
2946
3850
  allowCredentials: [],
2947
3851
  userVerification: "preferred"
@@ -2956,18 +3860,23 @@ var PasskeySigner = class {
2956
3860
  return { authenticatorData, clientDataJSON, r, s, challengeOffset };
2957
3861
  }
2958
3862
  };
2959
- function isIpAddress(host) {
3863
+ function isIpAddress2(host) {
2960
3864
  if (/^\d{1,3}(\.\d{1,3}){3}$/.test(host)) return true;
2961
3865
  if (host.includes(":")) return true;
2962
3866
  return false;
2963
3867
  }
2964
- function userHandle(userId) {
3868
+ function userHandle2(userId) {
2965
3869
  const bytes = new TextEncoder().encode(userId);
2966
3870
  return bytes.length <= 64 ? bytes : sha256.sha256(bytes);
2967
3871
  }
2968
- function buf(bytes) {
3872
+ function buf2(bytes) {
2969
3873
  return bytes.slice();
2970
3874
  }
3875
+ /*! Bundled license information:
3876
+
3877
+ @noble/ciphers/esm/utils.js:
3878
+ (*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) *)
3879
+ */
2971
3880
 
2972
3881
  exports.BackupSigner = BackupSigner;
2973
3882
  exports.Cavos = Cavos;
@@ -2976,12 +3885,12 @@ exports.CavosSolana = CavosSolana;
2976
3885
  exports.CavosStellar = CavosStellar;
2977
3886
  exports.DEVICE_ACCOUNT_CLASS_HASH = DEVICE_ACCOUNT_CLASS_HASH;
2978
3887
  exports.DEVICE_ACCOUNT_PROGRAM_ID = DEVICE_ACCOUNT_PROGRAM_ID;
2979
- exports.DEVICE_ACCOUNT_WASM_HASH = DEVICE_ACCOUNT_WASM_HASH;
2980
- exports.FACTORY_CONTRACT_ID = FACTORY_CONTRACT_ID;
3888
+ exports.HORIZON_URL = HORIZON_URL;
2981
3889
  exports.HttpRecoveryClient = HttpRecoveryClient;
2982
3890
  exports.HttpWalletRegistry = HttpWalletRegistry;
2983
3891
  exports.InMemoryWalletRegistry = InMemoryWalletRegistry;
2984
- exports.NATIVE_SAC_ID = NATIVE_SAC_ID;
3892
+ exports.LocalDeviceUnwrapKey = LocalDeviceUnwrapKey;
3893
+ exports.PasskeyPrf = PasskeyPrf;
2985
3894
  exports.PasskeySigner = PasskeySigner;
2986
3895
  exports.SECP256R1_PROGRAM_ID = SECP256R1_PROGRAM_ID;
2987
3896
  exports.SOLANA_NETWORKS = SOLANA_NETWORKS;
@@ -2995,7 +3904,9 @@ exports.StaticIdentity = StaticIdentity;
2995
3904
  exports.StellarAdapter = StellarAdapter;
2996
3905
  exports.StellarRelayer = StellarRelayer;
2997
3906
  exports.UDC_ADDRESS = UDC_ADDRESS;
3907
+ exports.WebCryptoDeviceUnwrapKey = WebCryptoDeviceUnwrapKey;
2998
3908
  exports.WebCryptoSigner = WebCryptoSigner;
3909
+ exports.XLM_DECIMALS = XLM_DECIMALS;
2999
3910
  exports.anchorDiscriminator = anchorDiscriminator;
3000
3911
  exports.approveDeviceEverywhere = approveDeviceEverywhere;
3001
3912
  exports.base64urlEncode = base64urlEncode;
@@ -3009,18 +3920,30 @@ exports.deriveAddressSeed = deriveAddressSeed;
3009
3920
  exports.deriveAddressSeedSolana = deriveAddressSeedSolana;
3010
3921
  exports.deriveAddressSeedStellar = deriveAddressSeedStellar;
3011
3922
  exports.deriveBackupKey = deriveBackupKey;
3012
- exports.deviceSignatureScVal = deviceSignatureScVal;
3923
+ exports.derivePasskeyKEK = derivePasskeyKEK;
3924
+ exports.deriveRecoveryKEK = deriveRecoveryKEK;
3925
+ exports.deriveStellarAddress = deriveStellarAddress;
3926
+ exports.deriveStellarMasterKeypair = deriveStellarMasterKeypair;
3927
+ exports.deviceSlotId = deviceSlotId;
3928
+ exports.eciesUnwrapDEK = eciesUnwrapDEK;
3929
+ exports.eciesWrapDEK = eciesWrapDEK;
3013
3930
  exports.encodeLowSSignature = encodeLowSSignature;
3014
- exports.encodeStellarLowSSignature = encodeLowSSignature2;
3931
+ exports.fromDataEntries = fromDataEntries;
3932
+ exports.generateControlKey = generateControlKey;
3933
+ exports.generateDEK = generateDEK;
3015
3934
  exports.generateRecoveryCode = generateRecoveryCode;
3016
3935
  exports.hexToBytes = hexToBytes;
3017
3936
  exports.lowS = lowS;
3937
+ exports.openControlSeed = openControlSeed;
3018
3938
  exports.recoverCandidatePublicKeys = recoverCandidatePublicKeys;
3019
3939
  exports.recoverYParity = recoverYParity;
3020
- exports.sec1Pubkey = sec1Pubkey;
3940
+ exports.sealControlSeed = sealControlSeed;
3021
3941
  exports.serializeInstructions = serializeInstructions;
3022
3942
  exports.signatureToFelts = signatureToFelts;
3943
+ exports.toDataEntries = toDataEntries;
3023
3944
  exports.u256ToFelts = u256ToFelts;
3945
+ exports.unwrapDEK = unwrapDEK;
3024
3946
  exports.webauthnDigest = webauthnDigest;
3947
+ exports.wrapDEK = wrapDEK;
3025
3948
  //# sourceMappingURL=index.js.map
3026
3949
  //# sourceMappingURL=index.js.map