Skip to content

Architecture

Murtaza Ali Imtiaz edited this page Jun 5, 2026 · 4 revisions

Architecture

System overview

┌──────────────────────────────────────────────────────────────────────────┐
│              polar-bear-rig-onchain  ·  Polar Bear (🍨)               │
│         rig-onchain-kit  ·  SignerContext  ·  Solana devnet              │
└──────────────────────────────────────────────────────────────────────────┘

            CLI Entry Point (main.rs)
            --mode [full|balance|quote|signer]  [--no-agent]
                         │
         ┌───────────────┴──────────────────────────────────┐
         │                                                  │
         ▼  --mode full                                     ▼  --mode full --no-agent
┌─────────────────────────────────────────────┐   ┌────────────────────────────────────┐
│  rig-core Agent (claude-sonnet-4-6)         │   │  Direct subsystem sequence         │
│  PEV loop governance                        │   │  balance → quote → signer          │
│  SolanaBalanceTool                          │   │  (no Anthropic client constructed) │
│  JupiterQuoteTool (dry-run)                 │   │  No ANTHROPIC_API_KEY required     │
│  SignerIsolationTool                        │   └────────────┬───────────────────────┘
│  Requires ANTHROPIC_API_KEY                 │                │
└────────────────────┬────────────────────────┘                │
                     │                                         │
         ┌───────────┴─────────────────────────────────────────┘
         │
         ▼
┌─────────────────────┐      ┌──────────────────────────────────────────────┐
│  onchain::signer    │      │  onchain::balance   onchain::jupiter         │
│  tokio::task_local! │      │                                              │
│  LocalSolanaSigner  │      │  SolanaClient        JupiterClient           │
│  with_signer(f)     │      │  (devnet-only)        (dry_run=true, always) │
│  snapshot_active()  │      │  get_balance()        GET /v6/quote          │
│  IsolationReport    │      │  → BalanceResult      → JupiterQuote         │
└─────────────────────┘      └──────────────────────────────────────────────┘

Module map

src/
├── lib.rs                crate root; re-exports agent, config, onchain
├── main.rs               binary entry; clap CLI; mode dispatch; --no-agent branch
├── config.rs             Config::from_env() - infallible; anthropic_api_key: Option<String>
├── agent/
│   ├── mod.rs            build() → impl Prompt; rig-core agent assembly; key validation
│   └── tools.rs          SolanaBalanceTool, JupiterQuoteTool, SignerIsolationTool
└── onchain/
    ├── mod.rs            execute_pipeline(), demo_signer(): public entry points
    ├── signer.rs         tokio::task_local! SignerContext; with_signer; IsolationReport
    ├── balance.rs        SolanaClient::devnet(); query_balance; BalanceResult
    ├── jupiter.rs        JupiterClient::dry_run(); get_quote; JupiterQuote
    └── types.rs          Lamports newtype; conversion helpers

Pipeline data flow

Full pipeline (--mode full)

main()
  └─ run_full(cfg, amount)
       │
       ├─ SolanaClient::devnet()           → Arc<SolanaClient>
       ├─ JupiterClient::dry_run()         → Arc<JupiterClient>
       ├─ agent::build(cfg, solana, jupiter)
       │    └─ cfg.anthropic_api_key.as_deref().context(...)?  ← key validation here
       │    └─ anthropic::Client::new(api_key)?
       │    └─ client.agent("claude-sonnet-4-6").tool(...).build()
       │
       └─ agent.prompt(task_prompt)
            │  (rig-core PEV loop)
            │
            ├─ Tool call: solana_balance(address)
            │    └─ SolanaClient::query_balance()
            │         └─ solana_client RPC get_balance → BalanceResult
            │
            ├─ Tool call: jupiter_quote(sol_amount, slippage_bps)
            │    └─ JupiterClient::get_quote()
            │         └─ reqwest GET /v6/quote → JupiterQuote
            │
            └─ Tool call: signer_isolation_log(task_id)
                 └─ snapshot_active()
                      └─ CURRENT_SIGNER.try_with() → Option<SignerSnapshot>

Full pipeline (--mode full --no-agent)

main()
  └─ run_full_no_agent(cfg, amount)
       │
       ├─ run_balance(cfg)    → SolanaClient::query_balance()
       ├─ run_quote(cfg)      → JupiterClient::get_quote()
       └─ demo_signer(cfg)    → signer::demo_signer() - 3 concurrent tasks

No agent::build() call - ANTHROPIC_API_KEY is never read.

SignerContext lifecycle

LocalSolanaSigner::ephemeral(label)
       │
with_signer(signer, || async { ... })
       │
       ├─ CURRENT_SIGNER.scope(arc, future)  ← task-local slot installed
       │       │
       │    [agent runs: balance → quote → isolation log]
       │       │
       └─ scope exits (normal / ? / panic)   ← slot evicted automatically

Tech stack

Layer Technology
AI Agent Framework rig-core ≥ 0.36
On-chain bridge rig-onchain-kit pattern (tokio::task_local! SignerContext)
Async runtime Tokio
Blockchain Solana (devnet only)
DEX aggregator Jupiter V6 /quote (dry-run, no transactions)
CLI clap ≥ 4
Logging tracing + tracing-subscriber
HTTP / TLS reqwest ≥ 0.13 + rustls
Error handling anyhow + thiserror ≥ 2
Env config dotenvy
Serialisation serde + serde_json
IDE Zed (tasks.json · debug.json · settings.json)

Key design decisions

Decision Rationale
tokio::task_local! not thread_local! Tokio tasks can migrate between OS threads at await points; task_local! scopes the signer slot to the task, not the thread.
anthropic_api_key: Option<String> Config::from_env() is infallible; key validation is deferred to agent::build(), which is only called for --mode full without --no-agent.
--no-agent flag Enables full-pipeline smoke tests and demos without an API key; CI can verify all subsystems without injecting secrets.
Lib + bin targets Integration tests are external crates that import polar_bear_rig_onchain::*; the lib target makes this possible.
Rust 2024 edition Matches the rig upstream repository and polar-bear-rig-hft.
Client::new(api_key)? Client::new is fallible in rig-core ≥ 0.36; ClientBuilder is the pre-0.36 API.
Both CompletionClient + ProviderClient in scope Both traits must be imported for .agent() to resolve in rig-core ≥ 0.36.
dotenvy not dotenv dotenv crate is unmaintained; dotenvy is the maintained fork.
reqwest "^0.13" + rustls feature rustls-tls was renamed to rustls in reqwest 0.13; the old name causes a compile error.
#[ignore] on live tests Prevents CI failures when ANTHROPIC_API_KEY is absent.
strip = "debuginfo" in release Reduces binary size; mirrors rig upstream release profile.
//! not /// at file tops /// at file scope with no following item triggers unused_doc_comments lint.

Related projects

Clone this wiki locally