Skip to main content

Build an Agent

Build agents that can autonomously interact with APIs, make x402 payments, and access paywalled resources. This guide covers two approaches: a basic agent for simple use cases and an AI-powered agent using LangChain for more complex decision-making.

Prerequisites

  • Node.js and npm installed
  • A SKALE Chain endpoint
  • Understanding of x402 protocol (see Make Payments)
  • Familiarity with TypeScript/JavaScript
  • A wallet with funds (USDC or supported token)
  • Anthropic API key (for LangChain agent with Claude)

Overview

Agent TypeBest For
Basic AgentSimple automation, scripts, scheduled tasks
LangChain AgentAI-powered services with payment protection, server-side AI integrations

Implementation

A basic agent handles x402 payments programmatically without AI. Perfect for automated scripts, bots, or services that need to access paywalled resources.

Step 1: Install Dependencies

npm install @x402/core @x402/evm viem dotenv

Step 2: Set Up Environment Variables

Create a .env file:
# Your wallet private key (never commit this!)
PRIVATE_KEY=0xYourPrivateKey

# USDC token contract on SKALE
PAYMENT_TOKEN_ADDRESS=0x2e08028E3C4c2356572E096d8EF835cD5C6030bD
PAYMENT_TOKEN_NAME="Bridged USDC (SKALE Bridge)"

Step 3: Define Your Chain

Create a file chain.ts:
import { defineChain } from "viem";

export const skaleChain = defineChain({
    id: 324705682,
    name: "SKALE Base Sepolia",
    nativeCurrency: { decimals: 18, name: "Credits", symbol: "CREDIT" },
    rpcUrls: {
        default: { http: ["https://base-sepolia-testnet.skalenodes.com/v1/base-testnet"] },
    },
});

Step 4: Create the Agent Class

Create a file agent.ts:
import { x402Client, x402HTTPClient } from "@x402/core/client";
import { ExactEvmScheme } from "@x402/evm";
import { privateKeyToAccount } from "viem/accounts";
import { createPublicClient, http, formatEther } from "viem";
import { skaleChain } from "./chain";
import "dotenv/config";

type AccessResult = {
    success: boolean;
    data?: unknown;
    error?: string;
};

class BasicAgent {
    private httpClient: x402HTTPClient;
    private walletAddress: string;
    private publicClient: ReturnType<typeof createPublicClient>;

    private constructor(
        httpClient: x402HTTPClient,
        walletAddress: string,
        publicClient: ReturnType<typeof createPublicClient>
    ) {
        this.httpClient = httpClient;
        this.walletAddress = walletAddress;
        this.publicClient = publicClient;
    }

    static async create(): Promise<BasicAgent> {
        const privateKey = process.env.PRIVATE_KEY;
        if (!privateKey) {
            throw new Error("PRIVATE_KEY environment variable is required");
        }

        // Create wallet account from private key
        const account = privateKeyToAccount(privateKey as `0x${string}`);

        // Create EVM scheme for signing payments
        const evmScheme = new ExactEvmScheme(account);

        // Register scheme for all EVM networks
        const coreClient = new x402Client().register("eip155:*", evmScheme);
        const httpClient = new x402HTTPClient(coreClient);

        // Create public client for balance checks
        const publicClient = createPublicClient({
            chain: skaleChain,
            transport: http(),
        });

        console.log(`Agent initialized with wallet: ${account.address}`);

        return new BasicAgent(httpClient, account.address, publicClient);
    }

    async accessResource(url: string): Promise<AccessResult> {
        console.log(`Accessing resource: ${url}`);

        try {
            const response = await fetch(url, {
                method: "GET",
                headers: { "Content-Type": "application/json" },
            });

            if (response.status === 402) {
                return this.handlePaymentRequired(response, url);
            }

            if (!response.ok) {
                return { success: false, error: `Request failed: ${response.status}` };
            }

            const data = await response.json();
            return { success: true, data };
        } catch (error) {
            const message = error instanceof Error ? error.message : "Unknown error";
            return { success: false, error: message };
        }
    }

    private async handlePaymentRequired(
        response: Response,
        url: string
    ): Promise<AccessResult> {
        console.log("Payment required (402), processing payment...");

        try {
            const responseBody = await response.json();

            // Get payment requirements from response headers and body
            const paymentRequired = this.httpClient.getPaymentRequiredResponse(
                (name: string) => response.headers.get(name),
                responseBody
            );

            console.log(`Payment options: ${paymentRequired.accepts.length}`);

            // Create signed payment payload
            const paymentPayload = await this.httpClient.createPaymentPayload(paymentRequired);

            // Encode payment headers for the retry request
            const paymentHeaders = this.httpClient.encodePaymentSignatureHeader(paymentPayload);

            // Retry request with payment
            const paidResponse = await fetch(url, {
                method: "GET",
                headers: {
                    "Content-Type": "application/json",
                    ...paymentHeaders,
                },
            });

            if (!paidResponse.ok) {
                return { success: false, error: `Payment failed: ${paidResponse.status}` };
            }

            // Check settlement response
            const settlement = this.httpClient.getPaymentSettleResponse(
                (name: string) => paidResponse.headers.get(name)
            );

            if (settlement?.transaction) {
                console.log(`Payment settled, tx: ${settlement.transaction}`);
            }

            const data = await paidResponse.json();
            console.log("Resource accessed successfully after payment!");
            return { success: true, data };
        } catch (error) {
            const message = error instanceof Error ? error.message : "Unknown error";
            console.error("Payment processing failed:", message);
            return { success: false, error: message };
        }
    }

}

export default BasicAgent;

Step 5: Use the Agent

Create a file index.ts:
import BasicAgent from "./agent";

async function main() {
    // Create the agent (async factory pattern)
    const agent = await BasicAgent.create();


    // Access a paywalled resource
    const result = await agent.accessResource("http://localhost:3000/premium/data");

    if (result.success) {
        console.log("Received data:", result.data);
    } else {
        console.error("Error:", result.error);
    }
}

main().catch(console.error);

Step 6: Run the Agent

npx tsx index.ts

Best Practices

  1. Set Spending Limits: Implement maximum payment amounts to prevent unexpected costs
  2. Use Environment Variables: Never hardcode private keys or API keys
  3. Log Transactions: Keep records of all payments for debugging and accounting
  4. Handle Errors Gracefully: Always wrap payment logic in try-catch blocks
  5. Test on Testnet First: Use SKALE testnet before deploying to production

Security Considerations

  • Never expose private keys in client-side code or logs
  • Use secure key management (environment variables, secrets manager)
  • Implement rate limiting to prevent abuse
  • Consider using a dedicated wallet with limited funds for agents
  • Audit your agent’s behavior regularly

Next Steps

Resources