---
title: "Onchain AI agent architectures: five build patterns"
description: "Five build patterns for onchain AI agents, each with a worked example: wallet watchers, event-driven reactors, portfolio rebalancers, detect-then-execute multi-agent splits, and safe pre-mainnet testing."
---

# Onchain AI agent architectures: five build patterns

<ImageBlock
  src="https://media.alchemy.com/blog/onchain-ai-agent-architectures-hero.png"
  alt="Cover illustration for the onchain AI agent architectures guide"
  width={1920}
  height={900}
  priority
/>

AI agents that read and write onchain state have moved from demos into production. The models can reason, agent wallets can sign under scoped permissions, and infrastructure can push an event to an agent seconds after it lands onchain. Architecture is where builds still go wrong. Agents poll for events they could subscribe to, hold signing power in the same process that reads untrusted input, or learn on mainnet with real funds.

An onchain agent is a loop: watch, decide, act. Production agents arrange that loop in five recurring shapes, and each one has a worked example below. Our [guide to building onchain agents](https://www.alchemy.com/blog/how-to-build-onchain-agents) covers the primitives underneath them all (a wallet, a payment rail, a data feed); this page is about how you arrange those primitives.

<EmbeddedTable
  table={{
    columns: [
      { key: "pattern", width: 170, title: "Pattern", dataType: "object" },
      { key: "trigger", width: 200, title: "Trigger", dataType: "object" },
      {
        key: "reads",
        width: 180,
        title: "The agent reads",
        dataType: "object",
      },
      { key: "does", width: 180, title: "The agent does", dataType: "object" },
      {
        key: "when",
        width: 230,
        title: "Reach for it when",
        dataType: "object",
      },
    ],
    data: [
      {
        pattern: { title: "Wallet watcher", tooltip: "", icon: "" },
        trigger: {
          title: "A transfer hits a watched address",
          tooltip: "",
          icon: "",
        },
        reads: { title: "Webhook or WebSocket events", tooltip: "", icon: "" },
        does: { title: "Wakes and evaluates", tooltip: "", icon: "" },
        when: {
          title: "You track deposits, whales, or counterparties",
          tooltip: "",
          icon: "",
        },
        id: 0,
      },
      {
        pattern: { title: "Event-driven reactor", tooltip: "", icon: "" },
        trigger: { title: "A contract emits an event", tooltip: "", icon: "" },
        reads: { title: "Filtered logs", tooltip: "", icon: "" },
        does: { title: "Responds with a transaction", tooltip: "", icon: "" },
        when: {
          title: "Protocol state changes drive your strategy",
          tooltip: "",
          icon: "",
        },
        id: 1,
      },
      {
        pattern: { title: "Portfolio rebalancer", tooltip: "", icon: "" },
        trigger: {
          title: "Allocation drifts past a band",
          tooltip: "",
          icon: "",
        },
        reads: { title: "Balances and prices", tooltip: "", icon: "" },
        does: { title: "Proposes a swap", tooltip: "", icon: "" },
        when: {
          title: "You hold target weights and want them enforced",
          tooltip: "",
          icon: "",
        },
        id: 2,
      },
      {
        pattern: { title: "Detect-then-execute split", tooltip: "", icon: "" },
        trigger: {
          title: "The detector emits a signal",
          tooltip: "",
          icon: "",
        },
        reads: {
          title: "Everything, while signing nothing",
          tooltip: "",
          icon: "",
        },
        does: {
          title: "The executor verifies, then signs",
          tooltip: "",
          icon: "",
        },
        when: {
          title: "Untrusted input meets real money",
          tooltip: "",
          icon: "",
        },
        id: 3,
      },
      {
        pattern: { title: "Pre-mainnet testing", tooltip: "", icon: "" },
        trigger: { title: "Every new capability", tooltip: "", icon: "" },
        reads: { title: "Testnets and dry-runs", tooltip: "", icon: "" },
        does: { title: "Promotes the agent in stages", tooltip: "", icon: "" },
        when: {
          title: "Always, before the other four go live",
          tooltip: "",
          icon: "",
        },
        id: 4,
      },
    ],
  }}
/>

## How do you build a real-time wallet watcher agent?

Register the addresses you care about with a webhook and let the chain come to you. Polling balances in a loop burns compute and still misses the moment. A push pipeline delivers the transfer seconds after it lands, which is the entire job of a watcher.

Say the agent tracks a fund's counterparty wallets and flags any large USDC movement. On Alchemy, a single [Address Activity webhook](https://www.alchemy.com/webhooks) covers native, ERC-20, ERC-721, and ERC-1155 transfers for [up to 100,000 addresses](https://www.alchemy.com/docs/reference/webhook-types), so one webhook watches the entire counterparty list. If the agent runs as a long-lived process and you would rather not expose a public URL, [WebSocket subscriptions](https://www.alchemy.com/docs/reference/subscription-api) do the same job in-process. Subscribe with `alchemy_minedTransactions` and you get confirmed transactions already filtered to your addresses, no log parsing required. On Solana, [Yellowstone gRPC streaming](https://www.alchemy.com/solana-grpc) fills the same slot at $75 per TB with gapless reconnects. When you are unsure which transport fits, our [webhooks vs WebSockets vs gRPC comparison](https://www.alchemy.com/overviews/webhooks-vs-websockets-vs-grpc) draws the lines in detail.

The handler itself should do almost nothing. Prove the delivery came from Alchemy, dedupe it, hand the event to the agent's decide step, and only then acknowledge it:

<CodeSnippet
  language="typescript"
  code={`import express from "express";
import { createHmac, timingSafeEqual } from "node:crypto";
const SIGNING_KEY = process.env.ALCHEMY_WEBHOOK_SIGNING_KEY!; // from the webhook's dashboard settings
const USDC = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"; // canonical USDC contract on Ethereum mainnet
const seen = new Set<string>();
function remember(key: string) {
  seen.add(key);
  if (seen.size > 10_000) seen.delete(seen.values().next().value!); // bound the set; only recent deliveries repeat
}
function wake(signal: unknown) {
  // The agent's decide step starts here. Queue the signal;
  // don't run model reasoning inside the request handler.
}
const app = express();
app.use(
  express.json({
    verify: (req, _res, buf) => {
      (req as { rawBody?: Buffer }).rawBody = buf; // keep the raw bytes for the HMAC check
    },
  })
);
app.post("/hooks/address-activity", (req, res) => {
  const sig = Buffer.from(String(req.headers["x-alchemy-signature"] ?? ""), "hex");
  const expected = createHmac("sha256", SIGNING_KEY)
    .update((req as { rawBody?: Buffer }).rawBody ?? Buffer.alloc(0))
    .digest();
  if (sig.length !== expected.length || !timingSafeEqual(sig, expected)) {
    return res.sendStatus(401); // not signed with our key; never process it
  }
  for (const transfer of req.body.event.activity) {
    const key = \`\${transfer.hash}:\${transfer.log?.logIndex ?? "native"}\`; // one tx can carry several transfers
    if (seen.has(key)) continue; // repeat delivery, already handled
    if (transfer.rawContract?.address?.toLowerCase() === USDC && transfer.value > 50_000) {
      wake({ kind: "large-transfer", ...transfer }); // if this throws, the key stays unmarked and the retry redelivers
    }
    remember(key); // mark handled only after the handoff succeeded
  }
  res.sendStatus(200); // ack only after queueing: a crash above gets retried, not lost
});
app.listen(8080);`}
/>

Three habits keep this pattern reliable in production. Verify the HMAC signature before trusting a delivery, because without it anyone who learns the endpoint URL can POST a fake transfer and drive your agent. Match tokens by contract address, never by symbol, since anyone can deploy a token that calls itself USDC and send it to a watched address. And deliveries are at-least-once, so the dedupe record is not optional. The in-memory set works for one long-lived process; a service that restarts or runs replicas needs the record somewhere shared and durable, like a Redis key or a database row, because a duplicate wake-up for a trading agent is a duplicate transaction. The mechanical filter belongs in the handler while the model stays out of it, because an LLM call per transfer costs more than the infrastructure that delivered the transfer.

## How do you let an AI agent monitor smart contract events and react automatically?

Subscribe to the contract's logs, filter to the one or two events that matter, and make the handler idempotent. Contract events are the cleanest trigger an agent can get, since the contract states exactly what happened and in what order.

Two surfaces cover this on Alchemy. [Custom webhooks](https://www.alchemy.com/docs/reference/custom-webhooks-quickstart) take a GraphQL filter, so you can match on a contract address and event topics and only ever receive the logs you asked for. That suits serverless reactors. For a long-running agent, a WebSocket logs subscription keeps everything in one process. Here is a reactor watching a Uniswap v3 pool, the kind of feed a trading agent uses to notice when a single swap moves the price:

<CodeSnippet
  language="typescript"
  code={`import { createPublicClient, webSocket, parseAbiItem } from "viem";
import { mainnet } from "viem/chains";
function react(args: unknown) {
  // Decide step: is this swap big enough to act on?
}
const client = createPublicClient({
  chain: mainnet,
  transport: webSocket("wss://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY"),
});
client.watchEvent({
  address: "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640", // USDC/WETH 0.05% pool
  event: parseAbiItem(
    "event Swap(address indexed sender, address indexed recipient, int256 amount0, int256 amount1, uint160 sqrtPriceX96, uint128 liquidity, int24 tick)"
  ),
  onLogs: (logs) => {
    for (const log of logs) {
      if (log.removed) continue; // reorg removal notice; irreversible actions also need the confirmation-depth rule below
      react(log.args);
    }
  },
});`}
/>

The `log.removed` check matters more than it looks. Chains reorganize, and a log your agent already acted on can disappear from the canonical chain a few blocks later. An idempotent handler plus a confirmation-depth rule (wait a few blocks before irreversible actions) is the standard defense. The reactor's other discipline is the same as the watcher's. The subscription filter does the cheap elimination, and the model only sees events that survived it.

## What do you need to build a DeFi portfolio rebalancing agent?

Four pieces: current balances, current prices, a drift rule, and a swap path. The agent reads the first two, checks the third, and only touches the fourth when the rule trips.

Take a treasury agent holding a 50/30/20 split of WETH, USDC, and WBTC. Balances come from the [Portfolio API](https://www.alchemy.com/docs/reference/portfolio-apis), which returns a wallet's tokens across several networks in one request instead of a fan-out of per-chain calls. Values come from the [Prices API](https://www.alchemy.com/docs/reference/prices-api-quickstart). The drift rule is arithmetic. A common starting point is a five-percentage-point band around each target weight. Check it on a timer, because drift mostly comes from prices moving rather than tokens moving, and a price change produces no onchain event to react to. A transfer reported by the watcher (pattern one) is the supplemental trigger that catches deposits and withdrawals the moment they land.

<CodeSnippet
  language="typescript"
  code={`const TARGET = { WETH: 0.5, USDC: 0.3, WBTC: 0.2 } as const;
const BAND = 0.05; // rebalance when a weight drifts 5 points from target
const WALLET = "0xYourTreasuryWallet"; // the wallet the agent manages
const res = await fetch(
  \`https://api.g.alchemy.com/data/v1/\${process.env.ALCHEMY_API_KEY}/assets/tokens/by-address\`,
  {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      addresses: [{ address: WALLET, networks: ["eth-mainnet"] }],
    }),
  }
);
const { data } = await res.json();
// Price each balance with the Prices API, sum to a total, then:
for (const [symbol, target] of Object.entries(TARGET)) {
  const drift = weightOf(symbol, data) - target; // weightOf: your portfolio math
  if (Math.abs(drift) > BAND) {
    propose({ symbol, drift }); // propose: log it and request approval; never swap directly
  }
}`}
/>

The execution side should not hold a raw private key. With a [scoped agent wallet](https://www.alchemy.com/blog/agent-wallets-alchemy-cli), the key stays in custody and the session carries only the capabilities you granted. Spending an ERC-20 needs an allowance for the router before the swap. Approving the exact amount each time costs one extra transaction and leaves no standing allowance for an attacker to drain:

<CodeSnippet
  language="bash"
  code={`# before each swap: let the router from your quote spend exactly this swap's WETH
alchemy evm approve 0xRouterFromQuote --token-address 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 --amount 0.4 -n eth-mainnet
# swap WETH into USDC (Ethereum mainnet contract addresses)
alchemy evm swap execute \\
  --from 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 \\
  --to 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 \\
  --amount 0.4 --slippage 0.5 -n eth-mainnet \\
  --signer session --json --no-interactive`}
/>

Notice the shape of the loop. The agent proposes, and something else approves. Early on that something is you. Later it can be a policy check on size, slippage, and asset allowlist. Our [DeFi AI agents overview](https://www.alchemy.com/overviews/defi-ai-agents) covers why that separation is the norm across production DeFi agents, and [gas sponsorship](https://www.alchemy.com/gasless-transactions) removes the last operational chore by paying fees under a policy you control, so the agent never needs to manage a gas balance.

## What's the best architecture for a detect-then-execute multi-agent system?

Split the agents by privilege, not by workload. The detector reads everything and can sign nothing. The executor signs transactions and believes nothing it has not re-verified.

Think of it as an analyst and a trader. The analyst watches the market all day and can talk to anyone. The trader takes the analyst's calls, checks them, and is the only one with access to the account.

The popular framing of this pattern comes from general agent tooling, where a planner model produces steps and executor agents run tools. That framing is about orchestration. Onchain, the split earns its complexity for a harder reason, custody. A detector consumes untrusted input all day: mempool noise, third-party APIs, event streams, sometimes social feeds. Any of that can carry a prompt injection. If the process reading hostile input is also the process holding signing power, one poisoned message can become a signed transaction. Separate them, and the worst a hijacked detector can do is make a bad suggestion that the executor throws away.

The detector is built from patterns one and two, wired to push infrastructure rather than a polling loop. It emits signals over a queue, and the queue doubles as your audit log. Keep the signal contract small and verifiable:

<CodeSnippet
  language="json"
  code={`{
  "kind": "arb-opportunity",
  "pair": "WETH/USDC",
  "evidence": { "txHash": "0x…", "block": 23411005 },
  "proposal": { "action": "swap", "from": "WETH", "to": "USDC", "amount": "0.4" },
  "observedAt": "2026-09-05T09:14:03Z",
  "expiresAt": "2026-09-05T09:14:33Z"
}`}
/>

The executor enforces three rules before acting on any signal:

- **Re-verify the evidence onchain.** Read the referenced transaction yourself rather than trusting the detector's summary. The detector's job was noticing; proving is the executor's.
- **Enforce the expiry.** Acting on a stale opportunity is how detect-then-execute systems lose money even without an attacker in the loop.
- **Cap spend at the wallet layer.** A per-signal and per-day budget lives in the [scoped session](https://www.alchemy.com/blog/agent-wallets-alchemy-cli), which you can revoke from the dashboard the moment behavior looks wrong.

On the tool side, the executor needs just two things: an RPC connection for verification and a signing surface. The detector is the hungrier half, and pairing it with indexed data (the [Transfers API](https://www.alchemy.com/docs/reference/transfers-api-quickstart) for history, the Portfolio API for state) keeps its token spend on reasoning instead of on decoding raw chain data. Our [comparison of blockchain APIs for autonomous agents](https://www.alchemy.com/overviews/best-blockchain-apis-for-autonomous-onchain-agents) covers the infrastructure side of that choice.

## How do you test AI agent transactions safely before going to mainnet?

Put four gates between the agent and real value: dry-run every transaction, cap what the key can spend, rehearse the full loop on a testnet, and keep a human approval on anything that moves funds.

- **Dry-run first.** The Alchemy CLI previews any send without signing or broadcasting (`alchemy evm send 0xRecipient 0.4 --dry-run`). In code, viem's `simulateContract` validates a contract call against live mainnet state without submitting it, so the rehearsal uses real prices and real pool depth rather than a stale fixture.
- **Cap the key.** A scoped session wallet expires on the schedule you set and can only do what you approved it for, and [gas sponsorship policies](https://www.alchemy.com/gasless-transactions) add allowlists and spend limits at the fee layer. A capped key turns a worst-case bug from an account drain into a bounded loss.
- **Rehearse on a testnet.** Point the same code at a Sepolia or Base Sepolia endpoint and fund the agent from our [testnet faucets](https://www.alchemy.com/faucets). The run is worthless if the code changes between rehearsal and production, so keep the network name in configuration and change nothing else.
- **Gate fund-moving actions.** While the agent is young, every send, swap, and approval waits for an explicit yes. Most teams loosen the gates deliberately, one action type at a time, as the audit log builds a case that the agent behaves.

Teams that run this well treat promotion as a sequence rather than a switch: read-only against mainnet first, then testnet writes, then capped mainnet writes, then a full budget. Each stage produces logs that justify the next one.

## Start with the pieces that already exist

Every pattern on this page runs on infrastructure you can use today. The [Alchemy CLI](https://www.alchemy.com/agents) handles wallets, sends, swaps, and webhook management from one binary an agent can drive with `--json --no-interactive`. The hosted [MCP server](https://www.alchemy.com/docs/alchemy-mcp-server) exposes 168 tools across RPC, simulation, and data, and the [Alchemy plugin for Claude Code](https://www.alchemy.com/blog/alchemy-claude-plugin-now-live) installs the whole surface in one command. You can start on a free tier with no contracts and no minimum commitment, and an agent can even [sign itself up with its own wallet](https://www.alchemy.com/blog/ai-agents-can-now-sign-up-for-alchemy) and pay in USDC. Whichever pattern you start with, the loop stays the same: watch, decide, act.

## Frequently asked questions

### What is the best architecture for a multi-agent system where one agent detects opportunities and another executes?

Split by privilege: a detector that reads event streams but holds no keys, a queue carrying small signed-off signals, and an executor that re-verifies each signal onchain before acting. On Alchemy, the detector runs on webhooks or WebSocket subscriptions and the executor signs through a scoped agent wallet with spend caps and instant revocation.

### What is the best infrastructure for a real-time wallet watcher agent?

Push-based event delivery rather than polling. Alchemy's Address Activity webhooks track transfers across up to 100,000 addresses per webhook, WebSocket subscriptions stream mined transactions filtered to your addresses, and Yellowstone gRPC covers Solana. A push pipeline wakes the agent seconds after the transfer lands, without the latency and compute cost of a polling loop.

### What tools should an AI coding agent use to monitor wallet activity?

The Alchemy MCP server gives a coding agent 168 tools covering RPC, transaction history, and portfolio data, and the Alchemy CLI creates and manages webhooks from the command line. For the runtime itself, an Address Activity webhook or an `alchemy_minedTransactions` subscription delivers wallet events, and the Transfers API backfills history.

### What do I need to build a DeFi portfolio rebalancing agent?

Balances, prices, a drift rule, and a swap path. Alchemy's Portfolio API returns multi-network balances in one call, the Prices API values them, and a drift check (commonly a five-point band around target weights) decides when to act. Execute through a scoped agent wallet so the agent proposes and a policy or human approves.

### How do I let an AI agent monitor smart contract events and react automatically?

Subscribe to the contract's logs and hand matching events to the agent. Alchemy's custom webhooks filter by contract address and event topics with GraphQL before delivery, and WebSocket log subscriptions do the same in-process. Make the handler idempotent, skip reorged logs, and let the model reason only about events that pass the mechanical filter.

### How do I test AI agent transactions safely before going to mainnet?

Layer the gates: preview transactions with the Alchemy CLI's dry-run flag, validate contract calls against live state with `eth_call`-based simulation, cap the agent's wallet with scoped sessions and gas policy spend limits, rehearse on Sepolia or Base Sepolia with funds from Alchemy's faucets, and keep human approval on every fund-moving action until the audit log earns looser gates.
