Skip to content

Conversation

@ignaciosantise
Copy link
Collaborator

@ignaciosantise ignaciosantise commented Jan 9, 2026

Summary

This PR introduces Expo Wallet — a complete sample WalletConnect wallet built with Expo SDK 54 and React Native 0.81. It demonstrates secure key management, WalletKit integration, and a plugin-based multi-chain architecture.

🎯 Purpose: Provide developers with a production-quality reference implementation for building WalletConnect-compatible wallets.

Features

  • 🔐 Secure Key Storage — Mnemonic stored in iOS Keychain / Android Keystore via expo-secure-store
  • Native Crypto — Fast cryptographic operations with react-native-quick-crypto
  • 🔗 WalletConnect v2 — Full WalletKit integration for dApp connections
  • 🏗️ Multi-Chain Ready — Plugin architecture for EVM (now) and Solana/Sui/TON/Tron (future)
  • 📱 QR Scanner — Scan WalletConnect QR codes to connect
  • 🎨 Modern UI — Tab navigation, modals, and custom components

Architecture

graph TB
    subgraph "App Entry"
        A[_layout.tsx] --> B[Polyfills]
        B --> C[useWalletInitialization]
    end

    subgraph "Secure Storage"
        D[(expo-secure-store<br/>Mnemonic)]
    end

    subgraph "Wallet Layer"
        E[EvmWallet<br/>viem HDAccount]
        F[WalletStore<br/>Zustand]
    end

    subgraph "WalletConnect"
        G[WalletKit]
        H[Request Handlers]
    end

    C --> D
    D --> E
    E --> F
    F --> G
    G --> H
    H --> E
Loading

Plugin Architecture (Multi-Chain)

graph TB
    subgraph "Wallet Store"
        Store[useWalletStore]
    end

    subgraph "Base Interface"
        IWallet[IWallet Interface]
    end

    subgraph "Chain Implementations"
        EVM[EvmWallet ✅]
        SOL[SolanaWallet 🔜]
        SUI[SuiWallet 🔜]
        TON[TonWallet 🔜]
    end

    IWallet --> EVM
    IWallet -.-> SOL
    IWallet -.-> SUI
    IWallet -.-> TON

    EVM --> Store
Loading

Data Flow

flowchart LR
    subgraph "Secure Storage"
        Mnemonic[(Mnemonic<br/>encrypted)]
    end

    subgraph "Memory Only"
        HDAccount[HDAccount<br/>private key]
    end

    subgraph "Public State"
        Address[Wallet Address]
        Sessions[WC Sessions]
    end

    Mnemonic -->|derive on init| HDAccount
    HDAccount -->|extract| Address
    Address --> Sessions
Loading

Project Structure

wallets/wallet-pay/
├── app/                      # Expo Router pages
│   ├── _layout.tsx          # Root layout (polyfills, init)
│   ├── (tabs)/              # Tab navigation
│   │   ├── index.tsx        # Home screen
│   │   ├── connected-apps.tsx
│   │   └── settings.tsx
│   ├── scanner.tsx          # QR code scanner
│   └── session-proposal.tsx # WC session approval modal
├── lib/                      # Core wallet logic
│   ├── base/wallet-base.ts  # IWallet interface
│   └── chains/evm/          # EVM implementation (viem)
├── stores/                   # Zustand state management
├── hooks/                    # React hooks (wallet init, WalletKit)
├── utils/                    # Utilities (storage, crypto, helpers)
├── components/               # UI components
├── constants/                # Theme, chains, spacing
└── patches/                  # bip39 patch for quick-crypto

Tech Stack

Category Technology
Framework Expo SDK 54, React Native 0.81
Language TypeScript 5.9 (strict)
Navigation Expo Router (file-based)
State Zustand
Crypto viem, react-native-quick-crypto
Storage expo-secure-store, react-native-mmkv
WalletConnect @reown/walletkit

Security

Data Storage Protection
Mnemonic expo-secure-store iOS Keychain / Android Keystore (encrypted)
Private Key Memory only Never written to disk
Addresses Zustand Public data, non-sensitive

Security Measures

  • ✅ Mnemonic validated before wallet restoration
  • ✅ WalletConnect URIs validated before pairing
  • eth_sign disabled (phishing attack vector)
  • ✅ Console logs wrapped with __DEV__ checks
  • ✅ Race condition protection on WalletKit init

Changes

Added

  • Complete Expo wallet app structure (app/, lib/, stores/, hooks/, utils/, components/, constants/)
  • EVM wallet implementation using viem (lib/chains/evm/)
  • WalletKit integration with session proposal handling
  • QR code scanner for WalletConnect pairing
  • Secure storage utilities with expo-secure-store
  • Zustand stores for wallet and settings state
  • Custom UI components (modal, scanner frame, header)
  • bip39 patch for react-native-quick-crypto compatibility
  • Documentation (README.md, AGENTS.md)

Stats

  • 85 files changed
  • ~19,000 lines added

Testing

  • Fresh install with npm install
  • App starts without errors on iOS
  • App starts without errors on Android
  • New wallet generates successfully (mnemonic stored securely)
  • QR scanner opens and scans WalletConnect URIs
  • Session proposal modal displays dApp info correctly
  • Session approval works and shows in Connected Apps
  • App restart restores wallet from secure storage

Documentation

Screenshots

Add screenshots of the wallet UI here

Related

* feat(wallet-pay): implement EVM wallet with secure key management

- Add viem-based EVM wallet implementation (lib/chains/evm/)
- Add secure mnemonic storage using expo-secure-store
- Add wallet store with Zustand for state management
- Add crypto polyfills for react-native-quick-crypto
- Add bip39 patch for native PBKDF2 performance
- Update WalletKit integration to use wallet addresses
- Configure babel module resolver for crypto aliases

Security: Mnemonic is not stored in wallet class memory,
only in secure storage. HDAccount with private key is kept
for signing operations.

Architecture follows plugin-based design for future
multi-chain support (Solana, Sui, TON, Tron).

* fix(wallet-pay): address PR review feedback

Security fixes:
- Disable eth_sign method (auto-reject) - phishing attack vector
- Add mnemonic validation before wallet restoration
- Add WalletConnect URI validation in QR scanner
- Add try-catch for JSON.parse in typed data parsing

Reliability fixes:
- Add initialization lock to prevent WalletKit race conditions
- Add error state to wallet initialization hook

Code quality:
- Wrap console.logs with __DEV__ check for production
- Add TODO comments for toast notifications

* docs(wallet-pay): update README with security documentation

- Rename to Expo Wallet
- Add architecture overview
- Document security considerations and hot wallet trade-offs
- List protected data and storage mechanisms
- Note eth_sign is disabled for security

* chore: address feedback
AGENTS.md:
- Detailed project overview and tech stack
- Architecture diagrams (mermaid) for init flow, plugin system, data flow
- Complete project structure documentation
- Coding conventions and patterns
- Security notes and common tasks

README.md:
- Add badges for Expo, React Native, TypeScript, WalletConnect
- Add architecture mermaid diagram
- Improve structure with emojis and sections
- Add roadmap section
- Link to AGENTS.md for detailed docs
…ons (#305)

* feat(wallet-pay): add session request modal for signing and transactions

- Add session-request.tsx modal screen for WalletConnect session_request events
- Create modal-content/ folder with modular content components:
  - SignMessageContent for personal_sign/eth_signTypedData requests
  - SendTransactionContent for eth_sendTransaction requests with accordion
- Add VerifyBadge component for WalletConnect Verify API status display
- Register session_request listener in _layout.tsx
- Add verify badge to session-proposal.tsx connect modal
- Fix hex message decoding in evm-wallet.ts for personal_sign
- Add helper functions: getRequestIntention, getSignParamsMessage,
  formatEthValue, getNativeTokenSymbol, truncateAddress
- Add caret-up-down.png icon for accordion toggle

* chore: address review comments
Copilot AI review requested due to automatic review settings January 9, 2026 18:06
@vercel
Copy link

vercel bot commented Jan 9, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
react-native-examples Ready Ready Preview, Comment Jan 9, 2026 6:44pm

@claude
Copy link

claude bot commented Jan 9, 2026

Claude finished @ignaciosantise's task in 1m 54s —— View job


Code Review Complete

Reviewed PR #306: feat: new wallet sample - Comprehensive WalletConnect wallet implementation with secure key management.


Found 5 issue(s)

Issue 1: Console.log in production code without DEV guard

ID: session-propo-console-log-a8f2
File: wallets/wallet-pay/app/session-proposal.tsx:47
Severity: LOW
Category: code_quality

Line 47 contains an unguarded console.log that will execute in production builds:

console.log('session', session);

Recommendation: Wrap in __DEV__ check or remove:

if (__DEV__) {
  console.log('session', session);
}

Issue 2: Missing error handling for WalletKit operations

ID: session-reque-error-handling-c3d1
File: wallets/wallet-pay/app/session-request.tsx:72-91
Severity: MEDIUM
Category: error_handling

The onApprove callback catches errors but only logs them without providing user feedback. Users won't know if the approval failed.

Recommendation: Display error message to user or implement toast notifications as indicated by TODOs in the codebase.

Issue 3: Race condition in WalletKit initialization check

ID: walletkit-race-condition-b5e7
File: wallets/wallet-pay/hooks/use-walletkit.ts:36-38
Severity: MEDIUM
Category: concurrency

The race condition protection uses isInitializing flag but doesn't handle the case where multiple components mount simultaneously before isInitializing is set to true. There's a brief window between checking if (walletKit || isInitializing) and setting isInitializing = true where a second call could slip through.

Recommendation: Use a Promise-based singleton pattern or atomic flag:

let initPromise: Promise<IWalletKit> | null = null;

async function init() {
  if (!walletReady || !evmAddress) return;
  if (walletKit) return;
  
  if (initPromise) {
    await initPromise;
    return;
  }
  
  initPromise = WalletKit.init({...});
  try {
    walletKit = await initPromise;
    stateListeners.forEach((listener) => listener(true));
  } finally {
    initPromise = null;
  }
}

Issue 4: Potential buffer overflow in hexToUtf8 conversion

ID: helpers-hex-conversion-d9a4
File: wallets/wallet-pay/utils/helpers.ts:165-176
Severity: LOW
Category: security

The hexToUtf8 function doesn't validate the maximum length of the hex string before conversion. Malicious dApps could send extremely large hex strings causing memory issues during conversion.

Recommendation: Add length validation:

function hexToUtf8(hex: string): string {
  try {
    const cleanHex = hex.startsWith('0x') ? hex.slice(2) : hex;
    if (cleanHex.length % 2 !== 0) return hex;
    if (cleanHex.length > 10000) return hex; // Max ~5KB string
    // ... rest of conversion
  } catch {
    return hex;
  }
}

Issue 5: eth_sendTransaction not fully implemented

ID: evm-handler-send-tx-incomplete-f1b3
File: wallets/wallet-pay/lib/chains/evm/evm-request-handler.ts:93-100
Severity: HIGH
Category: incomplete_implementation

The eth_sendTransaction case only signs the transaction but doesn't broadcast it to the network. The comment acknowledges this ("we'll handle separately") but this creates a functional gap - dApps requesting eth_sendTransaction expect the transaction to be broadcast and receive a transaction hash, not just a signature.

Recommendation: Either implement full broadcast functionality with RPC provider integration, or return a more appropriate error indicating the method is not yet supported:

case EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION: {
  // eth_sendTransaction requires broadcasting, which needs RPC provider setup
  return formatJsonRpcError(
    id,
    'eth_sendTransaction not yet supported. Use eth_signTransaction instead.'
  );
}

Automated Checks

✅ No external domain URLs detected outside approved domains (reown.com, walletconnect.com, walletconnect.org)
✅ No cache-control issues detected
✅ No GitHub Actions workflow security issues (no workflows in wallet-pay directory)
✅ No WalletConnect Pay architecture violations detected


Summary

This is a well-structured wallet implementation with good security practices. The main issues are:

  • 5 issues found: 1 HIGH (incomplete eth_sendTransaction), 2 MEDIUM (error handling, race condition), 2 LOW (console.log, buffer validation)
  • Key security measures are properly implemented: mnemonic in secure storage, eth_sign disabled, URI validation
  • Architecture follows clean plugin-based design with good separation of concerns

The HIGH severity issue with eth_sendTransaction should be addressed before production use - either implement full broadcast functionality or explicitly reject the method.


Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces Expo Wallet, a complete sample WalletConnect wallet implementation built with Expo SDK 54 and React Native 0.81. The implementation demonstrates secure key management using platform-native secure storage (iOS Keychain/Android Keystore), WalletKit integration for dApp connections, and a plugin-based multi-chain architecture currently supporting EVM chains with extensibility for future blockchain additions.

Changes:

  • Complete Expo wallet application structure with secure mnemonic storage, WalletKit integration, and QR scanning capabilities
  • EVM wallet implementation using viem with signing request handlers for personal_sign, eth_signTypedData, and transaction signing
  • Plugin-based multi-chain architecture with IWallet interface for future blockchain support (Solana, Sui, TON, Tron)

Reviewed changes

Copilot reviewed 52 out of 91 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
utils/*.ts Storage utilities, polyfills, mnemonic generation, and helper functions
stores/*.ts Zustand state management for wallet and settings
lib/**/*.ts Core wallet logic including IWallet interface and EVM implementation
hooks/*.ts React hooks for wallet initialization, WalletKit integration, and theming
components/**/*.tsx UI components including modals, buttons, text primitives, and scanner frame
app/**/*.tsx Expo Router pages for tabs, scanner, and session handling
constants/*.ts Theme configuration, EVM chains, signing methods, and spacing
Configuration files Package.json, babel, eslint, tsconfig, and app.json setup
Documentation README.md and AGENTS.md providing comprehensive project documentation
Comments suppressed due to low confidence (2)

wallets/wallet-pay/constants/theme.ts:1

  • The TODO comment suggests uncertainty about the Fonts configuration. This should be verified and either confirmed as correct or fixed, then the TODO removed. The font configurations appear complete for different platforms (iOS, web, default), so if they're working correctly, this comment should be removed.
    wallets/wallet-pay/components/primitives/text.tsx:1
  • The hardcoded font family 'KH Teka' in the styles should reference the font configuration from constants/theme.ts (the Fonts constant) for consistency and maintainability. This ensures font changes are managed in a single location.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@ignaciosantise ignaciosantise merged commit 21b7d9a into main Jan 9, 2026
4 of 5 checks passed
@ignaciosantise ignaciosantise deleted the chore/wallet-pay branch January 9, 2026 18:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants