Skip to content

Latest commit

 

History

13 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Aomi Client Example

License: MIT npm version TypeScript

The best blockchain harness for agentic AI - on-chain execution with runtime, skills, and component library.

What is this?

This repository is a reference implementation built on @aomi-labs/client, the TypeScript SDK for Aomi. The bot rotates between a risk asset and a stable asset using moving-average signals, sending natural-language trade instructions to the Aomi backend and auto-signing the resulting on-chain transactions and EIP-712 payloads locally.

This repo shows how to:

  • run portfolio logic locally
  • send natural-language trade instructions to the Aomi backend
  • auto-sign on-chain transaction requests with viem
  • auto-sign EIP-712 payloads for flows like CoW Swap

The current bot rotates between a configured risk asset and a stable asset in allocation steps based on moving-average signals.

What The Bot Does

The bot does not call DEX contracts directly.

Instead:

  1. it fetches market data
  2. it decides the next allocation state locally
  3. it asks the Aomi agent to execute the trade
  4. it auto-signs any wallet request emitted by the session
  5. it tracks portfolio state and logs results

Current allocation buckets:

  • full_risk
  • reduced_risk
  • mostly_stable
  • full_stable

Architecture

graph TB
    subgraph Bot["Bot Process (this repo)"]
        IDX["index.ts<br/><i>Main Loop</i>"]
        STR["strategy.ts<br/><i>Momentum Logic</i>"]
        MKT["market.ts<br/><i>Price + OHLCV</i>"]
        AGT["agent.ts<br/><i>Session Wrapper</i>"]
        SGN["signer.ts<br/><i>Tx + EIP-712 Signing</i>"]
        CFG["config.ts<br/><i>Env Config</i>"]

        IDX -->|"fetchMarketData()"| MKT
        IDX -->|"evaluate()"| STR
        STR -->|"TradeAction[]"| IDX
        IDX -->|"executeAction()"| AGT
        AGT -->|"wallet requests"| SGN
        CFG -.->|"BotConfig"| IDX
    end

    subgraph Aomi["Aomi Backend"]
        API["REST API<br/>/api/chat, /api/state"]
        SSE["SSE Stream<br/>tool updates + notices"]
        AI["AI Harness<br/><i>EVM Engine + execution planning</i>"]
    end

    subgraph Data["Market Data"]
        GECKO["GeckoTerminal<br/>token price + pool stats + OHLCV"]
    end

    subgraph Chain["Blockchain"]
        RPC["JSON-RPC"]
        DEX["DEXs / Routers / Orderflow"]
    end

    IDX -->|"natural-language trade intent"| API
    AGT <-->|"polling + SSE"| API
    AGT <-->|"tool events"| SSE
    AI --> DEX
    SGN -->|"sendTransaction / signTypedData"| RPC
    MKT --> GECKO
    RPC --> DEX
Loading

Sequence Diagram

sequenceDiagram
    participant Market as GeckoTerminal
    participant Bot as Bot Loop
    participant Strategy as Strategy Engine
    participant Agent as Aomi Session
    participant Backend as Aomi Backend
    participant Chain as EVM Chain

    Bot->>Agent: resolveWallet(address, chainId)
    Bot->>Agent: syncUserState()

    loop Every tick
        Bot->>Market: fetch token price + pool stats
        Market-->>Bot: price, 24h stats
        Bot->>Market: refresh OHLCV if stale
        Market-->>Bot: hourly candles

        Bot->>Strategy: evaluate(config, state, market)
        Strategy-->>Bot: TradeAction[] or []

        alt No action
            Bot-->>Bot: log portfolio stats
        else Action required
            Bot->>Agent: session.send("Swap risk/stable according to signal")
            Agent->>Backend: chat request
            Backend-->>Agent: tool updates / messages
            Backend-->>Agent: wallet_tx_request or wallet_eip712_request

            alt On-chain transaction
                Agent->>Chain: auto-sign and broadcast
                Chain-->>Agent: tx hash + receipt
                Agent->>Backend: session.resolve(req.id, { txHash })
            else EIP-712 signature
                Agent->>Chain: signTypedData
                Chain-->>Agent: signature
                Agent->>Backend: session.resolve(req.id, { signature })
            end

            Backend-->>Agent: final messages
            Agent-->>Bot: send() completes
            Bot-->>Bot: applyTrade() + log portfolio stats
        end
    end

    Bot->>Agent: session.close()
Loading

Current Strategy

The strategy in src/strategy.ts is a simple momentum rotation model:

  • fast MA well above slow MA: stay full_risk
  • positive but weakening spread: trim to reduced_risk
  • fast MA below slow MA: move to mostly_stable
  • strongly negative spread: move to full_stable
  • if drawdown exceeds MAX_DRAWDOWN, trigger emergency_exit

Only one step is taken at a time, and TRADE_COOLDOWN_MS prevents immediate churn.

Market Data

src/market.ts currently uses GeckoTerminal for:

  • token price by RISK_ASSET_ADDRESS
  • pool-level 24h stats by OHLCV_POOL_ADDRESS
  • hourly OHLCV candles for the slow moving average

Behavior today:

  • price samples are stored in memory for the fast MA
  • OHLCV is cached and refreshed every OHLCV_REFRESH_MS
  • GeckoTerminal 429s are retried with backoff
  • if OHLCV refresh fails, cached candles are reused

Execution And Signing

src/agent.ts wraps Session from @aomi-labs/client.

The bot:

  • creates a session with app, publicKey, and wallet userState
  • calls resolveWallet(...) so the session knows the wallet address and chain
  • sends messages with blocking session.send(...)
  • auto-handles wallet_tx_request
  • auto-handles wallet_eip712_request

Signing is local:

  • transactions are broadcast with viem in src/signer.ts
  • EIP-712 payloads are signed with wallet.signTypedData(...)

When a wallet request arrives, the bot logs messages like:

[bot] Auto-signing ERC-20 approval transaction...
[signer] Tx request id=...
[signer] Tx broadcast: 0x...
[bot] Auto-sign complete: ERC-20 approval transaction.

Quick Start

pnpm install
cp .env.example .env
pnpm start

For watch mode:

pnpm dev

Required Setup

Edit .env based on .env.example.

Minimum required values:

AOMI_BASE_URL=https://aomi.dev
AOMI_API_KEY=your-api-key
AOMI_APP=default

PRIVATE_KEY=0x...
RPC_URL=https://eth.llamarpc.com
CHAIN_ID=1

RISK_ASSET=wSOL
RISK_ASSET_ADDRESS=0x...
STABLE_ASSET=USDC
STABLE_ASSET_ADDRESS=0x...

GECKO_NETWORK=eth
OHLCV_POOL_ADDRESS=0x...

Configuration

src/config.ts loads the following env vars.

Aomi

  • AOMI_BASE_URL
  • AOMI_API_KEY
  • AOMI_APP
  • PUBLIC_KEY

Wallet / Chain

  • PRIVATE_KEY
  • RPC_URL
  • CHAIN_ID

Supported chain labels in code today:

  • 1 -> Ethereum mainnet
  • 42161 -> Arbitrum
  • 8453 -> Base
  • 10 -> Optimism
  • 137 -> Polygon

Trading Pair

  • RISK_ASSET
  • RISK_ASSET_ADDRESS
  • STABLE_ASSET
  • STABLE_ASSET_ADDRESS

Market Data

  • GECKO_NETWORK
  • OHLCV_POOL_ADDRESS
  • OHLCV_REFRESH_MS

Strategy

  • FAST_MA_PERIOD
  • SLOW_MA_PERIOD
  • MA_SPREAD_THRESHOLD
  • MAX_SLIPPAGE
  • MAX_DRAWDOWN
  • TRADE_COOLDOWN_MS

Starting Portfolio

  • INITIAL_RISK_AMOUNT
  • INITIAL_STABLE_AMOUNT

Bot Runtime

  • LOOP_INTERVAL_MS
  • DEBUG

Project Structure

src/
  index.ts      Main loop
  config.ts     Env loading and chain labels
  types.ts      Shared types
  strategy.ts   Momentum allocation logic
  market.ts     GeckoTerminal price, stats, and OHLCV fetchers
  agent.ts      Aomi session wrapper and auto-sign handlers
  signer.ts     viem wallet client + tx/EIP-712 signing
scripts/
  link-client.mjs

Using @aomi-labs/client Yourself

This repo's integration pattern is:

import {
  Session,
  type WalletRequest,
  type WalletTxPayload,
} from "@aomi-labs/client";

const session = new Session(
  { baseUrl: process.env.AOMI_BASE_URL!, apiKey: process.env.AOMI_API_KEY },
  {
    app: process.env.AOMI_APP ?? "default",
    publicKey: walletAddress,
    userState: { address: walletAddress, chainId: 1 },
  },
);

session.resolveWallet(walletAddress, 1);

session.on("wallet_tx_request", async (req: WalletRequest) => {
  const payload = req.payload as WalletTxPayload;
  const txHash = await walletClient.sendTransaction({
    account,
    to: payload.to as `0x${string}`,
    data: payload.data as `0x${string}` | undefined,
    value: payload.value ? BigInt(payload.value) : undefined,
  });

  await session.resolve(req.id, { txHash });
});

const result = await session.send("Swap 500 USDC for ETH on Ethereum mainnet.");
console.log(result.messages);

If you need gasless order flows or permits, also register wallet_eip712_request and resolve with { signature }.

Notes

  • The bot's portfolio state is tracked locally after each executed action.
  • The trade prompt is natural language; route selection is delegated to the Aomi agent.
  • The current code logs market, action, and portfolio lines on every loop.
  • Graceful shutdown prints a final portfolio report.

FAQ

Can I use @aomi-labs/client for a different strategy or use case? Yes. This repo is a reference implementation for a specific momentum rotation model, but @aomi-labs/client is general-purpose — use it to send any natural-language request to the Aomi backend and auto-sign the resulting wallet requests. Swap out src/strategy.ts and src/market.ts for your own logic and data sources.

Which chains does the bot support? EVM-compatible chains today. src/config.ts ships with labels for Ethereum (1), Arbitrum (42161), Base (8453), Optimism (10), and Polygon (137). Set the active chain via the CHAIN_ID env var and provide an RPC_URL for that chain.

Does the bot ever send my private key remotely? No. The bot reads PRIVATE_KEY from your local .env and signs transactions locally with viem. The private key is never sent to the Aomi backend or any external signer.

What's the difference between @aomi-labs/client and @aomi-labs/widget-lib? @aomi-labs/client is the TypeScript SDK for programmatic use — agents, bots, servers, and scripts. @aomi-labs/widget-lib is the React widget (<AomiFrame />) for embedding a chat UX in a user-facing app. This bot uses the client; apps that want a drop-in conversational assistant use the widget.

Does the bot retry if the Aomi backend or GeckoTerminal fails? Partially. GeckoTerminal 429 responses are retried with backoff, and if an OHLCV refresh fails, cached candles are reused. Chat and execution retries are not built in — check the logs and re-run if a session errors out.

Releases

Packages

Contributors

Languages