Guidance for AI assistants (Claude Code, Gemini, Codex, etc.) collaborating on this repository.
This document orients coding agents to the repo structure, development workflow, coding standards, and core architectural patterns used across Gem Wallet Core.
Gem Wallet Core is a Rust-based cryptocurrency wallet backend engine supporting 35+ blockchain networks. It is a Cargo workspace with 40+ crates covering transaction processing, asset management, DeFi integrations, and cross-platform mobile support.
- API Server (
apps/api/): REST API with WebSocket price streaming - Daemon (
apps/daemon/): Background services for asset updates, push notifications, transaction indexing - Parser (
apps/parser/): Multi-chain transaction parsing with message queue integration - Setup (
apps/setup/): Database initialization
Shared Rust library compiled to iOS Swift Package and Android AAR using UniFFI bindings. Contains blockchain RPC clients, swap integrations, payment URI decoding, and message signing.
- Key module:
gemstone::swapper— swapper module for on-device swap integrations
Individual gem_* crates for each blockchain with unified RPC client patterns:
- Bitcoin family: Bitcoin, Bitcoin Cash, Litecoin, Dogecoin
- Ethereum & L2s: Ethereum, Polygon, Arbitrum, Optimism, Base, zkSync, Linea
- Alternative L1s: Solana, Sui, TON, Aptos, NEAR, Stellar, Algorand
- Cosmos ecosystem: Cosmos Hub, Osmosis, Celestia, Injective, Sei, Noble
primitives/: Central types and models shared across the systemstorage/: Database models, migrations, and data access layer using Diesel ORMname_resolver/: ENS, SNS, and other naming service integrationsgem_evm/: EVM blockchain support with unified RPC client patternsgem_jsonrpc/: Internal JSON-RPC client library (replaces external alloy dependencies)gem_client/: Client trait abstraction used across services; implementations:ReqwestClient(backend) andAlienProvider(mobile)serde_serializers/: Custom Serde serializers/deserializers used across cratessecurity_provider/: Security provider abstractions and utilitiesgem_hypercore/: Perpetuals (perps) support via Hyperliquid integrationfiat/: Integration with fiat providers (MoonPay, Transak, Mercuryo, Banxa)nft/: NFT marketplace integrations (OpenSea, Magic Eden, NFTScan)pricer/: Asset pricing from CoinGecko and DEX sourceslocalizer/: i18n support for 20+ languages using Fluent
- Framework: Rust workspace with Rocket web framework
- Database: PostgreSQL (primary), Redis (caching)
- Message Queue: RabbitMQ with Lapin
- RPC: Custom
gem_jsonrpcclient library for blockchain interactions - Mobile: UniFFI for iOS/Android bindings
- Serialization: Serde with custom serializers
- Async: Tokio runtime
- Testing: Built-in Rust testing with integration tests
All commands use the just task runner. Run from the workspace root unless specified.
just build: Build the workspacejust build-gemstone: Build cross-platform libraryjust gemstone build-ios: Build iOS Swift Package (run ingemstone/)just gemstone build-android: Build Android AAR (run ingemstone/)
just test-workspace: Run all workspace testsjust test-all: Run all tests including integrationjust test <CRATE>: Test a specific cratejust gemstone test-ios: Run iOS integration tests (run ingemstone/)cargo test --test integration_test --package <CRATE> --features <FEATURE>: Run integration tests manually
just format: Format all codejust lint: Run clippy with warnings as errorsjust fix: Auto-fix clippy issuesjust unused: Find unused dependencies with cargo-machete
just migrate: Run Diesel migrationsjust setup-services: Start Docker services (PostgreSQL, Redis, Meilisearch, RabbitMQ)
just gemstone install-ios-targets: Install iOS Rust targets (run ingemstone/)just gemstone install-android-targets: Install Android Rust targets andcargo-ndk(run ingemstone/)- Note: Mobile builds require UniFFI bindings generation and platform-specific compilation
just localize: Update localization filesjust generate-ts-primitives: Generate TypeScript types from Rustjust outdated: Check for outdated dependencies
Follow the existing code style patterns unless explicitly asked to change.
- Line length: 160 characters maximum (configured in
rustfmt.toml) - Indentation: 4 spaces (Rust standard)
- Imports: Automatically reordered with rustfmt
- ALWAYS run
just formatbefore committing - Formatter enforces consistent style across all crates/workspace
- Write descriptive messages following conventional commit format
- Files/modules:
snake_case(e.g.,asset_id.rs,chain_address.rs) - Crates: Prefixed naming (
gem_*for blockchains,security_*for security) - Functions/variables:
snake_case - Structs/enums:
PascalCase - Constants:
SCREAMING_SNAKE_CASE
- Standard library imports first
- External crate imports
- Local crate imports
- Module re-exports with
pub use
IMPORTANT: Always import models and types at the top of the file. Never use inline imports inside functions (e.g., use crate::models::SomeType inside a function). Declare all imports in the file header.
- Use
thiserrorfor custom error types - Implement
Fromtraits for error conversion - Use consistent
Result<T, Error>return types - Propagate errors with the
?operator
- Separate database models from domain primitives
- Use
as_primitive()methods for conversion - Diesel ORM with PostgreSQL backend
- Support transactions and upserts
- Tokio runtime throughout
- Async client structs returning
Result<T, Error> - Use
Arc<tokio::sync::Mutex<T>>for shared async state
- One crate per blockchain using unified RPC client patterns
- UniFFI bindings require careful Rust API design for mobile compatibility
- Use
BigDecimalfor financial precision - Use async/await with Tokio across services
- Database models use Diesel ORM with automatic migrations
- Consider cross-platform performance constraints for mobile
Services access repositories through direct methods on DatabaseClient. This pattern:
- Separates data access and business logic
- Assigns each repository a specific domain (assets, devices, etc.)
- Implements all repository traits directly on
DatabaseClient - Returns primitive types from repository methods, not database models
- Simplifies the API via direct method calls
Example:
pub struct AssetsClient {
database: Box<DatabaseClient>,
}
impl AssetsClient {
pub fn new(database_url: &str) -> Self {
let database = Box::new(DatabaseClient::new(database_url));
Self { database }
}
pub fn get_asset(&mut self, id: &str) -> Result<Asset, Box<dyn Error + Send + Sync>> {
self.database.assets().get_asset(id)
}
pub fn get_assets_by_device_id(&mut self, device_id: &str) -> Result<Vec<Asset>, Box<dyn Error + Send + Sync>> {
let subscriptions = self.database.subscriptions().get_subscriptions_by_device_id(device_id)?;
// ... process subscriptions
self.database.assets().get_assets(asset_ids)
}
}Direct repository access methods available on DatabaseClient include:
assets()- Asset operationsdevices()- Device operationssubscriptions()- Subscription operationsprices()- Price operationstransactions()- Transaction operations- And more...
- Use
gem_jsonrpc::JsonRpcClientfor blockchain RPC interactions - Prefer
alloy_primitives::hex::encode_prefixed()for hex encoding with0xprefix - Use
alloy_primitives::Address::to_string()instead of manual formatting - RPC calls expect hex strings directly; avoid double encoding
- Use
JsonRpcClient::batch_call()for batch operations - Propagate errors via
JsonRpcError
- Each blockchain crate has a
provider/directory with trait implementations - Provider methods should fetch raw data via RPC, then call mapper functions for conversion
- Place mapper functions in separate
*_mapper.rsfiles for clean separation - Example:
get_balance_coin()callsself.get_balance()thenbalances_mapper::map_coin_balance() - This pattern ensures consistent data transformation and testability across all blockchain implementations
- Place integration tests in
tests/directories - Use
#[tokio::test]for async tests - Prefix test names descriptively with
test_ - Use
Result<(), Box<dyn std::error::Error + Send + Sync>>for test error handling - Configure integration tests with
test = falseand appropriaterequired-featuresfor manual execution - Prefer real networks for RPC client tests (e.g., Ethereum mainnet)
- Test data management: For long JSON test data (>20 lines), store in
testdata/and load withinclude_str!(); per-crate layout is typicallysrc/,tests/,testdata/
- Add integration tests for RPC functionality to verify real network compatibility
- Prefer recent blocks for batch operations (more reliable than historical blocks)
- Verify both successful calls and proper error propagation
- Use realistic contract addresses (e.g., USDC) for
eth_calltesting