@cavos/kit 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -184,11 +184,20 @@ declare class SolanaAdapter {
184
184
  readonly chain: "solana";
185
185
  readonly programId: PublicKey;
186
186
  constructor(opts?: SolanaAdapterOptions);
187
- /** Deterministic account address: PDA of [seed, address_seed, initial_signer_x]. */
188
- computeAddress(addressSeed: Uint8Array, initialSigner: DevicePublicKey): string;
187
+ /**
188
+ * Deterministic account address: PDA of [ACCOUNT_SEED, addressSeed] the
189
+ * device pubkey is NOT part of the seeds, so the address is recomputable
190
+ * from (userId, appSalt) alone. Anti-squatting is the integrator's
191
+ * responsibility (keep `appSalt` secret; deploy on first login).
192
+ */
193
+ computeAddress(addressSeed: Uint8Array): string;
189
194
  private pda;
190
- /** `initialize` instruction creating the account with its first device signer. */
191
- buildInitialize(addressSeed: Uint8Array, payer: string, initialSigner: DevicePublicKey): TransactionInstruction;
195
+ /**
196
+ * `initialize` instruction: creates the account PDA and registers the first
197
+ * device signer. No attestation is required — anti-squatting is NOT enforced
198
+ * on-chain.
199
+ */
200
+ buildInitialize(addressSeed: Uint8Array, payer: string, initialSigner: DevicePublicKey): TransactionInstruction[];
192
201
  /** `[precompile, add_signer]` bundle, authorized by an existing device signer. */
193
202
  buildAddSigner(account: string, newSigner: DevicePublicKey): Promise<TransactionInstruction[]>;
194
203
  /** `[precompile, remove_signer]` bundle, authorized by an existing device signer. */
@@ -209,6 +218,8 @@ declare class SolanaAdapter {
209
218
  */
210
219
  buildAddSignerViaPasskey(account: string, newSigner: DevicePublicKey, passkey: DevicePublicKey, leaves: Uint8Array[], leafIndex: number, assertion: PasskeyAssertion): TransactionInstruction[];
211
220
  /** Read whether `passkey` is a registered approver. */
221
+ /** True if the account has at least one passkey registered as an approver. */
222
+ hasPasskeyApprover(account: string): Promise<boolean>;
212
223
  isApprover(account: string, passkey: DevicePublicKey): Promise<boolean>;
213
224
  /** Read the current passkey-approval nonce. */
214
225
  passkeyNonce(account: string): Promise<bigint>;
@@ -331,6 +342,67 @@ declare class PasskeySigner {
331
342
  assert(challenge: Uint8Array): Promise<PasskeyAssertion>;
332
343
  }
333
344
 
345
+ /** A chain-native contract call (Starknet `Call`-shaped; generic for portability). */
346
+ interface ChainCall {
347
+ contractAddress: string;
348
+ entrypoint: string;
349
+ calldata: string[];
350
+ }
351
+ /**
352
+ * Options for state-changing wallet calls (`execute`, `addSigner`, etc.).
353
+ *
354
+ * await wallet.execute(calls, { sponsored: false }); // self-funded
355
+ *
356
+ * `sponsored` defaults to `true`: the Cavos relayer / paymaster pays gas (and, on
357
+ * Stellar, the reserve) so the user signs but never holds gas tokens. Pass
358
+ * `sponsored: false` to submit directly — the account pays its own fee / reserve
359
+ * from its own balance (ETH on Starknet, SOL on Solana, XLM on Stellar). Useful
360
+ * for testing the device signature, for fee transparency, or as a fallback when
361
+ * the relayer is unreachable. Self-funded mode requires the account to actually
362
+ * hold enough native balance for the fee (and Stellar reserve, if the call adds
363
+ * subentries).
364
+ */
365
+ interface ExecuteOptions {
366
+ sponsored?: boolean;
367
+ }
368
+ interface ComputeAddressParams {
369
+ addressSeed: bigint;
370
+ /**
371
+ * First device signer. Required by chains whose address derivation still
372
+ * includes the device pubkey (Solana PDA seeds, Stellar factory salt). The
373
+ * Starknet adapter IGNORES this — its address is `f(addressSeed)` only.
374
+ */
375
+ initialSigner?: DevicePublicKey;
376
+ /** Defaults to `addressSeed` when omitted. */
377
+ salt?: bigint;
378
+ }
379
+ /**
380
+ * Per-chain implementation surface. Phase 1 ships only Starknet, but the kit is
381
+ * designed so Stellar and Solana adapters drop in behind the same interface.
382
+ */
383
+ interface ChainAdapter {
384
+ readonly chain: "starknet" | "stellar" | "solana";
385
+ /**
386
+ * Deterministic account address. Starknet: `f(addressSeed)` only (device
387
+ * pubkey not in the derivation — recovery is self-custodial). Solana: also
388
+ * `f(addressSeed)` (PDA seeds use only the seed). Stellar: classic `G…`
389
+ * multisig where the address is the source account.
390
+ */
391
+ computeAddress(params: ComputeAddressParams): string;
392
+ /** Call(s) to deploy the account with its first device signer (UDC). */
393
+ buildDeploy(params: ComputeAddressParams): ChainCall[];
394
+ buildAddSigner(accountAddress: string, signer: DevicePublicKey): ChainCall;
395
+ buildRemoveSigner(accountAddress: string, signer: DevicePublicKey): ChainCall;
396
+ /** Read whether a pubkey is a currently-authorized signer of the account. */
397
+ isAuthorizedSigner(accountAddress: string, signer: DevicePublicKey): Promise<boolean>;
398
+ /**
399
+ * Compute the signature payload for an outgoing transaction: given the chain's
400
+ * tx hash, obtain a device assertion and serialize it to the chain's expected
401
+ * signature encoding.
402
+ */
403
+ buildSignature(txHash: bigint): Promise<string[]>;
404
+ }
405
+
334
406
  interface ConnectSolanaOptions {
335
407
  network: SolanaNetwork;
336
408
  /** Authenticated user (pass `identity` directly, or an `auth` provider). */
@@ -360,7 +432,7 @@ interface ConnectSolanaOptions {
360
432
  */
361
433
  feePayer?: Keypair;
362
434
  }
363
- type ConnectStatus$2 = "ready" | "needs-device-approval";
435
+ type ConnectStatus$1 = "ready" | "needs-device-approval";
364
436
  /**
365
437
  * Options for recovering a Solana account after losing every device signer.
366
438
  * Mirrors `RecoveryOptions` (Starknet), adapted to the Solana path: the backup
@@ -405,7 +477,7 @@ interface RecoverSolanaOptions {
405
477
  declare class CavosSolana {
406
478
  readonly identity: Identity;
407
479
  readonly address: string;
408
- readonly status: ConnectStatus$2;
480
+ readonly status: ConnectStatus$1;
409
481
  readonly connection: Connection;
410
482
  private readonly adapter;
411
483
  private readonly devicePubkey;
@@ -413,6 +485,8 @@ declare class CavosSolana {
413
485
  private readonly feePayer?;
414
486
  /** Discriminant for the `CavosWallet` union — narrows `execute()` per chain. */
415
487
  readonly chain: "solana";
488
+ /** True when this connect just created a brand-new account (first sign-up). */
489
+ isNewAccount: boolean;
416
490
  private constructor();
417
491
  get publicKey(): DevicePublicKey;
418
492
  static connect(opts: ConnectSolanaOptions): Promise<CavosSolana>;
@@ -431,6 +505,12 @@ declare class CavosSolana {
431
505
  addApprover(pubkey: DevicePublicKey): Promise<{
432
506
  transactionHash?: string;
433
507
  }>;
508
+ /** True if this account already has a passkey enrolled as an approver, so a
509
+ * new device can be approved with the passkey instead of the email flow. */
510
+ hasPasskey(): Promise<boolean>;
511
+ /** Re-read (from chain) whether THIS device is now an authorized signer.
512
+ * Used to poll for readiness after a passkey approval before it's indexed. */
513
+ isReady(): Promise<boolean>;
434
514
  /**
435
515
  * From a fresh browser (status `needs-device-approval`), approve adding THIS
436
516
  * device with the user's synced passkey. Gasless via the relayer — the bundle
@@ -448,7 +528,7 @@ declare class CavosSolana {
448
528
  transactionHash: string;
449
529
  }>;
450
530
  /** Move `amount` lamports out of the account to `destination` (device-signed). */
451
- execute(amount: bigint, destination: string): Promise<string>;
531
+ execute(amount: bigint, destination: string, opts?: ExecuteOptions): Promise<string>;
452
532
  /**
453
533
  * Run arbitrary CPI `instructions` with the account PDA as signer (device-
454
534
  * signed). The signature commits to sha256 of the canonical Borsh
@@ -457,9 +537,11 @@ declare class CavosSolana {
457
537
  *
458
538
  * What the relayer will sponsor is constrained by the app's Solana program
459
539
  * allowlist (configured in the dashboard) — programs outside the allowlist are
460
- * rejected before co-signing.
540
+ * rejected before co-signing. Pass `{ sponsored: false }` to bypass the relayer
541
+ * and pay the fee from a configured `feePayer` (e.g. for allowlisted programs
542
+ * the relayer rejects, or to test the device signature end-to-end).
461
543
  */
462
- executeInstructions(instructions: InstructionData[]): Promise<string>;
544
+ executeInstructions(instructions: InstructionData[], opts?: ExecuteOptions): Promise<string>;
463
545
  /**
464
546
  * Register the backup signer derived from `code` as an authorized signer of this
465
547
  * account (device-signed via precompile). Idempotent: returns without a tx if
@@ -485,41 +567,79 @@ declare class CavosSolana {
485
567
  * the backup key. The on-chain program needs no recovery-specific entrypoint.
486
568
  */
487
569
  static recover(opts: RecoverSolanaOptions): Promise<CavosSolana>;
570
+ /**
571
+ * Submit a built instruction bundle. Sponsored by default (relayer pays the
572
+ * fee); pass `{ sponsored: false }` to self-fund via the configured `feePayer`.
573
+ * The device signature is embedded inside the secp256r1 precompile instruction,
574
+ * NOT applied as a Solana tx signature — so switching only changes who pays,
575
+ * never the signing identity.
576
+ */
488
577
  private send;
489
578
  }
490
579
 
491
- /** Cavos device-account primitives on Stellar / Soroban. */
492
580
  /**
493
- * Deployed `cavos-account-factory` contract id per network (see
494
- * account-contracts/stellar/deployments). The factory is the fixed deployer that
495
- * makes account addresses a deterministic function of (identity, device pubkey).
581
+ * The device's P-256 **ECDH** key used only to unwrap the account DEK.
582
+ *
583
+ * Note this is a SEPARATE key from the `DeviceSigner` (which is an ECDSA signing
584
+ * key, non-extractable, and therefore cannot do ECDH). This key is per-device
585
+ * and non-syncable — losing the device loses only this convenience factor, not
586
+ * the account (the passkey / recovery factors survive). Its public key goes into
587
+ * the account's `cv:wd:<id>` on-chain envelope slot; the DEK is ECIES-wrapped to
588
+ * it so daily unlock is silent.
589
+ *
590
+ * `LocalDeviceUnwrapKey` holds a raw scalar (Node / tests / React Native secure
591
+ * storage). The browser implementation wraps a non-extractable WebCrypto ECDH
592
+ * key and overrides `unwrap` with `deriveBits`; it never exposes the scalar.
496
593
  */
497
- declare const FACTORY_CONTRACT_ID: {
498
- readonly "stellar-testnet": "CBCJIODXIEBOXXD66KCUCF7ZDYJARKI4ZIVQOVWPULOBH5XGNCDP6W3I";
499
- readonly "stellar-mainnet": "";
500
- };
501
- /** Uploaded Wasm hash of `cavos-device-account` (informational / verification). */
502
- declare const DEVICE_ACCOUNT_WASM_HASH: {
503
- readonly "stellar-testnet": "2671b085578e59a385ef5a5664e42f0450322fe3249539f588e1263ed5a31dce";
504
- readonly "stellar-mainnet": "";
505
- };
594
+ interface DeviceUnwrapKey {
595
+ /** SEC1 *uncompressed* (65-byte) public key — the ECIES recipient key that is
596
+ * published on-chain in the device's envelope slot. */
597
+ publicKeySec1(): Uint8Array;
598
+ /** A short, stable id for this device's envelope slot (`cv:wd:<id>`). */
599
+ slotId(): string;
600
+ /** Unwrap the account DEK from this device's ECIES blob. */
601
+ unwrap(blob: Uint8Array): Promise<Uint8Array>;
602
+ }
603
+ /** Raw-scalar device unwrap key (Node / React Native). */
604
+ declare class LocalDeviceUnwrapKey implements DeviceUnwrapKey {
605
+ private readonly scalar;
606
+ private constructor();
607
+ /** Generate a fresh device unwrap key. */
608
+ static generate(): LocalDeviceUnwrapKey;
609
+ /** Rebuild from a persisted 32-byte scalar. */
610
+ static fromScalar(scalar: Uint8Array): LocalDeviceUnwrapKey;
611
+ /** The raw scalar, for the caller to persist in secure storage. */
612
+ export(): Uint8Array;
613
+ publicKeySec1(): Uint8Array;
614
+ slotId(): string;
615
+ unwrap(blob: Uint8Array): Promise<Uint8Array>;
616
+ }
617
+ /** Stable short slot id for a device's envelope entry: first 8 hex of
618
+ * `sha256(pubkey)`. Deterministic from the public key, so it's the same on
619
+ * every read and can't collide in practice for a handful of devices. */
620
+ declare function deviceSlotId(publicKeySec1: Uint8Array): string;
621
+
622
+ /** Classic-Stellar (`G…`) network configuration.
623
+ *
624
+ * This is now THE Stellar implementation in the kit (the Soroban `C…`
625
+ * device-account path was removed — `G…` classic multisig is the default). Classic
626
+ * Stellar uses Horizon (not the Soroban RPC) to load account state and submit
627
+ * transactions. */
506
628
  declare const STELLAR_NETWORKS: {
507
629
  readonly "stellar-testnet": {
508
- readonly rpcUrl: "https://soroban-testnet.stellar.org";
509
630
  readonly passphrase: "Test SDF Network ; September 2015";
510
631
  };
511
632
  readonly "stellar-mainnet": {
512
- readonly rpcUrl: "https://soroban-rpc.mainnet.stellar.gateway.fm";
513
633
  readonly passphrase: "Public Global Stellar Network ; September 2015";
514
634
  };
515
635
  };
516
636
  type StellarNetwork = keyof typeof STELLAR_NETWORKS;
517
- /** Native XLM Stellar Asset Contract (SAC) id per network — the token the demo
518
- * moves. Any SEP-41 token contract works; this is a convenience default. */
519
- declare const NATIVE_SAC_ID: {
520
- readonly "stellar-testnet": "CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC";
521
- readonly "stellar-mainnet": "CAS3J7GYLGXMF6TDJBBYYSE3HQ6BBSMLNUQ34T6TZMYMW2EVH34XOWMA";
637
+ declare const HORIZON_URL: {
638
+ readonly "stellar-testnet": "https://horizon-testnet.stellar.org";
639
+ readonly "stellar-mainnet": "https://horizon.stellar.org";
522
640
  };
641
+ /** Native XLM has 7 decimals (stroops). */
642
+ declare const XLM_DECIMALS = 7;
523
643
 
524
644
  interface StellarRelayerOptions {
525
645
  /** Base URL of the Cavos backend exposing /api/stellar/relay. */
@@ -528,30 +648,33 @@ interface StellarRelayerOptions {
528
648
  appId: string;
529
649
  network: StellarNetwork;
530
650
  }
651
+ /** What the transaction is, so the backend applies the right validation gate.
652
+ * - `create` sponsored account creation (relayer = source + sponsor)
653
+ * - `fee-bump` a control-signed payment wrapped in a relayer fee-bump
654
+ * - `sponsored-data` a control-signed data write (add factor/device slot) whose
655
+ * new subentry reserves the relayer sponsors */
656
+ type StellarRelayKind = "create" | "fee-bump" | "sponsored-data";
531
657
  /**
532
- * Client for the Cavos Stellar sponsoring relayer. On Stellar the account is a
533
- * *contract*, which cannot be a transaction source so the relayer's own
534
- * G-account is the transaction source AND fee payer. The user's silent device
535
- * key never pays: it only signs the Soroban *authorization entry* (verified by
536
- * the account's `__check_auth`), which is independent of who submits or pays.
537
- * That gives a seedless, gasless experience with no fee-payer keypair on the
538
- * integrator side the same "relayer pays, device authorizes" split as Solana.
658
+ * Client for the classic-G sponsoring relayer. Unlike the Soroban relayer (which
659
+ * is the tx *source*), the classic relayer plays two roles:
660
+ * - **create**: it is the tx source + fee payer AND sponsors the new account's
661
+ * reserves (`begin/endSponsoringFutureReserves`), so the user locks no XLM.
662
+ * The SDK sends a master-signed create tx; the relayer co-signs + submits.
663
+ * - **fee-bump**: the user's control-signed inner tx (source = their `G…`) is
664
+ * wrapped in a fee-bump whose fee source is the relayer. The relayer signs the
665
+ * outer envelope only — it pays the fee, never moves the user's funds.
539
666
  *
540
- * The SDK builds the fully-assembled transaction (source = relayer, Soroban auth
541
- * entries already device-signed) and hands its unsigned XDR to the relayer, which
542
- * validates it against its allowlist, signs the envelope and submits.
667
+ * Either way the relayer is a fee payer / reserve sponsor, never a custodian.
543
668
  */
544
669
  declare class StellarRelayer {
545
670
  private readonly opts;
546
671
  private source?;
547
672
  constructor(opts: StellarRelayerOptions);
548
- /** The relayer's source/fee-payer G-account (fetched + cached from the backend). */
673
+ /** The relayer's source/fee-payer/sponsor G-account (fetched + cached). */
549
674
  getSource(): Promise<string>;
550
- /**
551
- * POST the assembled, device-authorized transaction XDR to the relayer to sign
552
- * the envelope + submit. Returns the confirmed transaction hash.
553
- */
554
- submit(transactionXdr: string): Promise<string>;
675
+ /** POST a (partially) signed transaction XDR for the relayer to co-sign + submit.
676
+ * `kind` selects the validation gate. Returns the confirmed transaction hash. */
677
+ submit(kind: StellarRelayKind, transactionXdr: string): Promise<string>;
555
678
  }
556
679
 
557
680
  interface ConnectStellarOptions {
@@ -560,157 +683,136 @@ interface ConnectStellarOptions {
560
683
  auth?: AuthProvider;
561
684
  identity?: Identity;
562
685
  appSalt: string;
563
- appId?: string;
564
- backendUrl?: string;
565
- registry?: WalletRegistry;
566
- /** RPC override (else the network default). */
567
- rpcUrl?: string;
568
- /** Factory contract id override (else the per-network default). */
569
- factoryId?: string;
570
- /** Override the device signer factory (native / tests); default WebCrypto. */
571
- createSigner?: (keyId: string) => Promise<DeviceSigner>;
686
+ /** This device's P-256 ECDH unwrap key (provisioned + persisted per device). */
687
+ deviceKey: DeviceUnwrapKey;
572
688
  /**
573
- * Gasless sponsorship via the Cavos relayer. When set (or when `appId` +
574
- * `backendUrl` are given) the relayer is the transaction source + fee payer, so
575
- * the integrator needs NO Stellar keypair the silent device key (which holds
576
- * no XLM) gets a seedless, gasless experience.
689
+ * Gasless sponsorship via the Cavos classic relayer. When set (or when `appId` +
690
+ * `backendUrl` are given) the relayer is the tx source + fee payer AND sponsors
691
+ * the account's reserves the user locks no XLM and pays no fees.
577
692
  */
578
693
  relayer?: StellarRelayer;
694
+ /** Cavos App ID — enables the default relayer when no `relayer` is passed. */
695
+ appId?: string;
696
+ /** Cavos backend base URL (default https://cavos.xyz). */
697
+ backendUrl?: string;
579
698
  /**
580
- * Self-funded fallback: a Stellar `Keypair` that is the transaction source +
581
- * fee payer. Used only when no `relayer` is configured (tests / advanced).
699
+ * Self-funded funder + fee payer: creates + submits classic transactions
700
+ * directly (the account pays its own reserves + fees). The advanced /
701
+ * self-hosted fallback used when no relayer is configured.
582
702
  */
583
703
  sourceKeypair?: Keypair$1;
704
+ /** Horizon URL override. */
705
+ horizonUrl?: string;
706
+ /** Starting balance for a fresh account, in stroops. */
707
+ startingBalance?: bigint;
584
708
  }
585
- interface RecoverStellarOptions extends Omit<ConnectStellarOptions, "auth"> {
586
- /** The recovery code the user stored when they ran setupRecovery. */
587
- code: string;
588
- /** Authenticated identity (same user who owns the account). */
589
- identity: Identity;
590
- }
591
- type ConnectStatus$1 = "ready" | "needs-device-approval";
709
+ type StellarConnectStatus = "ready" | "needs-device-approval";
592
710
  /**
593
- * High-level Stellar entry the Soroban analogue of `Cavos.connect` /
594
- * `CavosSolana.connect`. One call derives the deterministic device-bound account,
595
- * deploys it via the factory if needed, registers it for cross-device
596
- * recognition, and returns a ready handle whose silent P-256 device key
597
- * authorizes every action through the account's `__check_auth`.
711
+ * High-level entry for the classic-Stellar (`G…`) multisig account — the classic
712
+ * analogue of `CavosStellar` (Soroban). One `connect` derives the deterministic
713
+ * `G…` address, creates the account if needed, and on a known device unlocks the
714
+ * control key from the on-chain envelope so `execute` signs silently.
598
715
  *
599
- * const cavos = await CavosStellar.connect({ network: "stellar-testnet", identity, appSalt, relayer });
600
- * if (cavos.status === "ready") await cavos.execute(10_000_000n, dest); // 1 XLM
716
+ * Multiple unlock **factors** all wrap the same DEK, so opening any one yields the
717
+ * control key:
718
+ * - **device** (P-256 ECIES): silent daily signing, per-device, non-syncable;
719
+ * - **passkey** (WebAuthn PRF): synced anchor to approve a new device / recover;
720
+ * - **recovery code**: offline backup (optional).
601
721
  *
602
- * Gasless by default: with an `appId` the Cavos relayer is the tx source + fee
603
- * payer. `sourceKeypair` is the self-funded fallback.
722
+ * Self-custodial, no backend, no registry: the address is a pure function of
723
+ * identity and the control key lives only in the account's own data entries.
724
+ * Unlike the Soroban `CavosStellar`, this path uses NO wallet registry —
725
+ * creation needs neither an org API key nor a relayer. The optional relayer is
726
+ * only a fee payer + reserve sponsor (never a custodian or identity authority),
727
+ * so a bad/absent relayer can cost fees but can never move funds or squat an
728
+ * address.
604
729
  */
605
730
  declare class CavosStellar {
606
731
  readonly identity: Identity;
607
732
  readonly address: string;
608
- readonly status: ConnectStatus$1;
609
733
  readonly network: StellarNetwork;
610
734
  private readonly adapter;
611
- private readonly devicePubkey;
612
- private readonly relayer?;
613
- private readonly sourceKeypair?;
614
- /** Discriminant for the `CavosWallet` union — narrows `execute()` per chain. */
735
+ private readonly deviceKey;
736
+ private control;
737
+ private dek;
738
+ private readonly relayer;
615
739
  readonly chain: "stellar";
740
+ isNewAccount: boolean;
741
+ private statusValue;
616
742
  private constructor();
617
- get publicKey(): DevicePublicKey;
743
+ get status(): StellarConnectStatus;
618
744
  static connect(opts: ConnectStellarOptions): Promise<CavosStellar>;
619
- /** Authorize an additional device signer (device-signed via `__check_auth`). */
620
- addSigner(pubkey: DevicePublicKey): Promise<string>;
745
+ /** Native XLM balance of the account, in stroops. */
746
+ balance(): Promise<bigint>;
747
+ /** True if the account has a passkey factor enrolled (`cv:wp`), so a new device
748
+ * can be approved with the passkey instead of a recovery code. Mirrors the
749
+ * other chains' `hasPasskey()` for the React provider. */
750
+ hasPasskey(): Promise<boolean>;
751
+ /** Whether the control key is unlocked on this device (status ready). Classic
752
+ * approvals land synchronously via Horizon, so this reflects state immediately
753
+ * (no indexing delay to poll for). */
754
+ isReady(): Promise<boolean>;
621
755
  /**
622
- * Enroll a passkey as an approver (2FA-style step-up). Device-signed + gasless;
623
- * requires a ready device. Idempotent. Returns the passkey pubkey + tx hash.
756
+ * Move `amount` stroops of native XLM to `destination`, signed by the control
757
+ * key. Sponsored by default (the relayer fee-bumps and pays the fee); pass
758
+ * `{ sponsored: false }` to submit directly — the account pays its own (tiny)
759
+ * fee from its XLM balance. The control key signs identically in both modes;
760
+ * only the fee payer differs.
624
761
  */
625
- enrollPasskey(passkey: PasskeySigner, params: PasskeyEnrollParams): Promise<{
626
- publicKey: DevicePublicKey;
627
- transactionHash?: string;
628
- }>;
629
- /** Register an already-enrolled passkey pubkey as an approver (gasless).
630
- * Idempotent. Lets one passkey be registered across chains without re-prompting. */
631
- addApprover(pubkey: DevicePublicKey): Promise<{
632
- transactionHash?: string;
633
- }>;
762
+ execute(amount: bigint, destination: string, opts?: ExecuteOptions): Promise<string>;
634
763
  /**
635
- * From a fresh browser (status `needs-device-approval`), approve adding THIS
636
- * device using the user's synced passkey. Gasless via the relayer the call
637
- * carries the WebAuthn assertion, so no device signature is needed. Returns the
638
- * tx hash. No trip back to an already-authorized device.
764
+ * Enroll a passkey as an unlock factor: wrap the DEK under the passkey's PRF
765
+ * output and write the `cv:wp` entry. This is the synced anchor used to approve
766
+ * a new device or recover it survives device loss. Idempotent-ish: writing it
767
+ * again just overwrites the wrap of the same DEK. Requires a ready device.
639
768
  */
640
- approveThisDeviceWithPasskey(passkey: PasskeySigner): Promise<string>;
641
- /** This device's leaf + passkey nonce for a (possibly multi-chain) batch. */
642
- passkeyLeafForThisDevice(): Promise<{
643
- leaf: Uint8Array;
644
- nonce: bigint;
645
- }>;
646
- /** Submit `add_signer_via_passkey` given a shared assertion + batch position.
647
- * No device auth entry — authorized purely by the passkey assertion. */
648
- submitPasskeyApproval(assertion: PasskeyAssertion, leaves: Uint8Array[], leafIndex: number, nonce: bigint): Promise<{
649
- transactionHash: string;
650
- }>;
651
- /** Move `amount` stroops of native XLM to `destination` (device-signed). */
652
- execute(amount: bigint, destination: string): Promise<string>;
653
- /** Read this account's balance of `tokenId` (defaults to native XLM), in stroops. */
654
- balance(tokenId?: string): Promise<bigint>;
655
- /** Transfer `amount` of any SEP-41 token out of the account (device-signed). */
656
- executeTransfer(tokenId: string, amount: bigint, destination: string): Promise<string>;
769
+ enrollPasskey(prfOutput: Uint8Array): Promise<string>;
657
770
  /**
658
- * Register the backup signer derived from `code` as an authorized signer of
659
- * this account (device-signed). Idempotent. The code never leaves the device
660
- * only the derived public key travels on-chain. Mirrors the other chains.
771
+ * Set up a recovery code as an unlock factor: wrap the DEK under the code's KEK
772
+ * and write the `cv:wr` entry. Optional in v1 the integrating app decides when
773
+ * to surface it. The code never leaves the device; only the wrap goes on-chain.
774
+ * Requires a ready device.
661
775
  */
662
- setupRecovery(code: string): Promise<string | undefined>;
776
+ setupRecovery(code: string): Promise<string>;
663
777
  /**
664
- * Recover an account after losing every device signer: derive the backup key
665
- * from `code`, use it (not the new device) to authorize `add_signer(newDevice)`,
666
- * and return a ready handle bound to the new device. The address is unchanged.
778
+ * From a new browser/device (`needs-device-approval`), approve THIS device using
779
+ * the user's synced passkey: unlock the DEK via the passkey factor, then wrap it
780
+ * to this device's slot so future sessions unlock silently. Flips status to
781
+ * `ready`. No trip back to an already-authorized device.
667
782
  */
668
- static recover(opts: RecoverStellarOptions): Promise<CavosStellar>;
669
- /** The transaction source/fee-payer G-address (relayer or self-funded). */
670
- private resolveSource;
783
+ approveThisDeviceWithPasskey(prfOutput: Uint8Array): Promise<string>;
784
+ /** Approve THIS device using the recovery code (same as the passkey path, for
785
+ * the backup factor). */
786
+ approveThisDeviceWithRecovery(code: string): Promise<string>;
787
+ /** The control key's public G address (the weight-1 real signer), for display. */
788
+ get controlAddress(): string | undefined;
789
+ private approveThisDevice;
790
+ /** Write a single-factor wrap (passkey/recovery) into the account data entries,
791
+ * signed by the control key. Overwrites cleanly if the base already existed and
792
+ * the new blob has the same chunk count. */
793
+ private writeFactor;
671
794
  /**
672
- * Build simulate → device-sign auth assemble submit an invoke-contract
673
- * host function. `authAccount` is the account whose `__check_auth` must sign the
674
- * operation's Soroban auth entry (undefined for a plain factory deploy).
795
+ * Sign an inner (account-sourced) payment tx with the control key and submit it:
796
+ * - sponsored (default) with a relayer, wrap in a fee-bump (relayer pays
797
+ * the fee) and POST; falls back to self-funded if no relayer;
798
+ * - `{ sponsored: false }` → submit directly (the account pays its own fee).
799
+ * Payments add no subentries, so no reserve sponsorship is needed here.
675
800
  */
676
- private submitHostFunction;
677
- /** Submit a signed tx via RPC and poll to confirmation. Returns the hash. */
678
- private sendAndConfirm;
679
- }
680
-
681
- /** A chain-native contract call (Starknet `Call`-shaped; generic for portability). */
682
- interface ChainCall {
683
- contractAddress: string;
684
- entrypoint: string;
685
- calldata: string[];
686
- }
687
- interface ComputeAddressParams {
688
- addressSeed: bigint;
689
- /** First device signer — part of the address, making it unforgeable. */
690
- initialSigner: DevicePublicKey;
691
- /** Defaults to `addressSeed` when omitted. */
692
- salt?: bigint;
693
- }
694
- /**
695
- * Per-chain implementation surface. Phase 1 ships only Starknet, but the kit is
696
- * designed so Stellar and Solana adapters drop in behind the same interface.
697
- */
698
- interface ChainAdapter {
699
- readonly chain: "starknet" | "stellar" | "solana";
700
- /** Deterministic address from identity seed + the first device signer. */
701
- computeAddress(params: ComputeAddressParams): string;
702
- /** Call(s) to deploy the account with its first device signer (UDC). */
703
- buildDeploy(params: ComputeAddressParams): ChainCall[];
704
- buildAddSigner(accountAddress: string, signer: DevicePublicKey): ChainCall;
705
- buildRemoveSigner(accountAddress: string, signer: DevicePublicKey): ChainCall;
706
- /** Read whether a pubkey is a currently-authorized signer of the account. */
707
- isAuthorizedSigner(accountAddress: string, signer: DevicePublicKey): Promise<boolean>;
801
+ private submitInner;
708
802
  /**
709
- * Compute the signature payload for an outgoing transaction: given the chain's
710
- * tx hash, obtain a device assertion and serialize it to the chain's expected
711
- * signature encoding.
803
+ * Write data entries (add a factor / device slot) which create NEW subentries
804
+ * that each need ~0.5 XLM of reserve. A relayer-sponsored account holds no XLM,
805
+ * so the write must be sponsored by the relayer (source + sponsor), exactly like
806
+ * account creation — a plain fee-bump would fail with `op_low_reserve`.
807
+ * - sponsored (default) → with a relayer, build a sponsored write (relayer
808
+ * source + begin/end sponsoring), control-sign the account ops, relay
809
+ * co-signs + submits; falls back to self-funded if no relayer;
810
+ * - `{ sponsored: false }` → the account writes directly (it must hold its
811
+ * own reserve for the new subentries).
712
812
  */
713
- buildSignature(txHash: bigint): Promise<string[]>;
813
+ private submitDataWrite;
814
+ private requireControl;
815
+ private requireUnlocked;
714
816
  }
715
817
 
716
818
  /**
@@ -813,8 +915,12 @@ interface ConnectOptions {
813
915
  stellarRelayer?: StellarRelayer;
814
916
  /** Self-funded source/fee-payer Stellar keypair when no relayer is configured. */
815
917
  stellarSourceKeypair?: Keypair$1;
816
- /** Factory contract id override (else the per-network default). */
817
- factoryId?: string;
918
+ /**
919
+ * This device's ECDH unwrap key for the Stellar control-key envelope. Defaults
920
+ * to a persisted `WebCryptoDeviceUnwrapKey` in the browser; pass your own on
921
+ * React Native / server.
922
+ */
923
+ stellarDeviceKey?: DeviceUnwrapKey;
818
924
  }
819
925
  /** Whether this device can already operate the wallet, or needs to be added. */
820
926
  type ConnectStatus = "ready" | "needs-device-approval";
@@ -835,6 +941,13 @@ interface RecoveryOptions {
835
941
  classHash?: string;
836
942
  /** Off-chain user_id -> wallet map. Defaults to the hosted registry. */
837
943
  registry?: WalletRegistry;
944
+ /**
945
+ * Skip the registry entirely by passing the account address directly. With
946
+ * the seed-only derivation, the user can recompute the address from
947
+ * (userId, appSalt) alone — so recovery no longer depends on the Cavos
948
+ * backend. Pass this to make recovery fully self-custodial.
949
+ */
950
+ address?: string;
838
951
  /** Override the new device's signer (native / tests); default WebCrypto. */
839
952
  createSigner?: (keyId: string) => Promise<DeviceSigner>;
840
953
  }
@@ -863,6 +976,9 @@ declare class Cavos {
863
976
  readonly chain: "starknet";
864
977
  /** Request id of the pending device-addition, when status is needs-device-approval. */
865
978
  pendingRequestId: string | null;
979
+ /** True when this connect just created & deployed a brand-new account (first
980
+ * sign-up), so the UI can offer a one-time "secure your account" step. */
981
+ isNewAccount: boolean;
866
982
  private constructor();
867
983
  /**
868
984
  * Unified entry point. Pick a `chain` and an `network` environment; the kit
@@ -879,11 +995,14 @@ declare class Cavos {
879
995
  /** This device's public key (e.g. to request addition to an existing wallet). */
880
996
  get publicKey(): DevicePublicKey;
881
997
  /** Execute a sponsored (gasless) multicall, signed silently by the device. */
882
- execute(calls: ChainCall[]): Promise<{
998
+ execute(calls: ChainCall[], opts?: ExecuteOptions): Promise<{
883
999
  transactionHash: string;
884
1000
  }>;
885
- /** Authorize an additional device signer (sponsored). Self-submitted. */
886
- addSigner(pubkey: DevicePublicKey): Promise<{
1001
+ /**
1002
+ * Authorize an additional device signer. Sponsored by default; pass
1003
+ * `{ sponsored: false }` to pay the fee from the account's own ETH balance.
1004
+ */
1005
+ addSigner(pubkey: DevicePublicKey, opts?: ExecuteOptions): Promise<{
887
1006
  transactionHash: string;
888
1007
  }>;
889
1008
  /**
@@ -893,19 +1012,27 @@ declare class Cavos {
893
1012
  * approver. Call this whenever the app decides to prompt "turn on device
894
1013
  * approvals". Returns the passkey's public key + the enrollment tx hash.
895
1014
  */
896
- enrollPasskey(passkey: PasskeySigner, params: PasskeyEnrollParams): Promise<{
1015
+ enrollPasskey(passkey: PasskeySigner, params: PasskeyEnrollParams, opts?: ExecuteOptions): Promise<{
897
1016
  publicKey: DevicePublicKey;
898
1017
  transactionHash?: string;
899
1018
  }>;
900
1019
  /**
901
- * Register an ALREADY-enrolled passkey public key as an approver (gasless,
902
- * device-signed). Idempotent. Use this to register ONE passkey across multiple
903
- * chains without re-prompting `passkey.enroll()` on each: enroll once, then
904
- * call `addApprover(pubkey)` on each chain's wallet.
1020
+ * Register an ALREADY-enrolled passkey public key as an approver (gasless by
1021
+ * default, device-signed). Idempotent. Use this to register ONE passkey across
1022
+ * multiple chains without re-prompting `passkey.enroll()` on each: enroll once,
1023
+ * then call `addApprover(pubkey)` on each chain's wallet. Pass
1024
+ * `{ sponsored: false }` to pay the fee from the account's own balance.
905
1025
  */
906
- addApprover(pubkey: DevicePublicKey): Promise<{
1026
+ addApprover(pubkey: DevicePublicKey, opts?: ExecuteOptions): Promise<{
907
1027
  transactionHash?: string;
908
1028
  }>;
1029
+ /** True if this account already has a passkey enrolled as an approver, so a
1030
+ * new device can be approved with the passkey instead of the email flow. */
1031
+ hasPasskey(): Promise<boolean>;
1032
+ /** Re-read (from chain) whether THIS device is now an authorized signer.
1033
+ * Cheap and side-effect free — used to poll for readiness after a passkey /
1034
+ * device approval submits, before the new signer is indexed. */
1035
+ isReady(): Promise<boolean>;
909
1036
  /**
910
1037
  * From a brand-new browser (status `needs-device-approval`), use the user's
911
1038
  * synced passkey to authorize adding THIS device — no trip back to an already-
@@ -950,7 +1077,7 @@ declare class Cavos {
950
1077
  * add_signer (gasless). Returns the transaction hash (or undefined when the
951
1078
  * backup was already set up).
952
1079
  */
953
- setupRecovery(code: string): Promise<{
1080
+ setupRecovery(code: string, opts?: ExecuteOptions): Promise<{
954
1081
  transactionHash: string;
955
1082
  } | undefined>;
956
1083
  /**
@@ -964,8 +1091,10 @@ declare class Cavos {
964
1091
  */
965
1092
  static recover(opts: RecoveryOptions): Promise<Cavos>;
966
1093
  }
967
- /** A chain wallet that can approve THIS device via a passkey (implemented by
968
- * `Cavos`, `CavosSolana`, `CavosStellar`). */
1094
+ /** A chain wallet that can approve THIS device via a batched WebAuthn assertion
1095
+ * (implemented by `Cavos` and `CavosSolana`). Classic Stellar uses a WebAuthn PRF
1096
+ * factor instead (`CavosStellar.approveThisDeviceWithPasskey`), so it is
1097
+ * not part of this batch. */
969
1098
  interface PasskeyApprovable {
970
1099
  readonly chain: string;
971
1100
  readonly status: string;
@@ -984,11 +1113,12 @@ interface PasskeyApprovable {
984
1113
  * for all of them. Only wallets whose status is `needs-device-approval` are
985
1114
  * touched. Returns the per-chain tx hashes.
986
1115
  *
987
- * await approveDeviceEverywhere([starknet, solana, stellar], passkey);
1116
+ * await approveDeviceEverywhere([starknet, solana], passkey);
988
1117
  */
989
1118
  declare function approveDeviceEverywhere(wallets: PasskeyApprovable[], passkey: PasskeySigner): Promise<{
990
1119
  chain: string;
991
- transactionHash: string;
1120
+ transactionHash?: string;
1121
+ error?: string;
992
1122
  }[]>;
993
1123
 
994
- export { batchChallenge as $, type AuthProvider as A, type RecoverStellarOptions as B, type ChainAdapter as C, type DevicePublicKey as D, type EnrolledPasskey as E, FACTORY_CONTRACT_ID as F, type RecoveryOptions as G, SECP256R1_PROGRAM_ID as H, type Identity as I, SOLANA_NETWORKS as J, STELLAR_NETWORKS as K, SolanaAdapter as L, type SolanaAdapterOptions as M, NATIVE_SAC_ID as N, type SolanaNetwork as O, type PendingDeviceRequest as P, SolanaRelayer as Q, type RegisteredWallet as R, type StellarNetwork as S, type SolanaRelayerOptions as T, StaticIdentity as U, StellarRelayer as V, type WalletRegistry as W, type StellarRelayerOptions as X, anchorDiscriminator as Y, approveDeviceEverywhere as Z, base64urlEncode as _, type RecoveryClient as a, buildSecp256r1Instruction as a0, compressedPubkey as a1, encodeLowSSignature as a2, lowS as a3, recoverCandidatePublicKeys as a4, serializeInstructions as a5, webauthnDigest as a6, type DeviceSigner as b, type DeviceSignature as c, type ComputeAddressParams as d, type ChainCall as e, type PasskeyAssertion as f, Cavos as g, CavosSolana as h, CavosStellar as i, type CavosWallet as j, type Chain as k, type ConnectOptions as l, type ConnectSolanaOptions as m, type ConnectStatus as n, type ConnectStellarOptions as o, DEVICE_ACCOUNT_PROGRAM_ID as p, DEVICE_ACCOUNT_WASM_HASH as q, InMemoryWalletRegistry as r, type InstructionAccount as s, type InstructionData as t, type NetworkEnv as u, type PasskeyApprovable as v, type PasskeyEnrollParams as w, PasskeySigner as x, type PasskeySignerOptions as y, type RecoverSolanaOptions as z };
1124
+ export { anchorDiscriminator as $, type AuthProvider as A, type RecoveryOptions as B, type ChainAdapter as C, type DevicePublicKey as D, type EnrolledPasskey as E, SECP256R1_PROGRAM_ID as F, SOLANA_NETWORKS as G, HORIZON_URL as H, type Identity as I, STELLAR_NETWORKS as J, SolanaAdapter as K, LocalDeviceUnwrapKey as L, type SolanaAdapterOptions as M, type NetworkEnv as N, type SolanaNetwork as O, type PendingDeviceRequest as P, SolanaRelayer as Q, type RegisteredWallet as R, type StellarNetwork as S, type SolanaRelayerOptions as T, StaticIdentity as U, type StellarConnectStatus as V, type WalletRegistry as W, type StellarRelayKind as X, StellarRelayer as Y, type StellarRelayerOptions as Z, XLM_DECIMALS as _, type RecoveryClient as a, approveDeviceEverywhere as a0, base64urlEncode as a1, batchChallenge as a2, buildSecp256r1Instruction as a3, compressedPubkey as a4, deviceSlotId as a5, encodeLowSSignature as a6, lowS as a7, recoverCandidatePublicKeys as a8, serializeInstructions as a9, webauthnDigest as aa, type DeviceSigner as b, type DeviceSignature as c, type ComputeAddressParams as d, type ChainCall as e, type PasskeyAssertion as f, type DeviceUnwrapKey as g, Cavos as h, CavosSolana as i, CavosStellar as j, type CavosWallet as k, type Chain as l, type ConnectOptions as m, type ConnectSolanaOptions as n, type ConnectStatus as o, type ConnectStellarOptions as p, DEVICE_ACCOUNT_PROGRAM_ID as q, type ExecuteOptions as r, InMemoryWalletRegistry as s, type InstructionAccount as t, type InstructionData as u, type PasskeyApprovable as v, type PasskeyEnrollParams as w, PasskeySigner as x, type PasskeySignerOptions as y, type RecoverSolanaOptions as z };