Imported from avenbreaks/core (
AGENTS.md). Install upstream withnpx skills add avenbreaks/core. Copyright stays with the author.
AGENTS.md
Guidance for AI assistants (Claude Code, Gemini, Codex, etc.) collaborating on this repository.
Purpose & Audience
This document orients coding agents to the repo structure, development workflow, coding standards, and core architectural patterns used across Gem Wallet Core.
Project Overview
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.
Repository Layout
Applications (apps/)
- 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
Cross-Platform Library (gemstone/)
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::gem_swapper— swapper module for on-device swap integrations
Blockchain Support
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
Core Services (selected crates)
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
Technology Stack
- 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
Development Workflow
All commands use the just task runner. Run from the workspace root unless specified.
Build
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/)
Test
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
Code Quality
just format: Format all codejust lint: Run clippy with warnings as errorsjust fix: Auto-fix clippy issuesjust unused: Find unused dependencies with cargo-machete
Database
just migrate: Run Diesel migrationsjust setup-services: Start Docker services (PostgreSQL, Redis, Meilisearch, RabbitMQ)
Mobile
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
Utilities
just localize: Update localization filesjust generate-ts-primitives: Generate TypeScript types from Rustjust outdated: Check for outdated dependencies
Coding Standards
Follow the existing code style patterns unless explicitly asked to change.
Code Formatting
- 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
Commit Messages
- Write descriptive messages following conventional commit format
Naming
- 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
Imports
- 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.
Error Handling
- Use
thiserrorfor custom error types - Implement
Fromtraits for error conversion - Use consistent
Result<T, Error>return types - Propagate errors with the
?operator
Database Patterns
- Separate database models from domain primitives
- Use
as_primitive()methods for conversion - Diesel ORM with PostgreSQL backend
- Support transactions and upserts
Async Patterns
- Tokio runtime throughout
- Async client structs returning
Result<T, Error> - Use
Arc<tokio::sync::Mutex<T>>for shared async state
Architecture & Patterns
Key Development Patterns
- 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
Repository Pattern
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...
RPC Client Patterns
- 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
Blockchain Provider Patterns
- 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
Testing
Conventions
- 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/
Integration Testing
- 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