Opinionated React Native crypto x AI chat app boilerplate with embedded wallet support, conversational AI, and dynamic UI component injection.
mallory/
├── apps/
│ ├── client/ # React Native app (iOS, Android, Web)
│ └── server/ # Backend API (Node.js + Express)
├── packages/
│ └── shared/ # Shared types and utilities
└── package.json # Workspace configuration
- 🔐 Authentication: Google OAuth via Supabase
- 💬 AI Chat: Streaming conversations with Claude
- 💰 Embedded Wallet: Grid-powered smart contract wallets
- 🔑 Client-Side Signing: Secure transaction signing (keys never leave device)
- 📱 Cross-Platform: iOS, Android, and Web from single codebase
- 🎨 Modern UI: Beautiful, responsive design with Reanimated
- 🏷️ Version Tracking: Automatic version display with git commit hash
- 🤖 AI Streaming: Claude integration with Server-Sent Events and extended thinking
- 🔧 AI Tools: Web search (Exa), user memory (Supermemory), and 20+ Nansen data APIs
- 💰 x402 Payments: Server-side implementation for premium data access
- 💎 Wallet Data: Price enrichment via Birdeye API
- 🔒 Secure Auth: Supabase JWT validation
- 🚀 Production Ready: Comprehensive testing infrastructure
- 🔄 Synchronized Versioning: Single command updates all packages
- 🏷️ Automatic Releases: GitHub releases created on version tags
- 📝 Generated Changelogs: Commit history automatically compiled
- Node.js 18+ or Bun
- Git
- Expo CLI (optional, included in dependencies)
git clone https://github.com/darkresearch/mallory.git
cd mallory
bun install# Copy from template
cp apps/client/.env.example apps/client/.env
# Required variables:
EXPO_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
EXPO_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
EXPO_PUBLIC_BACKEND_API_URL=http://localhost:3001
EXPO_PUBLIC_GRID_API_KEY=your-grid-api-key
EXPO_PUBLIC_GRID_ENV=sandbox# Copy from template
cp apps/server/.env.example apps/server/.env
# Required variables:
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key
ANTHROPIC_API_KEY=sk-ant-your-key
BIRDEYE_API_KEY=your-birdeye-key
GRID_API_KEY=your-grid-api-key
# Optional (for AI tools):
EXA_API_KEY=your-exa-key
# Infinite Memory (OpenMemory for infinite context):
OPENMEMORY_URL=http://localhost:8080
OPENMEMORY_API_KEY=your-openmemory-keybun run dev# Terminal 1 - Backend
bun run server
# Terminal 2 - Client (Web)
bun run clientThe client will be available at:
- Web: http://localhost:8081
- API: http://localhost:3001
See apps/client/README.md for detailed client documentation.
Key Commands:
cd apps/client
# Web
bun run web
# iOS (requires Mac + Xcode)
bun run ios
# Android (requires Android Studio)
bun run androidSee apps/server/README.md for detailed server documentation.
API Endpoints:
POST /api/chat- AI chat streaming with tool callingGET /api/wallet/holdings- Wallet holdings with price dataGET /health- Health check
AI Tools:
searchWeb- Web search via Exa (always available)addMemory- User memory via Supermemory (optional)nansen*- 20+ Nansen API endpoints for blockchain analytics (requires x402 payments)
Mallory uses Grid for embedded wallets:
- Non-Custodial: User private keys never exist - Grid uses secure enclaves and MPC
- Email-Based Auth: Simple OTP verification flow
- Session Secrets: Generated client-side, passed to backend only when needed for signing
- Smart Contract Wallets: Spending limits and programmable transactions
- Production Ready: Sandbox and production environments
- x402 Integration: Automatic micropayments for premium data APIs
Grid's architecture means neither the client nor server ever has access to user private keys, making it truly non-custodial while still providing seamless transaction signing.
The packages/shared directory contains TypeScript types and utilities shared between client and server:
import type { ChatRequest, HoldingsResponse } from '@darkresearch/mallory-shared';
import { X402PaymentService } from '@darkresearch/mallory-shared';Mallory has comprehensive test coverage: unit tests, integration tests, and E2E tests.
Run tests:
cd apps/client
# Fast tests (unit + integration)
bun test
# E2E tests (requires backend running)
bun run test:e2e
# AI-powered tests (optional - expensive)
# These use Claude to verify response completeness and test 200k+ token conversations
bun test __tests__/e2e/chat-message-flow.test.ts # ~5-10 min, ~$1-2
bun test __tests__/e2e/long-context.test.ts # ~10-20 min, ~$2-3CI/CD:
- Regular tests run on every PR
- AI tests only run when
[run-ai-tests]is in commit message:git commit -m "fix: improve streaming [run-ai-tests]"
See apps/client/tests/CHAT_STATE_TESTS.md for full testing documentation.
- Web: Deploy to Vercel, Netlify, or any static host
- iOS: Deploy via Expo EAS or native build
- Android: Deploy via Expo EAS or native build
See apps/client/README.md for details.
- Recommended: Railway, Render, Fly.io
- Node.js: Any Node.js 18+ hosting
See apps/server/README.md for details.
The 400 response means Supabase doesn’t know about the Google provider yet. Here’s the full fix, covering both the browser login flow and the native Expo app.
- Go to Supabase dashboard → your project → Authentication → Providers → Google.
- Toggle Enable on. Supabase now expects a Google Client ID/secret.
Make a note of the Redirect URL shown at the top (it looks like
https://<project-ref>.supabase.co/auth/v1/callback). You’ll paste that into Google Cloud Console in a moment.
- Open https://console.cloud.google.com/apis/credentials (select the project you’ll use for Mallory).
- Ensure the OAuth consent screen is configured (app name, support email, scopes, test users).
- Click Create credentials → OAuth client ID.
- Choose Web application (works for both the web and native flows Mallory uses).
- Fill in:
- Authorized redirect URIs: add the Supabase callback from step 1, e.g.
https://<project-ref>.supabase.co/auth/v1/callback - Authorized JavaScript origins aren’t required for Supabase, but you can add your local web origin (e.g.
http://localhost:8081) if you’re running the web client.
- Authorized redirect URIs: add the Supabase callback from step 1, e.g.
- Save. Copy the generated Client ID and Client secret.
What about
exp://…?
That kind of URI applies when you’re using Expo’s WebView-based AuthSession. Mallory’s native flow uses@react-native-google-signin/google-signin, which talks directly to Google Play Services and does not need (or accept) anexp://URI. So you can skip adding exp:// schemes here.
Still on the Google provider page in Supabase:
- Paste the Client ID and Client secret you just copied.
- Click Save. Supabase now accepts Google logins.
Whether you’re running the browser app or the Expo-native build, you need the same Google client ID available at runtime.
- Edit
apps/client/.env(or wherever you manage env vars) and set:Mallory’s native layer uses the “Web application” client for Android; for iOS you can optionally create an iOS-specific client and set it here.EXPO_PUBLIC_GOOGLE_ANDROID_CLIENT_ID=<the web client ID from Google> EXPO_PUBLIC_GOOGLE_IOS_CLIENT_ID=<optional iOS client ID if you generated one>
- If you’re running the web client, also ensure the same ID is present; the
config.tsmodule pulls from theseEXPO_PUBLIC_*variables. - Restart Expo / Metro (and rebuild Android/iOS if needed) so the new env values take effect.
- Web: open the browser client, click Continue with Google, finish the Google OAuth flow; you should be redirected to Supabase, then back into Mallory with a logged-in session.
- Expo + Android/iOS: rebuild (
bun run androidorbun run ios), trigger Continue with Google. The native Google Sign-In dialog should appear. After you grant access, Supabase will issue the session tokens and Mallory should log you in.
- Web (browser): your “local dev URL” is the origin where your web app runs (e.g.
http://localhost:8081). Add it under “Authorized JavaScript origins” if you need to load Google scripts directly (not strictly required for Supabase-based OAuth, but harmless). - Expo React Native: no
exp://redirect URI is required for the Google Sign-In library Mallory uses. Just ensure the web client ID is configured and Supabase has the provider enabled. Once those steps are done, the “Unsupported provider: provider is not enabled” error disappears for both web and native runs.
Mallory uses synchronized semantic versioning across all packages.
Include [release: v*.*.*] in your PR title:
feat: add new wallet feature [release: v0.2.0]
When merged to main, the version automatically bumps and a GitHub release is created! 🚀
bun scripts/sync-version.js 0.2.0
git add . && git commit -m "chore: bump version to 0.2.0"
git tag v0.2.0 && git push && git push --tagsSee VERSION.md for details.
Contributions welcome! Please read CONTRIBUTING.md first.
Apache License 2.0 - see LICENSE for details.
- 📧 Email: hello@darkresearch.ai
- 🐛 Issues: GitHub Issues
- 📚 Docs: Full Documentation
Built with:
- Expo - React Native framework
- Grid (Squads) - Embedded wallets
- Anthropic - Claude AI with extended thinking
- Exa - AI-powered web search
- Supermemory - User memory & RAG
- Supabase - Auth & database
- Birdeye - Solana market data
- Nansen - Blockchain analytics (via x402)
- Faremeter - x402 payment protocol
- streamdown-rn - React Native markdown streaming
Made with ❤️ by Dark