Skip to content
docs

Keys and storage

The job: turn 12 or 24 words into keys for every chain, the same way other wallets do. Otherwise a user who imports their MetaMask or Phantom phrase sees an empty wallet.

In the kit: core.

  • getRandomSeedPhrase(12 | 24) creates a phrase; isValidSeedPhrase() checks the wordlist and the checksum.
  • The phrase becomes a 64-byte seed, and keys are derived along the standard paths: BIP-32 for secp256k1 chains (EVM, Bitcoin, Tron, Zcash), SLIP-10 for ed25519 chains (Solana, TON, NEAR).
  • Paths are pinned by test vectors, so account i is the address MetaMask, Phantom, Tonkeeper, TronLink and MyNearWallet derive. See Chain ids and derivation.

Decisions you make:

  • 12 or 24 words. Both are standard; 24 is the default.
  • A BIP-39 passphrase or not. The SDK uses an empty one, like every major wallet. A passphrase changes every address, so a user who forgets it loses the funds.
  • Imports. keyring imports a seed phrase, a single private key for one chain, and the 24-word TON format of Tonkeeper, Tonhub and MyTonWallet. See the Keyring guide.

Rules:

  • Don’t keep the phrase as a JS string longer than the onboarding screen. A string cannot be wiped from memory. The SDK turns a phrase into the 64-byte seed the moment it gets it and keeps only the bytes; dispose() wipes them. Better still, keep the 64-byte seed in secure storage next to the phrase and unlock with it (new Wallet(seedBytes)): the words are then read only when the user opens “Show recovery phrase”.
  • Logging an object never shows a key. Seeds and private keys are not properties of any SDK object, so console.log(account), JSON.stringify(wallet) or a crash report shows addresses and public keys only. The conformance suite checks this for every chain.
  • Never send the phrase or the seed anywhere. That includes analytics, crash reports, logs and clipboard history.
  • Don’t let the OS screenshot the phrase screen. Block screenshots and the app-switcher preview while it is shown.

The job: keep the seed on the device, encrypted, unlocked only by the user.

In the kit:

  • Mobile: @anywalletkit/secure-storage-react-native, an Expo module (Swift + Kotlin) that plugs into keyring as its SecretStorage. Each secret is a Keychain item / Keystore-wrapped file that only Face ID / Touch ID / a fingerprint, with the device passcode as the fallback, can open, on every read; it never leaves the device and is unreadable while the phone is locked. It needs a dev or Release build (not Expo Go) and NSFaceIDUsageDescription in Info.plist:

    import { createSecureStorage } from '@anywalletkit/secure-storage-react-native'
    const secrets = createSecureStorage({ namespace: 'my-wallet' })
    const keyring = await Keyring.load({ storage, secrets, chains })
  • Browser extension: extension-kit has an encrypted vault (password or passkey) and a session that locks after 15 minutes idle. See dApp connection.

What to use on mobile:

Platform Store Notes
iOS Keychain Services Use an access control that asks for Face ID or the device passcode, so a user whose Face ID fails is not locked out
Android Android Keystore Ask for biometrics with a device-credential fallback
Expo / React Native @anywalletkit/secure-storage-react-native (above); in Expo Go, expo-secure-store with requireAuthentication One API over both platforms

Backup:

  • Show the phrase once, then ask the user to confirm a few of the words.
  • Never store the phrase in plain files, app storage or a screenshot.
  • “Export private key” for one account goes behind the same authentication as the phrase. keyring.exportPrivateKey returns the key in the format the chain’s other wallets import, checked against the account’s address. See Keyring.
  • Encrypted backups: @anywalletkit/seed-vault seals a secret into a password- or key-protected file, and @anywalletkit/backup-cloud keeps it in the app’s iCloud Drive container with the key in iCloud Keychain (Android is planned).

Any key that signs somewhere else works through externalSigner(): a hardware wallet, an MPC service, a Safe, or a backend signer for bots. The account behaves the same; only the sign call goes out. See Custom signer.