Skip to main content

Why migrate

PIN-encrypted wallets are the shortest path to a working integration, but they are not the right default for production. A 6-digit PIN has roughly 20 bits of entropy — an attacker who obtains the encrypted ciphertext (e.g. via a leaky metadata field, a stolen backup, or any other ciphertext exposure) can brute-force it offline in seconds. Passkey authentication removes this vector:
  • The encryption key is derived from a hardware-backed WebAuthn credential (Face ID, Touch ID, Windows Hello, security key).
  • It never leaves the device’s secure element.
  • There is no low-entropy secret to crack.
This guide walks through migrating an existing PIN-only wallet to a passkey wallet using useMigrateWalletToPasskey.
Recommended for all production wallets. New wallets should be created with usePasskey: true from the start (see the Passkeys guide). This guide covers existing wallets that were created PIN-only.

Prerequisites

  • Wallet was created with a PIN (encryptKey) and currently has only encryptedPrivateKey set.
  • User is on a device that supports WebAuthn PRF (most modern browsers + Face ID / Touch ID / Windows Hello).
  • App is using @chipi-stack/nextjs ≥ v14.3.0 (which ships dual-key support for new wallets) or @chipi-stack/chipi-react ≥ v14.3.0.
  • @chipi-stack/chipi-passkey is installed.

How migration works

useMigrateWalletToPasskey performs an atomic key swap:
  1. The user’s current PIN is used to decrypt encryptedPrivateKey locally — if the PIN is wrong, migration aborts before touching anything else.
  2. A new passkey is created via WebAuthn (biometric prompt).
  3. The decrypted private key is re-encrypted using the passkey-derived key.
  4. The new ciphertext is sent to the backend via PATCH /chipi-wallets/update-encryption-details.
  5. The hook returns the updated wallet and the credentialIdpersist both.
The on-chain wallet address does not change. The same private key continues to control the same Starknet account; only the encryption envelope on the backend is replaced.

Migration component

After migration

Once migration succeeds:
  • wallet.encryptedPrivateKey is now passkey-encrypted.
  • All subsequent signing flows must use usePasskey: true. For example:
  • Store credentialId somewhere durable (your DB, Clerk privateMetadata via a server route, etc.). If localStorage is cleared, the SDK can recover the credential from the backend via /chipi-wallets/credential-recovery.

A note on PIN backup

useMigrateWalletToPasskey performs a single-key swap — after migration, the backend stores only the passkey-encrypted ciphertext. A wallet that was created PIN-only does not gain a PIN backup column through migration. If you want true dual-key (passkey primary, PIN backup), the path is to create new wallets with usePasskey: true from the start (see Passkeys → Create Wallet with Passkey + PIN). This sets both encryptedPrivateKey (passkey) and encryptedPrivateKeyBackup (PIN) at creation time, and the SDK’s useTransfer will automatically fall back to PIN if the passkey is unavailable. For migrated wallets, plan for passkey recovery via WebAuthn discoverable credentials and credentialId recovery rather than a PIN fallback.

Rollout strategy

A staged rollout reduces risk:
  1. Detect. On wallet load, check whether the wallet has a credentialId / passkey-encrypted state. If not, surface a one-time migration prompt.
  2. Prompt at a low-stakes moment. Don’t block the user mid-transfer. Show the upgrade flow on the next wallet screen visit, or after a successful (non-critical) action.
  3. Make it non-mandatory at first. Let users skip and re-prompt later. Track adoption.
  4. Mandate for high-value flows. Once adoption is high, gate sensitive flows (large transfers, treasury actions) behind passkey-only wallets.
  5. Monitor. Log migration attempts, successes, failures, and PRF-not-supported events so you can size the long tail.

Errors and edge cases