# OIF Solver [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/openintentsframework/oif-solver) [![codecov](https://codecov.io/github/openintentsframework/oif-solver/graph/badge.svg?token=JCYSH684F4)](https://codecov.io/github/openintentsframework/oif-solver) > :warning: This software is in alpha. Use in production environments at your own risk. A high-performance cross-chain solver implementation for the Open Intents Framework (OIF). This solver enables efficient cross-chain order execution by discovering intents, finding optimal execution paths, and settling transactions across multiple blockchain networks. ## Table of Contents - [Overview](#overview) - [High-Level Architecture](#high-level-architecture) - [Architecture](#architecture) - [Project Structure](#project-structure) - [Component Responsibilities](#component-responsibilities) - [Quick Start](#quick-start) - [Docker](#docker) - [Configuration](#configuration) - [Transaction Bumping](#transaction-bumping-tx_bump) - [AWS KMS Signing](#aws-kms-signing) - [API Reference](#api-reference) - [Cross-Chain Rebalancing](#cross-chain-rebalancing) - [Testing and Development with solver-demo](#testing-and-development-with-solver-demo) - [Development](#development) - [License](#license) ## Overview The OIF Solver is designed to: - Discover and monitor cross-chain intents from multiple sources - Find optimal execution paths across different chains and liquidity sources - Execute transactions efficiently while minimizing costs - Provide comprehensive monitoring and observability - Support multiple order types and protocols (currently EIP-7683) ## High-Level Architecture ```mermaid sequenceDiagram participant External as External Sources participant Discovery as Discovery Service participant Core as Core Engine participant Storage as Storage Service participant Order as Order Service participant Delivery as Delivery Service participant Settlement as Settlement Service Note over External,Settlement: Intent Discovery & Processing External->>Discovery: New Intent Event Discovery->>Core: Intent Discovered Core->>Order: Validate Intent Order->>Core: Validated Order Core->>Storage: Store Order Note over Core,Settlement: Intent Execution (Prepare → Fill) Core->>Order: Check Execution Strategy Order->>Core: Execute Decision (Status: Executing) Core->>Order: Generate Fill Transaction Order->>Core: Fill Transaction Ready Core->>Delivery: Submit Fill Transaction Delivery->>Core: Fill Confirmed (Status: Executed) Note over Core,Settlement: Post-Fill Processing Core->>Settlement: Generate PostFill Transaction Settlement->>Core: PostFill Transaction (if needed) Core->>Delivery: Submit PostFill Delivery->>Core: PostFill Confirmed (Status: PostFilled) Note over Core,Settlement: Settlement Monitoring Core->>Settlement: Start Monitoring for Claim Readiness Settlement->>Core: Monitor Fill Proof Settlement->>Core: Dispute Period Passed Note over Core,Settlement: Pre-Claim & Claim Core->>Settlement: Generate PreClaim Transaction Settlement->>Core: PreClaim Transaction (if needed) Core->>Delivery: Submit PreClaim Delivery->>Core: PreClaim Confirmed (Status: PreClaimed) Core->>Order: Generate Claim Transaction Order->>Core: Claim Transaction Ready Core->>Delivery: Submit Claim Delivery->>Core: Claim Confirmed (Status: Finalized) ``` ### Transaction State Transitions The solver manages orders through distinct transaction states with the following progression: 1. **Prepare** → Status: `Executing` (emits `OrderEvent::Executing`) 2. **Fill** → Status: `Executed` (emits `SettlementEvent::PostFillReady`) 3. **PostFill** → Status: `PostFilled` (emits `SettlementEvent::StartMonitoring`) 4. **PreClaim** → Status: `PreClaimed` (emits `SettlementEvent::ClaimReady`) 5. **Claim** → Status: `Finalized` (emits `SettlementEvent::Completed`) Each transition updates the order status in storage and triggers appropriate events for downstream processing. ## Architecture The solver is built as a modular Rust workspace with clearly defined service boundaries: ### Core Components - **solver-core**: Orchestrates the entire solver workflow and coordinates between services - **solver-types**: Defines shared data structures, traits, and interfaces used across all components - **solver-config**: Handles configuration loading and validation - **solver-storage**: Provides persistent storage abstraction with TTL management for solver state - **solver-account**: Manages cryptographic keys and signing operations ### Service Components - **solver-discovery**: Discovers new intents/orders from various blockchain and off-chain sources - **solver-order**: Validates intents, manages execution strategies, and generates transactions - **solver-delivery**: Handles transaction preparation, submission, and monitoring across multiple chains - **solver-settlement**: Manages settlement verification and claim processing after transaction execution ### Binaries - **solver-service**: Main executable that wires up all components and runs the solver in production - **solver-demo**: CLI tool for testing and demonstrating cross-chain intent execution in development environments ## Project Structure ``` oif-solver/ ├── Cargo.toml # Workspace definition ├── crates/ # Modular components │ ├── solver-account/ # Cryptographic operations │ ├── solver-config/ # Configuration management │ ├── solver-core/ # Orchestration engine │ ├── solver-delivery/ # Transaction submission │ ├── solver-demo/ # Testing and demo CLI │ ├── solver-discovery/ # Intent monitoring │ ├── solver-order/ # Order processing │ ├── solver-pricing/ # Price and profitability calculations │ ├── solver-service/ # Main executable │ ├── solver-settlement/ # Settlement verification │ ├── solver-storage/ # State persistence │ └── solver-types/ # Shared types ├── config/ # Bootstrap config and demo settings └── scripts/ # E2E testing and deployment scripts ``` ## Component Responsibilities ### solver-core - Orchestrates the entire order lifecycle - Manages event-driven communication between services - Implements the main solver loop - Handles graceful shutdown - Provides factory pattern for building solver instances ### solver-discovery - Monitors blockchain events for new intents - Supports multiple discovery sources simultaneously - Filters and validates discovered intents - Pushes valid intents to the core engine ### solver-order - Validates intents and converts them to orders - Implements execution strategies (when to execute) - Evaluates order profitability against minimum thresholds - Generates fill and claim transactions - Manages order-specific logic for different protocols - Supports callback data for settlement notifications ### solver-delivery - Submits transactions to multiple blockchains - Monitors transaction confirmation status - Manages gas estimation and pricing - Handles transaction retries and failures ### solver-settlement - Validates fill transactions - Extracts and stores fill proofs - Monitors when orders can be claimed - Manages dispute periods and settlement interactions ### solver-storage - Provides persistent storage for orders and state - Implements TTL (time-to-live) for automatic data expiration - Supports multiple storage backends: - **Memory**: Fast, non-persistent (for testing) - **File**: File-based persistence with custom binary format - **Redis**: Production-ready with native TTL and connection pooling - Index-based querying for efficient lookups - Ensures data consistency across services ### solver-account - Manages private keys and signing operations - Supports different key management backends: - **Local**: Private key stored in memory (default) - **AWS KMS**: Private key stored in AWS KMS HSM (feature-gated) - Provides secure signing for transactions - Handles address derivation ### solver-pricing - Provides pricing oracle implementations for asset valuation - Converts between wei amounts and fiat currencies - Supports multiple pricing backends - Manages pricing configuration - Enables profitability calculations and cost estimation ### solver-demo - Provides CLI tool for testing and demonstrating the solver - Manages local test environments with Anvil chains - Deploys and configures test contracts - Handles token operations (minting, approvals, balance checks) - Builds and submits test intents and quotes - Supports both on-chain and off-chain intent submission modes ## Quick Start ```bash # 1. Start Redis redis-server # 2. Set required environment variables export REDIS_URL=redis://localhost:6379 export SOLVER_PRIVATE_KEY=your_64_hex_character_private_key # 3. First run: Seed configuration (seedless mode) cargo run -- --bootstrap-config config/example.json # 3b. Optional: Seed with preset fallback values for known chains cargo run -- --seed testnet --bootstrap-config config/seed-overrides-testnet.json # 4. Subsequent runs: Configuration loads automatically from Redis cargo run -- ``` ### Auth Quickstart The solver has **two independent auth flags**: - **`orders_auth_enabled`** — JWT-gates the public Orders API (`POST /orders`, `GET /orders/{id}`) and the public client-registration endpoints (`/auth/register`, `/auth/refresh`). Default: `false`. - **`admin.enabled`** — gates SIWE admin login and the admin endpoints. SIWE works whenever this is true, regardless of `orders_auth_enabled`. If you enable Orders API auth in your seed overrides (`"orders_auth_enabled": true`) or enable admin auth (`"admin": { "enabled": true, ... }`), set `JWT_SECRET` before starting the service: ```bash export JWT_SECRET="$(openssl rand -base64 48)" # Optional export AUTH_PUBLIC_REGISTER_ENABLED=false ``` `JWT_SECRET` must be at least 32 bytes after trimming whitespace. The solver refuses to start with auth enabled if the secret is missing or too short. This prevents tokens from being signed with an unstable restart-local secret or a public demo placeholder. If `orders_auth_enabled = false`, `AUTH_PUBLIC_REGISTER_ENABLED` is ignored. Admin JWTs (`admin-all` scope) are obtained via SIWE wallet login: #### SIWE (admin wallet) ```bash ADMIN_ADDRESS=0xYourAdminWalletAddress # 1) Request SIWE nonce + canonical message NONCE_RESPONSE=$(curl -s -X POST http://localhost:3000/api/v1/auth/siwe/nonce \ -H "Content-Type: application/json" \ -d "{\"address\":\"$ADMIN_ADDRESS\"}") SIWE_MESSAGE=$(echo "$NONCE_RESPONSE" | jq -r '.message') # 2) Sign SIWE_MESSAGE with your wallet and set SIWE_SIGNATURE (0x... 65-byte signature) SIWE_SIGNATURE=0x... # 3) Exchange signed message for JWT access + refresh tokens SIWE_TOKENS=$(curl -s -X POST http://localhost:3000/api/v1/auth/siwe/verify \ -H "Content-Type: application/json" \ -d "$(jq -n --arg message "$SIWE_MESSAGE" --arg signature "$SIWE_SIGNATURE" '{message: $message, signature: $signature}')") TOKEN=$(echo "$SIWE_TOKENS" | jq -r '.access_token') REFRESH_TOKEN=$(echo "$SIWE_TOKENS" | jq -r '.refresh_token') ``` SIWE-issued access tokens use the same JWT format as other auth flows and currently use `TOKEN_DEFAULT_TTL_SECONDS.min(TOKEN_MAX_TTL_SECONDS)` (900s with current constants). SIWE also returns a refresh token with the configured `refresh_token_expiry_hours`, and that refresh token is compatible with `POST /api/v1/auth/refresh`. Access and refresh tokens are not interchangeable: - **Access tokens** include `typ = "access"` and are the only tokens accepted in the `Authorization: Bearer ` header for protected APIs. - **Refresh tokens** include `typ = "refresh"` and are accepted only by `POST /api/v1/auth/refresh` to obtain a new access/refresh token pair. Tokens issued before this token-type claim was introduced do not contain `typ` and are rejected after upgrade. Users and admins should re-authenticate once after deploying this change. ## Docker The solver can be run in a Docker container for production deployments. ### Building the Image ```bash # Standard build (local wallet only) docker build -t oif-solver . # Build with KMS support (requires AWS credentials at runtime) docker build --build-arg FEATURES=kms -t oif-solver . ``` ### Running the Container ```bash # Create a docker-compatible env file (removes 'export' prefixes if present) sed 's/^export //' .env > .env.docker # First run: Seed configuration to Redis docker run -it --rm \ -p 3000:3000 \ --env-file .env.docker \ -v $(pwd)/config:/app/config:ro \ oif-solver --seed testnet --bootstrap-config /app/config/seed-overrides-testnet.json # Subsequent runs: Load from Redis (set SOLVER_ID in .env.docker) docker run -it --rm \ -p 3000:3000 \ --env-file .env.docker \ oif-solver # Force re-seed (overwrite existing configuration) docker run -it --rm \ -p 3000:3000 \ --env-file .env.docker \ -v $(pwd)/config:/app/config:ro \ oif-solver --seed testnet --bootstrap-config /app/config/seed-overrides-testnet.json --force-seed ``` ### Docker Networking The solver binds to `0.0.0.0:3000` by default, which works for Docker deployments. For Redis connectivity from within Docker: - **Docker Compose:** Use service name (e.g., `redis://redis:6379`) - **Host machine Redis:** Use `redis://host.docker.internal:6379` (Mac/Windows) - **External Redis:** Use full URL (e.g., `redis://your-redis-host:6379`) ### Docker Environment Variables | Variable | Required | Description | |----------|----------|-------------| | `REDIS_URL` | Yes | Redis connection URL (for production, use managed services like AWS ElastiCache) | | `SOLVER_ID` | For loading | Solver ID to load config from Redis (after first seed) | | `RUST_LOG` | No | Log level (default: `info`) | | `SOLVER_PRIVATE_KEY` | Conditional | Required for local wallet if no inline `private_key` in bootstrap config | | `JWT_SECRET` | Conditional | Required when Orders API auth or admin auth is enabled. Must be at least 32 bytes; generate with `openssl rand -base64 48` | | `AWS_ACCESS_KEY_ID` | For KMS | AWS access key (if using KMS signer) | | `AWS_SECRET_ACCESS_KEY` | For KMS | AWS secret key (if using KMS signer) | | `AWS_REGION` | For KMS | AWS region (if using KMS signer, default: from config) | **Note:** `SOLVER_PRIVATE_KEY` is required when using the local wallet without an inline `private_key` in bootstrap config. Not needed if using KMS or providing `private_key` directly in the JSON. **AWS Credentials:** When running in AWS (ECS, EKS, EC2), use IAM roles instead of explicit credentials. The SDK automatically uses the instance/task role credentials. Only set `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` for local development or non-AWS environments. ### Volume Mounts | Mount | Purpose | |-------|---------| | `/app/config` | Bootstrap config JSON file (read-only, only needed for seeding) | See [docs/config-storage.md](docs/config-storage.md) for detailed documentation. ## Configuration The solver uses Redis as the single source of truth for runtime configuration. Configuration is seeded once and then loaded from Redis on subsequent startups. ### Seeding Configuration ```bash # Seedless bootstrap (all network + settlement values from JSON) cargo run -- --bootstrap-config config/example.json # Seed testnet configuration (uses testnet preset fallbacks where applicable) cargo run -- --seed testnet --bootstrap-config config/seed-overrides-testnet.json # Seed mainnet configuration (uses mainnet preset fallbacks where applicable) cargo run -- --seed mainnet --bootstrap-config config/seed-overrides-mainnet.json # Seed using a non-seeded networks JSON example cargo run -- --seed testnet --bootstrap-config config/non-seeded-networks-example.json # Or pass JSON directly (useful for deployment services) cargo run -- --seed testnet --bootstrap-config '{"solver_id":"my-solver","solver_name":"My Testnet Solver","networks":[{"chain_id":11155420,"name":"optimism-sepolia","type":"parent","tokens":[{"symbol":"USDC","name":"USD Coin","address":"0x191688B2Ff5Be8F0A5BCAB3E819C900a810FAaf6","decimals":6}]},{"chain_id":84532,"name":"base-sepolia","type":"hub","tokens":[{"symbol":"USDC","name":"USD Coin","address":"0x73c83DAcc74bB8a704717AC09703b959E74b9705","decimals":6}]}]}' # Force re-seed (overwrite existing) cargo run -- --seed testnet --bootstrap-config config/seed-overrides-testnet.json --force-seed ``` Legacy alias support: - `--seed-overrides` is still accepted temporarily as a deprecated alias for `--bootstrap-config`. ### Environment Variables | Variable | Required | Description | |----------|----------|-------------| | `REDIS_URL` | Yes | Redis connection URL (default: `redis://localhost:6379`). For production, use managed services like AWS ElastiCache | | `SOLVER_PRIVATE_KEY` | Conditional | 64-character hex private key (without 0x prefix) | | `SOLVER_ID` | For loading | Solver ID to load from Redis (set after first seed) | | `JWT_SECRET` | Conditional | Required when Orders API auth or admin auth is enabled. Must be at least 32 bytes; generate with `openssl rand -base64 48` | | `SOLVER_INGRESS_MODE` | No | `active` by default; set `intake_disabled` to stop accepting new solver work. Changing this value requires a process restart/redeploy. | `SOLVER_PRIVATE_KEY` is only required when using the local wallet without an inline `private_key` in bootstrap config. Not needed if using KMS or providing `private_key` directly in the JSON. ### Intake Disabled Mode Set `SOLVER_INGRESS_MODE=intake_disabled` at startup to stop accepting new solver work without shutting down the process. When enabled, the solver rejects: - `POST /api/v1/quotes` - `POST /api/v1/orders` quote acceptances - `POST /api/v1/orders` direct submissions - newly discovered on-chain/off-chain intents The solver continues processing already accepted orders and settlement events. Health checks, asset reads, admin balance reads, and admin withdrawals remain available. Use this for custody-cap operations where OpenZeppelin must stop new intake without receiving Polygon admin withdrawal permissions. **Toggling the mode requires a process restart/redeploy.** Restore normal operations by setting `SOLVER_INGRESS_MODE=active` or leaving the variable unset before restarting the process. ### Bootstrap Config Format Create a bootstrap JSON file specifying which networks to support. Networks can be: - Seeded networks (from the selected preset) - Non-seeded networks (new chain IDs) when full required fields are provided Token arrays may be empty at boot and can be populated later via admin APIs. ```json { "solver_id": "my-solver-instance", "solver_name": "My Solver Instance", "min_profitability_pct": "2.5", "gas_buffer_bps": 1500, "commission_bps": 20, "rate_buffer_bps": 14, "networks": [ { "chain_id": 11155420, "name": "optimism-sepolia", "type": "parent", "tokens": [ { "symbol": "USDC", "name": "USD Coin", "address": "0x191688B2Ff5Be8F0A5BCAB3E819C900a810FAaf6", "decimals": 6 } ] }, { "chain_id": 84532, "name": "base-sepolia", "type": "hub", "tokens": [ { "symbol": "USDC", "name": "USD Coin", "address": "0x73c83DAcc74bB8a704717AC09703b959E74b9705", "decimals": 6 } ], "rpc_urls": ["https://my-custom-rpc.com"] } ] } ``` Optional fee overrides can be set at the top level: `min_profitability_pct` (decimal string), `gas_buffer_bps`, `commission_bps`, and `rate_buffer_bps` (basis points). For per-chain EIP-1559 fee tuning (priority floor, fallback, percentile speed, gas-price cap), see [docs/fee-policy.md](docs/fee-policy.md). The solver auto-generates sensible defaults — you only need a `fee_policy` block in your bootstrap config if you want to override them. ### Transaction Bumping (`tx_bump`) The solver can run a background same-nonce transaction bump sweeper for stale in-flight attempts. It is disabled by default and should only be enabled with per-chain fee caps, signer-balance monitoring, and alerts for the bump/conflict events listed in [docs/tx-bump-operations.md](docs/tx-bump-operations.md). The sweeper: - Finds non-terminal attempts that have been pending longer than the configured threshold. - Checks whether the lineage tip already mined before submitting a replacement. - Submits a same-nonce replacement with bumped fees when the attempt is still pending. - Reconciles confirmed attempts through the attempt ledger so restart recovery and bumping agree on chain truth. Example bootstrap config: ```json { "tx_bump": { "enabled": false, "sweep_interval_secs": 15, "default_pending_threshold_secs": 60, "default_bump_percent": 15, "default_max_replacements_per_stage": 3, "default_max_fee_per_gas_cap_wei": "150000000000", "default_max_priority_fee_per_gas_cap_wei": "5000000000", "default_profitability_gate_fail_closed": false, "default_receipt_preflight_fail_closed": true, "chains": { "8453": { "pending_threshold_secs": 60, "bump_percent": 15, "max_replacements_per_stage": 3, "max_fee_per_gas_cap_wei": "50000000000", "max_priority_fee_per_gas_cap_wei": "2000000000", "profitability_gate_fail_closed": true, "receipt_preflight_fail_closed": true } } } } ``` Chains not present in `tx_bump.chains` are not bumped. Optional network types are: `parent`, `hub`, and `new`. For non-seeded networks, the following fields are required per network: - `name` - `type` - `input_settler_address` - `output_settler_address` - `rpc_urls` (at least one URL) Compact addresses are optional: - `input_settler_compact_address` - `the_compact_address` - `allocator_address` ### Settlement Selection (`settlement.type`) Settlement implementation is selected from bootstrap config JSON: - `hyperlane` (default when omitted) - `direct` - `broadcaster` When `settlement.type = "direct"`, `settlement.direct` is required. When `settlement.type = "hyperlane"` and you include non-seeded networks, provide `settlement.hyperlane` maps (`mailboxes`, `igp_addresses`, `oracles.input`, `oracles.output`) for all configured chains. When `settlement.type = "broadcaster"`, `settlement.broadcaster` is required with `oracles`, `routes`, `broadcaster_addresses`, `receiver_addresses`, and `broadcaster_ids`. Optionally include `pusher_directions` for proactive L1 block hash pushing (required for ETH→ARB, ETH→OP, and ETH→Linea directions). Each pusher direction takes a typed `l2_params` object (`type: "arbitrum"`, `"op_stack"`, `"linea"`, or `"raw"`). See `config/example-broadcaster.json` and `docs/oracles/pusher-setup-guide.md` for details. See `config/non-seeded-networks-example.json` for a full non-seeded Hyperlane example (both chain IDs are non-seeded). **Note:** The `solver_id` field is optional but recommended. If provided, seeding becomes idempotent (re-running `--seed` with same config skips seeding). After seeding, set `SOLVER_ID` env var for subsequent runs. ### Supported Networks **Testnet:** | Chain | Chain ID | |-------|----------| | Optimism Sepolia | 11155420 | | Base Sepolia | 84532 | **Mainnet:** | Chain | Chain ID | |-------|----------| | Optimism | 10 | | Base | 8453 | | Arbitrum | 42161 | These are preset-backed networks. You can also seed non-seeded chain IDs by providing the required non-seeded network fields and settlement overrides described above. ### Running Redis ```bash # Using Docker docker run -d --name redis -p 6379:6379 redis:latest # Or install locally redis-server ``` ### AWS KMS Signing For production deployments requiring enhanced security, the solver supports AWS KMS for transaction signing. The private key never leaves the KMS hardware security module. #### Prerequisites 1. **Create a KMS Key** (must be secp256k1 for Ethereum compatibility): ```bash aws kms create-key \ --key-spec ECC_SECG_P256K1 \ --key-usage SIGN_VERIFY \ --description "OIF Solver signing key" ``` Note the `KeyId` from the output. 2. **IAM Permissions** - Your AWS credentials need: ```json { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action": ["kms:Sign", "kms:GetPublicKey"], "Resource": "arn:aws:kms:REGION:ACCOUNT:key/KEY_ID" }] } ``` #### Building with KMS Support ```bash # Build with KMS feature enabled cargo build --release --features kms ``` #### Configuration Configure KMS in your bootstrap config JSON: ```json { "solver_id": "my-solver", "account": { "primary": "kms", "implementations": { "kms": { "key_id": "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012", "region": "us-east-1" } } }, "networks": [...] } ``` | Field | Required | Description | |-------|----------|-------------| | `key_id` | Yes | KMS key ID or full ARN | | `region` | Yes | AWS region (e.g., `us-east-1`) | | `endpoint` | No | Custom endpoint URL (for LocalStack testing) | #### Running with KMS ```bash # Set AWS credentials (or use IAM roles on EC2/ECS) export AWS_ACCESS_KEY_ID=your_access_key export AWS_SECRET_ACCESS_KEY=your_secret_key export AWS_REGION=us-east-1 # Seed and run cargo run --features kms -- --seed testnet --bootstrap-config config/seed-overrides-kms.json ``` #### Finding Your KMS Wallet Address The solver logs the wallet address at startup: ``` INFO solver_core::builder: Solver address initialized component="account" address=0x1234...abcd ``` You can also query the address directly using the AWS CLI: ```bash # Get public key from KMS and derive Ethereum address aws kms get-public-key --key-id "your-key-id" --output text --query 'PublicKey' | \ base64 -d | openssl ec -pubin -inform DER -outform PEM 2>/dev/null | \ openssl ec -pubin -conv_form uncompressed -outform DER 2>/dev/null | \ tail -c 64 | keccak-256sum | tail -c 41 | sed 's/^/0x/' ``` Or use a simpler approach - run the solver once and it will display the address in the logs. #### LocalStack Testing For local development, you can test KMS integration with LocalStack: ```bash # Start LocalStack docker run -d --name localstack -p 4566:4566 localstack/localstack # Create test key aws --endpoint-url=http://localhost:4566 kms create-key \ --key-spec ECC_SECG_P256K1 \ --key-usage SIGN_VERIFY # Configure with endpoint { "account": { "primary": "kms", "implementations": { "kms": { "key_id": "your-localstack-key-id", "region": "us-east-1", "endpoint": "http://localhost:4566" } } } } ``` ## API Reference The solver provides a REST API for interacting with the system and submitting off-chain intents. Full OpenAPI specifications are available in the `api-spec/` directory. ### API Specifications - **Orders API**: [`api-spec/orders-api.yaml`](api-spec/orders-api.yaml) - Submit and track cross-chain intent orders - **Assets API**: [`api-spec/assets-api.yaml`](api-spec/assets-api.yaml) - Query supported assets and networks - **Auth API**: [`api-spec/auth-api.yaml`](api-spec/auth-api.yaml) - JWT issuance flows (SIWE + register/refresh) - **Admin API**: [`api-spec/admin-api.yaml`](api-spec/admin-api.yaml) - Wallet-based admin operations (EIP-712 signed) - **Admin Auth Guide**: [`docs/admin-authentication.md`](docs/admin-authentication.md) - Practical auth + nonce flow guide - **Rebalance API**: [`api-spec/rebalance-api.yaml`](api-spec/rebalance-api.yaml) - Cross-chain rebalancing endpoints (EIP-712 signed) ### API Flows The following diagrams show the detailed RPC interactions for each API endpoint. #### Quote Request Flow **RPC calls:** 8 (EIP-3009) · 6 (ResourceLock) · 6 (Permit2) ```mermaid sequenceDiagram participant Client participant Solver participant PricingAPI as Pricing API participant OriginRPC as Origin Chain RPC participant DestRPC as Dest Chain RPC participant TokenRPC as Token Contract RPC participant SettlerEscrow as InputSettlerEscrow participant SettlerCompact as InputSettlerCompact Client->>Solver: POST /api/v1/quotes Note over Solver: Validate request, extract chain IDs Note over Solver,DestRPC: Chain Data & Cost Context Calculation (parallel) par Origin Chain Data Solver->>OriginRPC: eth_gasPrice Solver->>OriginRPC: eth_blockNumber OriginRPC-->>Solver: gas price + block and Dest Chain Data Solver->>DestRPC: eth_gasPrice Solver->>DestRPC: eth_blockNumber DestRPC-->>Solver: gas price + block and Token Pricing (HTTP) Note over Solver,PricingAPI: Pricing source configured (CoinGecko or DefiLlama) Solver->>PricingAPI: GET /simple/price or /prices/current PricingAPI-->>Solver: price data (USD) end Note over Solver: Calculate cost context (gas costs, swap amounts, profit margins) Note over Solver,DestRPC: Solver Balance Check Solver->>DestRPC: eth_call ERC20 balanceOf(solver_address) Note right of DestRPC: selector 0x70a08231 DestRPC-->>Solver: uint256 balance Note over Solver: Custody Strategy Decision & Quote Generation alt EIP-3009 Capability & Quote Generation Solver->>OriginRPC: eth_call RECEIVE_WITH_AUTHORIZATION_TYPEHASH() Note right of OriginRPC: selector 0x7f2eecc3 OriginRPC-->>Solver: bytes32 typehash (if supported) Note over Solver,TokenRPC: EIP-3009 Domain Separator Solver->>TokenRPC: eth_call token DOMAIN_SEPARATOR() Note right of TokenRPC: selector 0x3644e515 TokenRPC-->>Solver: bytes32 domainSeparator Note over Solver,SettlerEscrow: Order ID for Quote (Escrow) Solver->>SettlerEscrow: eth_call orderIdentifier(order) SettlerEscrow-->>Solver: bytes32 orderId Note over Solver: Generate EIP-3009 Quote + EIP-712 typed data else ResourceLock Quote Generation Note over Solver,SettlerCompact: Order ID for Quote (Compact) Solver->>SettlerCompact: eth_call orderIdentifier(order) SettlerCompact-->>Solver: bytes32 orderId Note over Solver: Generate ResourceLock Quote + EIP-712 typed data else Permit2 Quote Generation Note over Solver,SettlerEscrow: Order ID for Quote (Escrow) Solver->>SettlerEscrow: eth_call orderIdentifier(order) SettlerEscrow-->>Solver: bytes32 orderId Note over Solver: Generate Permit2 Quote + EIP-712 typed data end Solver-->>Client: Quote Response with multiple options ``` #### Order Submission Flow **RPC calls:** 2 (EIP-3009) · 3 (ResourceLock) · 2 (Permit2) ```mermaid sequenceDiagram participant Client participant Solver participant OriginRPC as Origin Chain RPC participant TheCompact as TheCompact Contract participant SettlerEscrow as InputSettlerEscrow participant SettlerCompact as InputSettlerCompact participant Discovery as Discovery Service Client->>Solver: POST /api/v1/orders {order, signature} Note over Solver: Parse order, detect lock_type alt ResourceLock Order Note over Solver,TheCompact: Signature Validation (EIP-712) Solver->>TheCompact: eth_call TheCompact DOMAIN_SEPARATOR() Note right of TheCompact: selector 0x3644e515 TheCompact-->>Solver: bytes32 domainSeparator Note over Solver: EIP-712 hash + ecrecover Note over Solver,TheCompact: Capacity Check (TheCompact deposit) Solver->>TheCompact: eth_call TheCompact balanceOf(user, tokenId) Note right of TheCompact: selector 0x00fdd58e TheCompact-->>Solver: uint256 depositBalance Note over Solver,SettlerCompact: Order ID Computation (Compact) Solver->>SettlerCompact: eth_call orderIdentifier(order) SettlerCompact-->>Solver: bytes32 orderId else Permit2 Order Note over Solver: Signature Recovery (EIP-712 ecrecover) Note over Solver: Derive user address from signature Note over Solver: Inject user into order payload Note over Solver,OriginRPC: Capacity Check (Wallet) Solver->>OriginRPC: eth_call ERC20 balanceOf(user) Note right of OriginRPC: selector 0x70a08231 OriginRPC-->>Solver: uint256 walletBalance Note over Solver,SettlerEscrow: Order ID Computation (Escrow) Solver->>SettlerEscrow: eth_call orderIdentifier(order) SettlerEscrow-->>Solver: bytes32 orderId else EIP-3009 Order Note over Solver: Signature Recovery (EIP-712 ecrecover) Note over Solver: Derive user address from signature Note over Solver: Inject user into order payload Note over Solver,OriginRPC: Capacity Check (Wallet) Solver->>OriginRPC: eth_call ERC20 balanceOf(user) Note right of OriginRPC: selector 0x70a08231 OriginRPC-->>Solver: uint256 walletBalance Note over Solver,SettlerEscrow: Order ID Computation (Escrow) Solver->>SettlerEscrow: eth_call orderIdentifier(order) SettlerEscrow-->>Solver: bytes32 orderId end Note over Solver,Discovery: Forward to Discovery Service Solver->>Discovery: POST /intent {order, signature, quote_id?, origin_submission?} alt Discovery Success Discovery-->>Solver: PostOrderResponse {status: "received", order_id} Solver-->>Client: 200 OK + PostOrderResponse else Discovery Error Discovery-->>Solver: PostOrderResponse {status: "error", message} Solver-->>Client: 400/500 + PostOrderResponse end ``` ### Available Endpoints #### Quotes - **POST `/api/v1/quotes`** - Request price quotes for a cross-chain swap - Request body: ```json { "user": "...", "intent": { "intentType": "...", "inputs": [...], "outputs": [...], "swapType": "...", "originSubmission": {...} }, "supportedTypes": [...] } ``` - Returns: Array of quotes with `quoteId`, order structure ready for signing, and preview of amounts #### Orders - **POST `/api/v1/orders`** - Submit a new order (direct or from quote) - Quote acceptance: ```json { "quoteId": "...", "signature": "0x..." } ``` - Direct submission: ```json { "order": { "type": "...", "payload": {...} }, "signature": "0x...", "originSubmission": {...} } ``` - Supported order types: `oif-escrow-v0`, `oif-resource-lock-v0`, `oif-3009-v0` - Returns: ```json { "orderId": "...", "status": "received", "message": null } ``` - **GET `/api/v1/orders/{id}`** - Get order status and details - Returns complete order information including status, amounts, settlement data, and fill transaction #### Assets - **GET `/api/v1/assets`** - Get all supported assets across all networks - Returns a map of chain IDs to network configurations with supported assets, including network `name`/`type` and asset `name` - **GET `/api/v1/assets/{chain_id}`** - Get supported assets for a specific chain - Returns network configuration (including `name`/`type`) and asset list (including asset `name`) #### Health - **GET `/health`** - Health check endpoint - Returns solver health status including storage readiness, Redis persistence info, and startup readiness. - **Steady-state response (fully operational):** ```json { "status": "healthy", "storage": { "backend": "Redis", "ready": true, "checks": [], "details": {} }, "redis": { "connected": true, "persistence_enabled": true, "rdb_enabled": true, "aof_enabled": false }, "solver_id": "my-solver", "version": "0.1.0", "startup": { "approvals_ready": true } } ``` - **While the signer needs native gas to complete startup approvals**, the API stays up and HTTP `200 OK` is returned, but `startup.approvals_ready` is `false` and `blocked_signers` lists the addresses that need funding. The solver retries approvals every 30 seconds and the field flips to `true` automatically once they succeed. ```json { "status": "healthy", "storage": { "backend": "Redis", "ready": true, "checks": [], "details": {} }, "redis": { "connected": true, "persistence_enabled": true, "rdb_enabled": true, "aof_enabled": false }, "solver_id": "my-solver", "version": "0.1.0", "startup": { "approvals_ready": false, "reason": "waiting_for_native_gas", "blocked_signers": [ { "chain_id": 1, "signer": "0x3c0189...", "balance_wei": "0" } ] } } ``` - Frontends should branch on `startup.approvals_ready`, not on the HTTP status, to surface a "fund this address" prompt. - Status codes: `200 OK` when storage is ready (regardless of startup readiness), `503 Service Unavailable` when storage or another core dependency is not ready. #### Admin API (Wallet-Based Authentication) The admin API enables authorized wallet addresses to perform administrative operations using EIP-712 signed messages. This provides secure, decentralized admin access without shared secrets. **Setup:** Configure typed admin whitelist entries in your seed overrides JSON: ```json { "admin": { "enabled": true, "domain": "localhost", "withdrawals": { "enabled": true, "recipient_allowlist": [ "0x1111111111111111111111111111111111111111" ] }, "whitelist": [ { "address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "role": "admin" }, { "address": "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", "role": "read-only" } ] } } ``` `role: "admin"` receives `admin-all` and can sign EIP-712 transactions. `role: "read-only"` receives `admin-read` through SIWE and can call admin `GET` endpoints only. The legacy `admin_addresses` list is still accepted as input and is normalized to full admin entries. `admin.withdrawals.enabled` gates `POST /api/v1/admin/withdrawals`. `admin.withdrawals.recipient_allowlist` is optional and defaults to empty. Empty means withdrawals can target any recipient, preserving previous behavior. Once the list contains at least one address, every admin withdrawal recipient must match one of those addresses exactly. Use this to pin withdrawals to operator-controlled destinations such as a cold-storage multisig. `admin.domain` is the SIWE/admin login domain. Use `"localhost"` for local development because the wallet signs a login message for the local host. In production, set it to the public host your admin UI uses, such as `"solver.example.com"`. Admin action signatures use a separate EIP-712 domain returned by `GET /api/v1/admin/types`: ```json { "name": "OIF Solver Admin", "version": "1", "chainId": 1, "salt": "0x..." } ``` The `salt` is derived from `solver_id` and binds each admin signature to one solver instance. Clients must use the complete `domain` object and `types.EIP712Domain` returned by `/admin/types`, including `salt`, instead of rebuilding the domain from `admin.domain` or hard-coding `"localhost"`. **Endpoints:** - **GET `/api/v1/admin/balances`** - Get solver balances by chain and token (protected, read-only) - **GET `/api/v1/admin/config`** - Get current solver configuration snapshot (protected, read-only) - **GET `/api/v1/admin/nonce`** - Get a nonce for signing admin actions (protected, read-only) - **POST `/api/v1/admin/nonce`** - Get a nonce for signing admin actions (protected, admin-all) - Response: ```json { "nonce": "1706184000123456", "expiresIn": 300, "domain": "solver.example.com", "chainId": 1 } ``` - **GET `/api/v1/admin/types`** - Get EIP-712 type definitions for client-side signing (protected) - Returns the EIP-712 domain, including the solver-derived `salt`, and type definitions for all admin actions (AddToken, RemoveToken, etc.) - **POST `/api/v1/admin/tokens`** - Add a new token to a network (protected) - Request body: ```json { "signature": "0x...", "contents": { "chainId": 10, "symbol": "USDC", "name": "USD Coin", "tokenAddress": "0x...", "decimals": 6, "nonce": 1706184000123456, "deadline": 1706184300 } } ``` - The signature must be an EIP-712 typed data signature from an authorized admin - Changes are hot-reloaded immediately (no restart required) - **POST `/api/v1/admin/tokens/batch`** - Add multiple tokens in one signed request (protected) - `contents.tokens` contains 2-50 entries with `chainId`, `symbol`, `name` (optional), `tokenAddress`, and `decimals` - **DELETE `/api/v1/admin/tokens`** - Remove a token from a network (protected) - **POST `/api/v1/admin/tokens/approve`** - Approve token allowance for settlements (protected) - **POST `/api/v1/admin/withdrawals`** - Perform an admin withdrawal (protected) - If `admin.withdrawals.recipient_allowlist` is non-empty, `contents.recipient` must be in that allowlist - **GET `/api/v1/admin/whitelist`** - List typed admin whitelist entries (protected, read-only) - **POST `/api/v1/admin/whitelist`** - Set a wallet role to `admin` or `read-only` (protected, EIP-712 signed) - **DELETE `/api/v1/admin/whitelist`** - Remove an admin (protected) - **GET `/api/v1/admin/fees`** - Get fee configuration (protected) - **PUT `/api/v1/admin/fees`** - Update fee configuration (protected, EIP-712 signed) - **GET `/api/v1/admin/gas`** - Get gas configuration (protected) - **PUT `/api/v1/admin/gas`** - Update gas configuration (protected, EIP-712 signed) **Admin Action Flow:** 1. Call `POST /api/v1/admin/nonce` (or `GET` if your token is read-only) to get a fresh nonce 2. Call `GET /api/v1/admin/types` to get the EIP-712 domain and type definitions 3. Construct the typed data with the nonce, a deadline, and the exact `domain` and `types` from `/admin/types` 4. Sign with your admin wallet (EIP-712) 5. Submit the signed action to the appropriate endpoint **Note:** Protected admin endpoints require a JWT with `admin-read` for `GET` routes and `admin-all` for mutating routes. Obtain it via SIWE (`POST /api/v1/auth/siwe/nonce` + `POST /api/v1/auth/siwe/verify`). SIWE only requires `admin.enabled = true` — it works regardless of `orders_auth_enabled`. **Note:** `/api/v1/auth/siwe/nonce` is dedicated to SIWE login. `/api/v1/admin/nonce` is dedicated to EIP-712 admin action signing. **Note:** The `domain` string in `/api/v1/admin/nonce` is the configured admin login domain. For EIP-712 signing, use the structured domain from `/api/v1/admin/types`. **Note:** `POST /api/v1/auth/register` never issues `admin-all` and is disabled by default unless `AUTH_PUBLIC_REGISTER_ENABLED=true`. **Note:** Admin withdrawals require `admin.withdrawals.enabled = true` in config. Configure `admin.withdrawals.recipient_allowlist` to restrict withdrawals to vetted recipient addresses; leaving it empty keeps withdrawals unrestricted by recipient. #### Fees and Profitability The solver enforces profitability via four configurable controls: - `min_profitability_pct`: Minimum profit as a percentage of transaction value - `commission_bps`: Additional profit requirement in bps of transaction value - `gas_buffer_bps`: Buffer applied to estimated gas costs in bps - `rate_buffer_bps`: Safety margin applied to market rates to protect against price movement How it’s applied: ```text transaction_value = max(total_input_usd, total_output_usd) min_profit = transaction_value * min_profitability_pct / 100 commission = transaction_value * commission_bps / 10_000 required_profit = min_profit + commission ``` The rate buffer is applied during swap amount calculation: - ExactInput: `output_usd = market_output_usd * (1 - rate_buffer_bps/10_000)` - ExactOutput: `input_usd = market_input_usd / (1 - rate_buffer_bps/10_000)` Fee config can be set in bootstrap config and updated at runtime via `PUT /api/v1/admin/fees`. ### Example Usage ```bash # Request a quote for a cross-chain swap curl -X POST http://localhost:3000/api/v1/quotes \ -H "Content-Type: application/json" \ -d '{ "user": "0x74...", "intent": { "intentType": "oif-swap", "inputs": [{ "user": "0x74...", "asset": "0x12...", "amount": "1000000" }], "outputs": [{ "receiver": "0x11...", "asset": "0x11...", "amount": "990000" }], "swapType": "exact-input", "originSubmission": { "mode": "user", "schemes": ["permit2"] } }, "supportedTypes": ["oif-escrow-v0"] }' # Accept a quote (submit order with quoteId) curl -X POST http://localhost:3000/api/v1/orders \ -H "Content-Type: application/json" \ -d '{ "quoteId": "quote_abc123def456", "signature": "0x1234567890abcdef..." }' # Submit an order directly (without quote) curl -X POST http://localhost:3000/api/v1/orders \ -H "Content-Type: application/json" \ -d '{ "order": { "type": "oif-escrow-v0", "payload": { "signatureType": "eip712", "domain": {...}, "primaryType": "PermitBatchWitnessTransferFrom", "message": {...} } }, "signature": "0x1234567890abcdef...", "originSubmission": { "mode": "user", "schemes": ["permit2"] } }' # Check order status curl http://localhost:3000/api/v1/orders/1fa518079ecf01372290adf75c55858771efcbcee080594cc8bc24e3309a3a09 # Get supported assets for chain 31338 curl http://localhost:3000/api/v1/assets/31338 # Get all supported assets curl http://localhost:3000/api/v1/assets # Health check curl http://localhost:3000/health # Admin: Get JWT via SIWE (wallet login) # See docs/admin-authentication.md for a full SIWE example. # Admin: Get nonce for signing admin actions (protected) curl -X POST http://localhost:3000/api/v1/admin/nonce \ -H "Authorization: Bearer $TOKEN" # Admin: Get EIP-712 types for signing curl http://localhost:3000/api/v1/admin/types \ -H "Authorization: Bearer $TOKEN" # Admin: Get solver balances (protected) curl http://localhost:3000/api/v1/admin/balances \ -H "Authorization: Bearer $TOKEN" # Admin: Get solver config snapshot (protected) curl http://localhost:3000/api/v1/admin/config \ -H "Authorization: Bearer $TOKEN" # Admin: Get admin whitelist (protected) curl http://localhost:3000/api/v1/admin/whitelist \ -H "Authorization: Bearer $TOKEN" ``` The API server is enabled by default on port 3000 when the solver is running. You can disable it or change the port in the configuration file. ### Logging Configuration The solver uses the `RUST_LOG` environment variable for fine-grained logging control. You can specify different log levels for different modules: ```bash # Show debug logs for solver modules only RUST_LOG=solver_core=debug,solver_delivery=debug,info cargo run # Reduce noise from external crates RUST_LOG=info,hyper=warn,alloy_provider=warn cargo run # Debug specific modules RUST_LOG=solver_core=debug,solver_delivery=info,alloy=warn,hyper=warn cargo run # Show all debug logs (very verbose) RUST_LOG=debug cargo run ``` Available log levels (from most to least verbose): - `trace` - Very detailed debugging information - `debug` - Debugging information - `info` - General information (default) - `warn` - Warning messages - `error` - Error messages only The `--log-level` flag acts as a fallback when `RUST_LOG` is not set: ```bash # Uses info level for all modules when RUST_LOG is not set cargo run -- --log-level info ``` ## Cross-Chain Rebalancing The solver includes automated cross-chain token rebalancing. A background monitor checks balances at a configurable interval and automatically bridges tokens when they drift outside a target band. **Key features:** - Threshold-based auto-trigger with configurable target balance and deviation band per pair - Safety guards: cooldowns, max concurrent transfers, per-pair locking, transaction serialization - Manual trigger and admin resolution via EIP-712 signed API endpoints - 6-state transfer lifecycle with automatic escalation to NeedsIntervention on timeouts - Runtime config updates via PUT endpoints (hot-reload without restart) **Quick start:** Add a `rebalance` section to your bootstrap config. See [`docs/rebalance.md`](docs/rebalance.md) for the full config reference, and [`api-spec/rebalance-api.yaml`](api-spec/rebalance-api.yaml) for the OpenAPI spec. ## Testing and Development with solver-demo The project includes a Rust-based CLI tool (`solver-demo`) for testing cross-chain intent execution. This tool provides comprehensive functionality for setting up test environments, managing tokens, and testing intent execution flows. **Note:** The demo has been tested on macOS systems only. ### Prerequisites - [Foundry](https://book.getfoundry.sh/getting-started/installation) (for Anvil and deployment) - Rust toolchain (stable, 1.88.0+) - [OIF Contracts](https://github.com/openintentsframework/oif-contracts) compiled with `forge build` (the demo looks for artifacts in `oif-contracts/out/`) ### Quick Start > **Note:** The solver-demo tool uses local Anvil chains for testing. The main solver binary uses Redis-based configuration for production deployments. See below for demo-specific setup. ```bash # 0. Clone and build the OIF contracts (needed for local deployment) git clone https://github.com/openintentsframework/oif-contracts.git cd oif-contracts && forge build && cd .. # 1. Initialize configuration and load it cargo run -p solver-demo -- init new config/demo.json cargo run -p solver-demo -- init load config/demo.json --local # 2. Start local environment (Anvil chains) cargo run -p solver-demo -- env start # 3. Deploy contracts and setup test environment cargo run -p solver-demo -- env deploy --all cargo run -p solver-demo -- env setup # 4. In another terminal, the demo tests can be run without starting the main solver # The solver-demo tool handles intent building, quoting, and submission for testing # 5. Build and test intents # Build an intent request cargo run -p solver-demo -- intent build --from-chain 31337 --to-chain 31338 --from-token TOKA --to-token TOKB --amount 100 \ --swap-type exact-input --settlement escrow --auth permit2 # Get quote and sign cargo run -p solver-demo -- quote get .oif-demo/requests/get_quote.req.json cargo run -p solver-demo -- quote sign .oif-demo/requests/get_quote.res.json # Submit the order cargo run -p solver-demo -- intent submit .oif-demo/requests/post_order.req.json # 6. Monitor token balances cargo run -p solver-demo -- token balance ``` > The demo tool requires the Permit2 contract bytecode file located at `crates/solver-demo/data/permit2_bytecode.hex`. This file contains the canonical Permit2 bytecode and is essential for deploying contracts to local Anvil chains. The bytecode is automatically used during the `env deploy` step. > > To fetch and store the Permit2 bytecode, run this command from the project root: > > ```bash > # Fetch Permit2 bytecode from Ethereum mainnet and save to the required location > # This will skip if the file already exists > [ ! -f crates/solver-demo/data/permit2_bytecode.hex ] && mkdir -p crates/solver-demo/data && cast code 0x000000000022D473030F116dDEE9F6B43aC78BA3 --rpc-url https://eth.llamarpc.com > crates/solver-demo/data/permit2_bytecode.hex || echo "Permit2 bytecode already exists at crates/solver-demo/data/permit2_bytecode.hex" > ``` ### Commands Overview #### Initialization Commands ```bash # Create new configuration template cargo run -p solver-demo -- init new --chains # Load existing configuration cargo run -p solver-demo -- init load [--local] # Show current configuration cargo run -p solver-demo -- config ``` #### Environment Management ```bash # Start Anvil chains cargo run -p solver-demo -- env start # Stop Anvil chains cargo run -p solver-demo -- env stop # Deploy contracts cargo run -p solver-demo -- env deploy --all # Setup test environment (mint tokens, approvals, etc.) cargo run -p solver-demo -- env setup [--chain ] [--amount ] ``` #### Intent Operations ```bash # Build an intent request # Format: intent build cargo run -p solver-demo -- intent build --from-chain 31337 --to-chain 31338 --from-token TOKA --to-token TOKB --amount 100 \ --swap-type exact-input \ --settlement escrow \ --auth permit2 \ [--output ] # Build batch intents from JSON file cargo run -p solver-demo -- intent build-batch [--output ] # Submit intent to solver API (off-chain) cargo run -p solver-demo -- intent submit # Submit intent directly to blockchain (on-chain) cargo run -p solver-demo -- intent submit --onchain # Check intent/order status cargo run -p solver-demo -- intent status # Test batch intent submission cargo run -p solver-demo -- intent test ``` **Supported Options:** - `--swap-type`: `exact-input` or `exact-output` - `--settlement`: `escrow` or `compact` (resource locks) - `--auth`: `permit2` or `eip3009` (for off-chain submission) - `--callback-recipient`: Address to receive callback after fill (optional) - `--callback-data`: Hex-encoded data to pass to callback (optional) **Example with Callback:** ```bash # Build intent with callback for post-fill notification # - callback-recipient: MockCallbackExecutorWithFee contract on Base Sepolia # - callback-data: ABI-encoded address to receive the fee (0xd890aa4d1b1517a16f9c3d938d06721356e48b7d) oif-demo intent build \ --to-chain 84532 \ --from-chain 11155420 \ --from-token USDC \ --to-token USDC \ --swap-type exact-output \ --amount 1 \ --settlement escrow \ --auth permit2 \ --callback-recipient 0xf2a313a3Dc028295e1dFa3BEE34EaFD2f801C994 \ --callback-data 0x000000000000000000000000d890aa4d1b1517a16f9c3d938d06721356e48b7d ``` The callback recipient will be called after the fill transaction with the provided callback data. The solver simulates the callback during gas estimation to ensure it won't revert and to accurately estimate gas costs. #### Quote Operations ```bash # Get quote from solver API cargo run -p solver-demo -- quote get [--output ] # Sign a quote (prepares order request for submission) cargo run -p solver-demo -- quote sign \ [--quote-index ] \ [--signature ] \ [--output ] # Test batch quote flow (get quotes and sign them) cargo run -p solver-demo -- quote test ``` #### Token Operations ```bash # List tokens on all or specific chains cargo run -p solver-demo -- token list [--chains ] # Mint tokens to an account cargo run -p solver-demo -- token mint --chain --token --amount [--to
] # Approve token spending cargo run -p solver-demo -- token approve --chain --token --spender --amount # Check token balances cargo run -p solver-demo -- token balance [--account ] [--chains ] cargo run -p solver-demo -- token balance # All accounts (default) cargo run -p solver-demo -- token balance --account user # Just user account # Monitor balances with auto-refresh cargo run -p solver-demo -- token balance [--account ] --follow ``` #### Account Management ```bash # List configured accounts cargo run -p solver-demo -- account list # Show account details cargo run -p solver-demo -- account info ``` ### Output File Naming Conventions The demo tool generates files in the `.oif-demo/requests/` directory following a clear naming convention: - **`.req.json`** - Request payloads sent to the API - **`.res.json`** - Responses received from the API | File | Description | Generated By | | ---------------------- | --------------------------- | -------------------- | | `get_quote.req.json` | Quote request payload | `intent build` | | `get_quote.res.json` | Quote response with pricing | `quote get` | | `post_order.req.json` | Signed order request | `quote sign` | | `get_quotes.req.json` | Batch quote requests | `intent build-batch` | | `post_orders.req.json` | Batch signed orders | `quote test` | ### Environment Setup Details The demo tool provides a complete workflow for setting up a test environment: 1. **Initialize Configuration** (`init new` / `init load` / `init load-storage`): - Creates or loads solver configuration - Can load runtime config directly from storage backend (`init load-storage`) - Sets up network definitions and RPC endpoints - Configures account keys and signing - Stores session data in `.oif-demo/` directory 2. **Start Blockchain Networks** (`env start`): - Launches Anvil chains (default: 31337 on port 8545, 31338 on port 8546) - Manages chain processes in the background - Validates connectivity to each chain 3. **Deploy Smart Contracts** (`env deploy`): - Deploys test tokens (TokenA, TokenB) on configured chains - Deploys escrow settlers (InputSettler, OutputSettler) - Deploys compact settlers and Permit2 contracts - Updates session with deployed contract addresses 4. **Setup Test Environment** (`env setup`): - Mints tokens to test accounts (user, solver, recipient) - Approves token spending for Permit2 and settler contracts - Registers allocator with TheCompact - Validates all approvals and registrations ### Running the Solver (Production) For production deployments, the solver uses Redis-based configuration: ```bash # Build the project cargo build # Seed configuration (first run only) cargo run -- --seed testnet --bootstrap-config config/seed-overrides-testnet.json # Run the solver (loads config from Redis) export SOLVER_ID=your-solver-id # Output from seeding step cargo run # Or with debug logs for debugging RUST_LOG=solver_core=debug,solver_delivery=info,info cargo run ``` The solver will: - Connect to configured chains - Start monitoring for new intents - Process discovered intents automatically ## Development This project uses a Rust workspace structure. Each crate is independently versioned and can be used separately. ### Building from Source ```bash # Build all crates cargo build --all # Build in release mode cargo build --release # Run all tests cargo test --all # Run tests with output cargo test --all -- --nocapture ``` ## License Licensed under MIT