Understanding how PassPay is structured and how LazorKit integrates with the application.
┌─────────────────────────────────────────────────────────────────────────────┐
│ PASSPAY ARCHITECTURE │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ UI LAYER │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Welcome │ │ Wallet │ │ Transfer │ │ Memo │ │ Stake │ │
│ │ Screen │ │ Screen │ │ Screen │ │ Screen │ │ Screen │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │ │ │
└────────┼─────────────┼─────────────┼─────────────┼─────────────┼───────────┘
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ HOOKS LAYER │
│ │
│ ┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐ │
│ │ useWalletGuard │ │useLazorkitTrans. │ │ useSolBalance │ │
│ └─────────┬─────────┘ └─────────┬─────────┘ └─────────┬─────────┘ │
│ │ │ │ │
│ ┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐ │
│ │ useClipboard │ │ useTxHistory │ │ useColorScheme │ │
│ └───────────────────┘ └───────────────────┘ └───────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────────────────┐ │
│ │ useSession │ │
│ │ - Auto-restore session - Wallet sync - Expiry monitoring │ │
│ └───────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ SERVICES LAYER │
│ │
│ ┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐ │
│ │ transfer.ts │ │ staking.ts │ │ memo.ts │ │
│ │ - validation │ │ - stake accts │ │ - memo instr. │ │
│ │ - instructions │ │ - delegation │ │ - verification │ │
│ └─────────┬─────────┘ └─────────┬─────────┘ └─────────┬─────────┘ │
│ │ │ │ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ rpc.ts │ │
│ │ - Connection singleton │ │
│ │ - Request caching │ │
│ └───────────────────────────────┬─────────────────────────────────┘ │
│ │ │
└───────────────────────────────────┼─────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ LAZORKIT SDK │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ LazorKitProvider │ │
│ │ (Wraps entire application) │ │
│ └───────────────────────────────┬─────────────────────────────────┘ │
│ │ │
│ ┌──────────────┐ ┌─────────────────────────┐ ┌──────────────────┐ │
│ │ useWallet │ │ signAndSendTransaction │ │ signMessage │ │
│ └──────────────┘ └─────────────────────────┘ └──────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ EXTERNAL SERVICES │
│ │
│ ┌──────────────┐ ┌─────────────────────────┐ ┌──────────────────┐ │
│ │ LazorKit │ │ Solana Devnet │ │ Paymaster │ │
│ │ Portal │ │ RPC │ │ Service │ │
│ │ │ │ │ │ │ │
│ │ Authentication│ │ Blockchain │ │ Fee │ │
│ │ & Signing │ │ Transactions │ │ Sponsorship │ │
│ └──────────────┘ └─────────────────────────┘ └──────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
passpay-mobile/
├── app/ # 📱 Screens (Expo Router)
│ ├── _layout.tsx # Root layout with LazorKitProvider
│ ├── welcome.tsx # Welcome/onboarding screen
│ └── (tabs)/ # Tab-based navigation
│ ├── _layout.tsx # Tab configuration
│ ├── index.tsx # Wallet connection screen
│ ├── transfer.tsx # Gasless SOL transfers
│ ├── memo.tsx # On-chain memos
│ └── stake.tsx # Native SOL staking
│
├── components/ # 🎨 UI Components
│ ├── common/ # Shared components
│ │ ├── Logo.tsx # App logo component
│ │ └── ScreenHeader.tsx # Reusable header
│ ├── ui/ # UI primitives
│ │ └── icon-symbol.tsx # Icon components
│ └── haptic-tab.tsx # Tab with haptic feedback
│
├── features/ # 🎯 Feature Modules
│ ├── wallet/ # Wallet feature
│ │ ├── hooks/ # Wallet-specific hooks
│ │ │ ├── use-sol-balance.ts
│ │ │ └── use-wallet-guard.tsx
│ │ └── styles/ # Wallet screen styles
│ │ └── home.styles.ts
│ ├── transfer/ # Transfer feature
│ │ ├── services/ # Transfer business logic
│ │ │ └── transfer.service.ts
│ │ └── styles/ # Transfer screen styles
│ │ └── transfer.styles.ts
│ ├── staking/ # Staking feature
│ │ ├── services/ # Staking business logic
│ │ │ └── staking.service.ts
│ │ └── styles/ # Staking screen styles
│ │ └── stake.styles.ts
│ └── memo/ # Memo feature
│ ├── services/ # Memo business logic
│ │ └── memo.service.ts
│ └── styles/ # Memo screen styles
│ └── memo.styles.ts
│ └── session/ # Session feature
│ ├── hooks/ # Session hooks
│ │ └── use-session.ts
│ └── services/ # Session persistence
│ └── session.service.ts
│
├── hooks/ # 🪝 Shared React Hooks
│ ├── index.ts # Central exports
│ ├── use-lazorkit-transaction.ts # Transaction handling
│ ├── use-transaction-history.ts # History tracking
│ ├── use-clipboard.ts # Clipboard operations
│ └── use-color-scheme.ts # Theme detection
│
├── services/ # ⚙️ Shared Services
│ └── rpc.ts # Solana connection singleton
│
├── types/ # 📝 TypeScript Types
│ └── (shared type definitions)
│
├── utils/ # 🔧 Helper Functions
│ ├── helpers.ts # Common utilities
│ └── redirect-url.ts # Deep link URL builder
│
├── constants/ # 🎨 App Constants
│ └── theme.ts # Colors and theme values
│
├── styles/ # 💅 Shared Styles
│ ├── index.ts # Central exports
│ ├── shared.styles.ts # Shared styles
│ └── welcome.styles.ts # Welcome screen styles
│
├── __tests__/ # 🧪 Test Files
│ ├── utils/ # Utility tests
│ ├── services/ # Service tests
│ └── hooks/ # Hook tests
│
├── docs/ # 📚 Documentation
│ ├── README.md # Doc index
│ ├── tutorials/ # Step-by-step guides
│ └── ... # Other docs
│
├── polyfills.ts # 🔌 Required polyfills
├── index.js # 📍 App entry point
├── app.json # 📋 Expo configuration
└── package.json # 📦 Dependencies
User taps "Connect"
│
▼
┌─────────────────────┐
│ handleConnect() │ ← HomeScreen (index.tsx)
│ in screen │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ connect() │ ← useWallet (LazorKit SDK)
│ from useWallet │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ Open browser to │ ← LazorKit Portal
│ portal.lazor.sh │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ User authenticates │ ← Device Biometrics
│ with biometrics │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ Redirect back │ ← Deep Link (passpaymobile://)
│ to app │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ smartWalletPubkey │ ← App State Updated
│ is now available │
└─────────────────────┘
User initiates transaction
│
▼
┌─────────────────────┐
│ Screen Component │ ← e.g., TransferScreen
│ validates input │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ Service creates │ ← transfer.ts / staking.ts
│ instruction(s) │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ useLazorkitTx │ ← Custom Hook
│ execute() │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ signAndSendTx() │ ← LazorKit SDK
│ from useWallet │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ Portal opens for │ ← LazorKit Portal
│ biometric signing │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ Paymaster sponsors │ ← Paymaster Service
│ transaction fee │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ Transaction sent │ ← Solana Network
│ to Solana │
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ onSuccess callback │ ← Back to App
│ with signature │
└─────────────────────┘
<LazorKitProvider>
{" "}
// LazorKit SDK
<ThemeProvider>
{" "}
// React Navigation
<Stack>
{" "}
// Expo Router
<Stack.Screen name="welcome" />
<Stack.Screen
name="(tabs)" // Tab Navigator
>
<Tabs.Screen name="index" /> // Wallet
<Tabs.Screen name="transfer" /> // Transfer
<Tabs.Screen name="memo" /> // Memo
<Tabs.Screen name="stake" /> // Stake
</Stack.Screen>
</Stack>
</ThemeProvider>
</LazorKitProvider>┌─────────────────────────────────────────────────────────────────────────────┐
│ HOOK DEPENDENCIES │
└─────────────────────────────────────────────────────────────────────────────┘
useLazorkitTransaction
├── useWallet (LazorKit) // For signAndSendTransaction
├── useState (React) // For loading, error states
└── getRedirectUrl (utils) // For deep link
useWalletGuard
├── useWallet (LazorKit) // For isConnected, smartWalletPubkey
└── truncateAddress (utils) // For display formatting
useSolBalance
├── useWallet (LazorKit) // For smartWalletPubkey
├── useFocusEffect (Expo Router) // For screen focus
└── getConnection (services/rpc) // For balance fetch
useTransactionHistory
├── useState (React) // For history state
├── useCallback (React) // For memoized functions
└── getExplorerUrl (utils) // For explorer links
useClipboard
├── Clipboard (expo-clipboard) // For copy
└── Haptics (expo-haptics) // For feedback
Problem: Solana libraries require Buffer and crypto before they're imported.
Solution: Custom entry point (index.js) loads polyfills before Expo Router.
// index.js - loads BEFORE expo-router
import "./polyfills"; // Buffer, crypto, URL polyfills
import "expo-router/entry"; // Then start the appProblem: Creating multiple RPC connections causes rate limiting.
Solution: Singleton pattern in services/rpc.ts.
let _connection: Connection | null = null;
export function getConnection(): Connection {
if (!_connection) {
_connection = new Connection(DEVNET_RPC, { commitment: "confirmed" });
}
return _connection;
}Problem: Stake accounts normally require a new keypair as signer.
Solution: Use createAccountWithSeed to derive addresses without extra signers.
const stakeAccountPubkey = await PublicKey.createWithSeed(
walletPubkey, // Base
`stake:${Date.now()}`, // Unique seed
StakeProgram.programId // Program
);Problem: Repeated boilerplate across screens.
Solution: Extract patterns into reusable hooks.
// Before: 50+ lines per screen
// After: 5 lines
const { execute, loading } = useLazorkitTransaction({ gasless: true });
await execute({ instructions: [ix] });Problem: Different redirect schemes for Expo Go vs standalone builds.
Solution: Dynamic URL generation based on environment.
export function getRedirectUrl(path: string = ""): string {
const isExpoGo = Constants.appOwnership === "expo";
if (isExpoGo) {
return Linking.createURL(path); // exp://...
}
return `passpaymobile://${path}`; // Custom scheme
}| Aspect | Implementation |
|---|---|
| Private Keys | Stored in device Secure Enclave |
| Signing | Biometric authentication required |
| Session | Managed by LazorKit SDK |
| Transaction Verification | Portal confirms before signing |
| Aspect | Implementation |
|---|---|
| Input Validation | Validate addresses and amounts before transactions |
| Error Handling | Graceful failures with user-friendly messages |
| RPC Security | Use reputable RPC endpoints |
| Deep Link Validation | Validate redirect URLs |
// Screens loaded on navigation, not app start
<Stack.Screen name="stake" options={{ lazy: true }} />const fetchBalance = useCallback(async () => {
// Only recreated when dependencies change
}, [publicKey, connection]);// services/rpc.ts
const cache = new Map<string, { data: T; timestamp: number }>();
const CACHE_TTL = 30_000; // 30 seconds// Only fetch when screen is visible
useFocusEffect(
useCallback(() => {
fetchData();
}, [])
);- New Screen: Add to
app/(tabs)/and register in tab layout - New Feature Module: Create in
features/<feature-name>/ - Feature Structure:
services/- Business logicstyles/- Screen styleshooks/- Feature-specific hooks (optional)
- Shared Hooks: Add to root
hooks/if truly shared across features - Update Docs: Add tutorial to
docs/tutorials/
features/token-transfer/
├── services/
│ └── token-transfer.service.ts # SPL token transfer logic
├── styles/
│ └── token-transfer.styles.ts # Screen styling
└── hooks/
└── use-token-balance.ts # Token balance fetching
app/(tabs)/token-transfer.tsx # Screen implementation
1. Create services/token-transfer.ts
- createTokenTransferInstruction()
- getTokenBalance()
2. Create hooks/use-token-balance.ts
- Similar to useSolBalance
3. Create app/(tabs)/token-transfer.tsx
- Use hooks and services
4. Add styles/token-transfer.styles.ts
5. Add tab to app/(tabs)/_layout.tsx