Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.skale.space/llms.txt

Use this file to discover all available pages before exploring further.

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