Why this matters now
The v0.3 roadmap gates ECO reward payouts on in-app wallets. Any path that encrypts on-chain funds is only as secure as the key store. Right now walletVault opens a plain new MMKV({ id: 'wallet-vault' }) with no encryptionKey, meaning every secret key (Stellar S… seed) is written as a cleartext string to the device's MMKV flat-file. A rooted device, ADB pull, or backup extraction yields every key with no further effort.
Problem / What
src/services/walletVault.ts instantiates MMKV without the encryptionKey option:
const storage = new MMKV({ id: 'wallet-vault' });
MMKV supports AES-256 encryption when an encryptionKey is provided. The key should be derived per-device using react-native-keychain (iOS Keychain / Android Keystore) so it is hardware-bound and never leaves the secure enclave. The flow is: on first launch, generate a random 32-byte key → store it in the OS secure enclave → pass it as encryptionKey to MMKV on every subsequent open.
Key Challenges
react-native-keychain is not yet a dependency; it must be added and linked.
- The migration path: existing users who already have a secret stored in the unencrypted vault must be transparently migrated (read the old value → re-write to the encrypted instance → delete from the plain instance) without losing their key.
- The encryption key itself must never be serialised to JS state, logs, or Zustand stores.
- On Android, Keystore key generation may throw on API < 23; a fallback (or minimum SDK guard) is needed.
Acceptance Criteria
Relevant files / functions
src/services/walletVault.ts — entire file
src/__tests__/walletVault.test.ts — extend to cover migration
package.json — add react-native-keychain
Out of scope
- Changing how secrets are used after retrieval (
signChallengeXDR, buildPaymentXDR).
- Biometric gate (Touch ID / Face ID) before key retrieval — that is a separate UX feature.
Why this matters now
The v0.3 roadmap gates ECO reward payouts on in-app wallets. Any path that encrypts on-chain funds is only as secure as the key store. Right now
walletVaultopens a plainnew MMKV({ id: 'wallet-vault' })with noencryptionKey, meaning every secret key (StellarS…seed) is written as a cleartext string to the device's MMKV flat-file. A rooted device, ADB pull, or backup extraction yields every key with no further effort.Problem / What
src/services/walletVault.tsinstantiates MMKV without theencryptionKeyoption:MMKV supports AES-256 encryption when an
encryptionKeyis provided. The key should be derived per-device usingreact-native-keychain(iOS Keychain / Android Keystore) so it is hardware-bound and never leaves the secure enclave. The flow is: on first launch, generate a random 32-byte key → store it in the OS secure enclave → pass it asencryptionKeyto MMKV on every subsequent open.Key Challenges
react-native-keychainis not yet a dependency; it must be added and linked.Acceptance Criteria
walletVaultopens MMKV with a device-boundencryptionKeysourced from the OS secure enclave.react-native-keychainis added as a pinned dependency and linked for both platforms.walletVault.test.tstests continue to pass (mock the keychain in the test environment).Relevant files / functions
src/services/walletVault.ts— entire filesrc/__tests__/walletVault.test.ts— extend to cover migrationpackage.json— addreact-native-keychainOut of scope
signChallengeXDR,buildPaymentXDR).