AgentSmith is a TypeScript framework for building autonomous DEX swap agents: programs that watch liquidity pools, apply a pluggable strategy, and submit swaps through a standard executor. It targets the “agentic economy” pattern where off-chain intelligence drives on-chain execution (see Uniswap v4 docs for the current singleton PoolManager + hooks model, and the v4 SDK overview which aligns with viem-based reads/writes).
This repo ships a production-shaped core with Uniswap V2–style pool Swap logs and a V2 router executor—because that ABI is stable, widely deployed, and ideal for learning and integration tests. The same PoolEventListener / SwapExecutor interfaces are the extension points for Uniswap v4 (Swap / custom hook events + Universal Router calldata) without rewriting your strategies.
Autonomous agents with their own keys are increasingly treated as first-class DeFi participants: policy-bound execution (see ERC-8196: AI Agent Authenticated Wallet and related verification ideas such as ERC-8126), identity and tooling stacks, and TypeScript SDKs aimed at machine signers (not only human dApps). For framing, see The Agent Economy (arXiv) and practical write-ups such as Ethereum Agent — TypeScript SDK for autonomous Ethereum use, AI agents as DeFi traders, and crypto AI agents / wallets. Larger wallet + policy + tool stacks (e.g. Coinbase AgentKit — TypeScript) pair well with a small execution-focused library. AgentSmith stays narrow: strategies + events + execution + memory, so you can add policy, multisig, or MPC layers on top.
| Area | What you get |
|---|---|
| Agent core | Agent wires listener → MarketSnapshot → strategy → optional rate limit → ERC-20 allowance → swap → trade memory. Optional balanceTokens loads balances for N assets (multi-token strategies). |
| Strategies | GridStrategy, RebalancingStrategy (2-token, pool price), MultiTokenRebalancingStrategy (N ≥ 2, external numeraire prices), SimpleMomentumStrategy. |
| On-chain listener | createUniswapV2PoolListener — subscribes to V2 pair Swap via viem watchContractEvent. |
| Action executor | UniswapV2SwapExecutor — approve + swapExactTokensForTokens on a V2-compatible router. |
| Memory | InMemoryTradeMemory (process RAM) and JsonlTradeMemory (append-only JSONL file). Same TradeMemoryAPI can back SQLite, IPFS, or Ceramic. |
Language: the framework is TypeScript end-to-end. A Python runtime can still operate the same agent by spawning Node or by porting the small Strategy / TradeMemoryAPI contracts—only TS is implemented here.
- Node.js ≥ 20
- A wallet with RPC access (e.g. Ethereum Sepolia) when you run against a real network—not required for unit tests.
cd AgentSmith
npm install
npm run build
npm testimport { http, createPublicClient, createWalletClient } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { sepolia } from "viem/chains";
import {
Agent,
GridStrategy,
InMemoryTradeMemory,
UniswapV2SwapExecutor,
createErc20BalanceProvider,
createUniswapV2PoolListener,
} from "./dist/index.js";
const account = privateKeyToAccount(process.env.AGENT_PRIVATE_KEY as `0x${string}`);
const transport = http(process.env.RPC_URL);
const publicClient = createPublicClient({ chain: sepolia, transport });
const walletClient = createWalletClient({ account, chain: sepolia, transport });
const token0 = "0x…" as const;
const token1 = "0x…" as const;
const pair = "0x…" as const;
const router = "0x…" as const;
const agent = new Agent({
strategy: new GridStrategy({
token0,
token1,
lowerPrice: 1,
upperPrice: 3,
steps: 6,
tradeSizeToken0: 10n ** 15n,
tradeSizeToken1: 10n ** 15n,
}),
listener: createUniswapV2PoolListener(publicClient, pair),
executor: new UniswapV2SwapExecutor(walletClient, publicClient, router, sepolia),
balanceProvider: createErc20BalanceProvider(publicClient, account.address),
memory: new InMemoryTradeMemory(),
agentAddress: account.address,
token0,
token1,
allowanceSpender: router,
minActionIntervalMs: 60_000,
});
const unwatch = await agent.start();
// … later: unwatch();After npm run build, importing from ./dist/index.js (as above) is the simplest approach inside this repo. If another package lists this folder in dependencies (for example "agentsmith": "file:../AgentSmith"), you can import from the package name agentsmith instead.
Security: never commit private keys. Use environment variables and a dedicated agent wallet with limited funds.
- Events: listen to
PoolManagerSwaplogs with viem, or poll the Uniswap v4 subgraph for swap history when you prefer GraphQL over a live WebSocket RPC. - Reads: use StateView (and related helpers) for slot0/liquidity-style state—see Read pool state and StateView.
- Execution: implement
SwapExecutorwith the Universal Router and v4 planner commands (Uniswap developer guides). - Strategies: keep them unchanged—they only need
MarketSnapshot.priceToken1PerToken0and token balances.
Agent—handlePoolSwap(payload)for tests;start()subscribes to the listener.balanceTokens— optional; passed toBalanceProvider.getBalances(default[token0, token1]). Use withMultiTokenRebalancingStrategyso every holding is inctx.balances.MultiTokenRebalancingStrategy—getPrices(ctx)supplies numeraire prices; emits one swap from the most overweight token to the most underweight (your router must support that leg or you plug in a multi-hop executor).minActionIntervalMs— If the last successful swap was less than this many ms ago, no swaps from the current tick are sent (the strategy still runs). All actions in one tick share the same gate, so a batch of swaps is not partially suppressed. Stateful strategies (e.g. grid) may still advance internal state when swaps are skipped—set the interval to0or tune the strategy if that matters.priceToken1PerToken0FromSwap— marginal price from a V2-styleSwapevent.- Strategies — implement
Strategy.onMarketUpdate(ctx)→AgentAction[](swap|noop).
| Command | Purpose |
|---|---|
npm run build |
prebuild wipes dist/, then emit JavaScript + declarations (tests excluded). |
npm test |
Vitest unit suite. |
npm run typecheck |
tsc --noEmit on library sources. |
Telegram: @AuraTerminal