Cavos

Starknet

Use @cavos/kit on Starknet — silent device signing, gasless execution via paymaster, and the Cairo DeviceAccount.

Starknet is one of the current adapters in Cavos' every-chain architecture. A deterministic address derived from the user's identity is controlled by a silent secp256r1 (P-256) device key, validated on-chain by the Cairo DeviceAccount account contract. Deploy and execution are sponsored by the Cavos paymaster, so the user pays no gas.

Connect

Cavos.connect with chain: "starknet" (or chains: ["starknet"]) does everything: authenticate, derive the deterministic address, create/load the silent device key, and wire up the gas sponsor. Connect never deploys. The account is created lazily on the first execute.

TypeScript
import { Cavos } from "@cavos/kit";

const session = await Cavos.connect({
  chains: ["starknet"],
  defaultChain: "starknet",
  network: "testnet",                   // "testnet" (sepolia) | "mainnet"
  appSalt: "my-app",                    // device-key namespace
  identity: {                           // from your login (see Authentication)
    userId: user.id,
    email: user.email,
  },
  appId: process.env.NEXT_PUBLIC_CAVOS_APP_ID,
  paymasterApiKey: process.env.NEXT_PUBLIC_CAVOS_PAYMASTER_API_KEY!, // app-scoped, client-visible
});

const wallet = session.wallet("starknet");
console.log(wallet.address);            // deterministic address
console.log(wallet.status);             // "undeployed" | "ready" | "needs-device-approval"

wallet.chain narrows to "starknet". The instance members (address, status, execute, addSigner, setupRecovery) are documented in the API reference.

Status values

StatusMeaningCan execute?
undeployedAddress derived, no on-chain account yetYes — first execute deploys + runs atomically
readyAccount deployed and this device authorizedYes
needs-device-approvalAccount deployed but this device not authorizedNo — route to device approval

Lazy deploy: An undeployed wallet can sign messages and execute. The first execute deploys the account and runs the user's calls in a single sponsored transaction. After that, status becomes ready.

Execute a transaction

Both undeployed and ready wallets can execute. Only needs-device-approval must be handled differently.

TypeScript
if (wallet.chain === "starknet" && wallet.status !== "needs-device-approval") {
  const { transactionHash } = await wallet.execute([
    {
      contractAddress: STRK_TOKEN,
      entrypoint: "approve",
      calldata: [spender, amountLow, amountHigh],
    },
  ]);
  console.log(transactionHash);
}

When the account is undeployed, this call deploys the account first, then executes the calls — atomically in a single paymaster-sponsored transaction.

If status is "needs-device-approval", this device is new to an existing wallet. Approve it from an already-registered device — see Multi-device or Passkeys.

How signing works

The device key is a non-extractable secp256r1 key held in IndexedDB via WebCrypto. When you call execute:

  1. starknet.js computes the v3 transaction hash.
  2. The device key signs sha256(tx_hash) — WebCrypto's ECDSA hashes the message internally, so there is no user interaction.
  3. The signature is serialized as 5 felts: [r_low, r_high, s_low, s_high, y_parity].
  4. On-chain, DeviceAccount.__validate__ recomputes sha256(tx_hash), normalizes high-s, recovers the secp256r1 signer, and checks it is authorized.

This 5-felt encoding is byte-compatible with the contract and covered by a cross-checked test in the account-contracts repo.

Gasless execution

execute routes through the Cavos paymaster (SNIP-9 execute_from_outside), so the user pays no gas. Both account deployment (first execute) and every subsequent execution are sponsored — the user never needs ETH or STRK.

Sign messages

Sign arbitrary messages off-chain with the device key. Works on both undeployed and ready wallets:

TypeScript
const sig = await wallet.signMessage("Hello, Cavos!");
// sig.signature: Uint8Array (64 bytes, r||s)
// sig.publicKey: "04..." (uncompressed secp256r1)
// sig.curve: "secp256r1"

The message is prefixed with "Cavos Signed Message:\n<len>\n" before signing.

Recovery and passkeys

Set up recovery factors so the user can regain access from a new device:

TypeScript
// On a ready or undeployed wallet
const code = generateRecoveryCode();
await wallet.setupRecovery(code);      // registers backup signer (pending if undeployed)
// Show `code` to the user once — the kit never persists it

For undeployed wallets, setupRecovery stores the backup signer pending. It gets included in the first deploy transaction automatically.

See Recovery and Passkeys for the full model.

Advanced: a plain starknet.js Account

StarknetDeviceSigner is a drop-in starknet.js SignerInterface, so you can plug the device key into a standard Account and your own paymaster flow:

TypeScript
import { Account, RpcProvider } from "starknet";
import { StarknetDeviceSigner, WebCryptoSigner } from "@cavos/kit";

const provider = new RpcProvider({ nodeUrl: "https://api.cartridge.gg/x/starknet/sepolia" });
const signer = await WebCryptoSigner.loadOrCreate({ keyId: address });
const account = new Account({
  provider,
  address,
  signer: new StarknetDeviceSigner(signer),
  cairoVersion: "1",
});

Gas estimation gotcha (self-funded path). secp256r1 validation is heavy (~27M L2 gas). Default fee estimation uses SKIP_VALIDATE, which under-bounds and causes "Out of gas". Estimate with validation on and pass those bounds:

TypeScript
const fee = await account.estimateInvokeFee(calls, { skipValidate: false });
await account.execute(calls, { resourceBounds: fee.resourceBounds });

The Cavos paymaster path handles this for you — this only matters when you submit transactions yourself.

Networks & constants

TypeScript
import { STARKNET_NETWORKS, DEVICE_ACCOUNT_CLASS_HASH } from "@cavos/kit";

STARKNET_NETWORKS.sepolia;            // RPC + chain config
DEVICE_ACCOUNT_CLASS_HASH.sepolia;    // deployed DeviceAccount class hash

network accepts "testnet" (resolves to sepolia) and "mainnet". The class hash is the declared DeviceAccount contract; the kit ships the current values.

What's available

  • ✅ Silent secp256r1 device signer, deterministic address.
  • ✅ Lazy deploy — first execute creates the account atomically.
  • ✅ Gasless execute + deployment via the Cavos paymaster.
  • ✅ Sign messages off-chain while undeployed.
  • Multi-device approval + recovery (recovery-code backup) — non-custodial.
  • Passkeys as approvers for new-device authorization.

For the cross-chain model and how Solana and Stellar compare, see Chains, Solana, and Stellar.

On this page