Blockchain & On-Chain Agents Basics
8 examples to get you started with on-chain agent tools - 5 basic and 3 intermediate.
Search across all documentation pages
8 examples to get you started with on-chain agent tools - 5 basic and 3 intermediate.
You will pin Solana devnet, wrap a tiny SOL transfer as a tool, confirm the signature, and keep the agent from treating chain I/O like a harmless HTTP call.
mkdir onchain-agent-basics && cd onchain-agent-basics
npm init -y
npm install @solana/web3.js bs58 zod
export SOLANA_CLUSTER_URL="https://api.devnet.solana.com"
# DEVNET only. Do not put mainnet keys here.
export AGENT_PAYER_SECRET_BASE58="..."Generate a throwaway keypair when needed:
node -e "const {Keypair}=require('@solana/web3.js'); const k=Keypair.generate(); console.log(require('bs58').encode(k.secretKey)); console.log(k.publicKey.toBase58());"Never rely on a silent default RPC.
import { Connection, clusterApiUrl } from "@solana/web3.js";
const cluster = process.env.SOLANA_CLUSTER ?? "devnet";
if (cluster !== "devnet") {
throw new Error(`Refusing to run basics against cluster=${cluster}`);
}
const connection = new Connection(
process.env.SOLANA_CLUSTER_URL ?? clusterApiUrl("devnet"),
"confirmed",
);
const version = await connection.getVersion();
console.log("rpc ok", version);confirmed here) so later confirms match your policy.Related: Testing On-Chain Agent Flows on Devnet Before Mainnet
The agent model proposes args. The runtime holds the key.
import { Keypair } from "@solana/web3.js";
import bs58 from "bs58";
function loadPayer(): Keypair {
const secret = process.env.AGENT_PAYER_SECRET_BASE58;
if (!secret) throw new Error("AGENT_PAYER_SECRET_BASE58 missing");
return Keypair.fromSecretKey(bs58.decode(secret));
}
const payer = loadPayer();
console.log("payer", payer.publicKey.toBase58());Validate before you build a transaction.
import { z } from "zod";
import { PublicKey, LAMPORTS_PER_SOL } from "@solana/web3.js";
const TransferSolArgs = z.object({
to: z.string().refine((s) => {
try {
new PublicKey(s);
return true;
} catch {
return false;
}
}, "invalid pubkey"),
sol: z.number().positive().max(0.1), // hard tutorial cap
intentId: z.string().min(8).max(128),
});
type TransferSolArgs = z.infer<typeof TransferSolArgs>;
// Example agent-facing description (framework-specific wiring omitted):
// name: transferSOL
// description: Send a small amount of SOL on the pinned cluster. Always pass a unique intentId.intentId is your idempotency handle for retries.0.1 SOL is pedagogical; production caps come from risk policy.Related: Why On-Chain Agents Need Different Guardrails Than Other Tools
import {
Connection,
Keypair,
PublicKey,
SystemProgram,
Transaction,
sendAndConfirmTransaction,
LAMPORTS_PER_SOL,
} from "@solana/web3.js";
const seenIntents = new Set<string>(); // demo only; use durable storage in real agents
export async function transferSOLTool(input: unknown, connection: Connection, payer: Keypair) {
const args = TransferSolArgs.parse(input);
if (seenIntents.has(args.intentId)) {
return { status: "duplicate", intentId: args.intentId };
}
const tx = new Transaction().add(
SystemProgram.transfer({
fromPubkey: payer.publicKey,
toPubkey: new PublicKey(args.to),
lamports: Math.round(args.sol * LAMPORTS_PER_SOL),
}),
);
const signature = await sendAndConfirmTransaction(connection, tx, [payer], {
commitment: "confirmed",
});
seenIntents.add(args.intentId);
return {
status: "confirmed",
intentId: args.intentId,
signature,
cluster: "devnet",
to: args.to,
sol: args.sol,
};
}Set is for learning; see idempotency.// Pseudocode: replace with your framework's tool registry (LangGraph, OpenAI Agents SDK, etc.)
async function handleUserTurn(userText: string) {
// 1) Model chooses tool + args (not shown)
const modelArgs = {
to: process.env.DEMO_RECIPIENT!, // known devnet address
sol: 0.001,
intentId: `demo-${Date.now()}`,
};
// 2) Runtime executes; model never sees the secret key
const result = await transferSOLTool(modelArgs, connection, payer);
return `On-chain result: ${JSON.stringify(result)}`;
}
console.log(await handleUserTurn("Tip the demo wallet 0.001 SOL on devnet"));intentId ↔ signature for support and audits.export async function getBalanceSol(connection: Connection, who: PublicKey) {
const lamports = await connection.getBalance(who, "confirmed");
return lamports / LAMPORTS_PER_SOL;
}
const bal = await getBalanceSol(connection, payer.publicKey);
if (bal < 0.01) {
throw new Error(`Fund devnet payer first; balance=${bal}`);
}getBalance as its own read tool when useful.export async function observeSignature(connection: Connection, signature: string) {
const latest = await connection.getLatestBlockhash("confirmed");
const conf = await connection.confirmTransaction(
{ signature, ...latest },
"confirmed",
);
if (conf.value.err) {
return { status: "failed", signature, err: conf.value.err };
}
return { status: "confirmed", signature };
}function assertSafeSpendEnv() {
if ((process.env.SOLANA_CLUSTER ?? "devnet") !== "devnet") {
throw new Error("basics runner is devnet-only");
}
if (process.env.ALLOW_MAINNET_AGENT === "1") {
throw new Error("ALLOW_MAINNET_AGENT is not valid in basics examples");
}
}
function assertArgs(args: TransferSolArgs) {
if (args.sol > 0.1) throw new Error("amount exceeds basics cap");
}Related: Building transferSOL and createTransaction Agent Tools
Stack versions: Pins from the category manifest (verify at build): OpenRouter (~315+ models, July 2026 pricing/fees); LangGraph 1.0+; CrewAI 1.14+; Microsoft Agent Framework 1.0; Vercel AI SDK 6; Pydantic AI (latest); LlamaIndex (latest); OpenAI Agents SDK (latest + MCP); MCP (Linux Foundation governance); A2A (HTTP+SSE+JSON-RPC 2.0); Solana
@solana/web3.js+@solana/spl-token.
Reviewed by Chris St. John·Last updated Jul 16, 2026