npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@circuitorg/agent-sdk

v1.3.9

Published

typescript sdk for the Agent Toolset Service

Readme

Circuit Agent SDK - TypeScript

NOTE: Please refer to our all in one Circuit Documentation: We will be reducing this down to instructions for sdk developers and will refer agent developers to the public gitbooks.

Clean, unified, type-safe TypeScript SDK for building cross-chain agents on Circuit

A simplified TypeScript SDK for building automated agents to deploy on Circuit. Agents receive a single AgentContext object containing everything they need - request data, SDK methods, and unified logging. No boilerplate, no return values to manage, just write your logic.

💡 Best used with Circuit Agents CLI - Deploy, manage, and test your agents with ease

📑 Table of Contents

🚀 Quick Start

Install the SDK

npm install @circuitorg/agent-sdk
# or
yarn add @circuitorg/agent-sdk
# or
bun add @circuitorg/agent-sdk

Create Your First Agent

Every agent receives a single AgentContext object that provides:

Session Data:

  • agent.sessionId - Unique session identifier
  • agent.sessionWalletAddress - The wallet address for this session
  • agent.currentPositions - Assets allocated to the agent at the start of this execution

Available Methods:

  • agent.log() - Send messages to users and log locally
  • agent.memory - Persist data across executions (.set(), .get(), .list(), .delete())
  • agent.platforms.polymarket - Trade prediction markets (.marketOrder(), .redeemPositions())
  • agent.swidge - Cross-chain swaps and bridges (.quote(), .execute())
  • agent.signAndSend() - Execute custom built transactions on any supported chain
  • agent.signMessage() - Sign messages (EVM only)
  • agent.transactions() - Get transaction history with asset changes

Important: currentPositions reflects your allocated assets at the start of each execution. To avoid failed transactions, you should be tracking intra-run position changes based on transactions the agent makes during the execution. Circuit will provide updated positions on the next execution request.

Required Asset setup

Note: For native tokens, use the following null addresses for solana/ethereum

// Requiring 1 SOL
[[requiredAssets]]
network = "solana"
address = "11111111111111111111111111111111"
minimumAmount = "1000000000"

// Requiring 1 ETH
[[requiredAssets]]
network = "ethereum:<chainId>"
address = "0x0000000000000000000000000000000000000000"
minimumAmount = "1000000000000000000"
import { Agent, AgentContext } from "@circuitorg/agent-sdk";

async function run(agent: AgentContext): Promise<void> {
  /**
   * Main agent logic - receives AgentContext with everything needed.
   * No return value - errors are caught automatically.
   */
  // Access session data
  await agent.log(`Starting execution for session ${agent.sessionId}`);
  await agent.log(`Wallet: ${agent.sessionWalletAddress}`);
  await agent.log(`Managing ${agent.currentPositions.length} allocated positions`);

  // Use SDK methods
  await agent.memory.set("last_run", Date.now().toString());

  // Your agent logic here - track position changes within this execution
  // Circuit will provide updated positions on the next run
}

async function unwind(agent: AgentContext, positions: CurrentPosition[]): Promise<void> {
  /**Allocation adjustment / cleanup when agent is asked to unwind positions.*/
  await agent.log(`Unwinding ${positions.length} positions`);
}

// Boilerplate - This should never change unless you want to change the names of your run and unwind functions
const agent = new Agent({
  runFunction: run,
  unwindFunction: unwind
});

export default agent.getExport();

🎯 Core Concepts

The AgentContext Object

Every agent function receives a single AgentContext object that contains:

Request Data:

  • agent.sessionId - Unique session identifier
  • agent.sessionWalletAddress - Wallet address for this session
  • agent.currentPositions - Current positions allocated to this agent

SDK Methods:

  • agent.log() - Unified logging (console + backend)
  • agent.memory - Session-scoped key-value storage
  • agent.platforms.polymarket - Prediction market operations
  • agent.swidge - Cross-chain swap operations
  • agent.signAndSend() - Sign and broadcast transactions
  • agent.signMessage() - Sign messages on EVM
  • agent.transactions() - Get transaction history with asset changes

Run/Unwind Function Requirements

  1. Signature: async function myFunction(agent: AgentContext): Promise<void>
  2. Return: Always return void (or no return statement)
  3. Errors: You should surface any relevant errors via agent.log('error message', { error: true }). All errors from built-in SDK functions will be caught gracefully and provided in the return data for you to handle as necessary.

📝 Unified Logging with agent.log()

Use agent.log() to communicate with your users and debug your agent. Every message appears in your terminal, and by default also shows up in the Circuit UI for your users to see. Simply pass debug: true to skip sending the message to the user.

async function run(agent: AgentContext): Promise<void> {
  // Standard log: Shows to user in Circuit UI
  await agent.log("Processing transaction");

  // Error log: Shows to user in Circuit UI (as an error)
  const result = await agent.memory.get("key");
  if (!result.success) {
    await agent.log(result.error_message || result.error, { error: true });
  }

  // Debug log: Only you see this in your terminal
  await agent.log("Internal state: processing...", { debug: true });

  // Logging objects
  // Objects are pretty-printed to console and serialized/truncated for backend
  await agent.log({ wallet: agent.sessionWalletAddress, status: "active" });

  // Check if message reached the user
  const logResult = await agent.log("Important message");
  if (!logResult.success) {
    // Rare - usually means Circuit UI is unreachable
  }
}

What Your Users See:

| Code | You See (Terminal) | User Sees (Circuit UI) | |------|-------------------|----------------------| | agent.log("msg") | ✅ In your terminal | ✅ In Circuit UI | | agent.log("msg", { error: true }) | ✅ As error in terminal | ✅ As error in Circuit UI | | agent.log("msg", { debug: true }) | ✅ In terminal | ❌ Hidden from user | | agent.log("msg", { error: true, debug: true }) | ✅ As error in terminal | ❌ Hidden from user |

💾 Session Memory Storage

Store and retrieve data for your agent's session with simple operations. Memory is automatically scoped to your session ID, and for now is simple string storage. You will need to handle serialization of whatever data you want to store here.

async function run(agent: AgentContext): Promise<void> {
  // Set a value
  const result = await agent.memory.set("lastSwapNetwork", "ethereum:42161");
  if (!result.success) {
    await agent.log(result.error_message || result.error, { error: true });
  }

  // Get a value
  const getResult = await agent.memory.get("lastSwapNetwork");
  if (getResult.success && getResult.data) {
    const network = getResult.data.value;
    await agent.log(`Using network: ${network}`);
  } else {
    await agent.log("No saved network found");
  }

  // List all keys
  const listResult = await agent.memory.list();
  if (listResult.success && listResult.data) {
    await agent.log(`Found ${listResult.data.count} keys: ${listResult.data.keys}`);
  }

  // Delete a key
  await agent.memory.delete("tempData");
}

All memory operations return responses with .success and .error_message:

const result = await agent.memory.set("key", "value");
if (!result.success) {
  await agent.log(`Failed to save: ${result.error_message}`, { error: true });
}

🌉 Cross-Chain Swaps with Swidge

Built-in Swidge integration for seamless cross-chain token swaps and bridges.

Get and execute a quote

Note: It is important to always validate quotes before executing. Circuit will always do its best to return a quote, and as of now, will only filter out quotes with price impacts exceeding 100% to ensure maximum flexibility. It is on the agent to makes sure a quote is valid, given its own parameters

Bulk Execution: execute() accepts both single quotes and arrays. Pass an array to execute multiple swaps in parallel: await agent.swidge.execute([quote1.data, quote2.data]) returns an array of results.

async function run(agent: AgentContext): Promise<void> {
  // 1. Get quote
  const quote = await agent.swidge.quote({
    from: { network: "ethereum:8453", address: agent.sessionWalletAddress },
    to: { network: "ethereum:137", address: agent.sessionWalletAddress },
    amount: "1000000000000000",  // 0.001 ETH
    toToken: "0x2791bca1f2de4661ed88a30c99a7a9449aa84174",
    slippage: "2.0",
  });

  if (!quote.success) {
    await agent.log(`Quote failed: ${quote.error_message}`, { error: true });
    return;
  }

  if (quote.data && Math.abs(parseFloat(quote.data.priceImpact.percentage)) > 10) {
    await agent.log(`Warning: Price impact is too high: ${quote.data.priceImpact.percentage}%`, { error: true, debug: true });
  } else if (quote.data) {
    await agent.log(`You'll receive: ${quote.data.assetReceive.amountFormatted}`);
    await agent.log(`Fees: ${quote.data.fees.map(f => f.name).join(', ')}`);

    // 2. Execute the swap
    const result = await agent.swidge.execute(quote.data);

    if (result.success && result.data) {
      await agent.log(`Swap status: ${result.data.status}`);
      if (result.data.status === "success") {
        await agent.log("✅ Swap completed!");
        await agent.log(`In tx: ${result.data.in.txs}`);
        await agent.log(`Out tx: ${result.data.out.txs}`);
      } else if (result.data.status === "failure") {
        await agent.log("❌ Swap failed", { error: true });
      }
    } else {
      await agent.log(result.error_message || result.error, { error: true });
    }
  }
}

📈 Polymarket Prediction Markets

Trade prediction markets on Polygon.

Place Market Orders

Note: Right now, polymarket's API accepts different decimal precision for buys and sells, this will result in dust positions if you are selling out of a position before expiry. Once expired, dust can be cleaned up during the redeem step.

async function run(agent: AgentContext): Promise<void> {
  // Buy order - size is USD amount to spend
  const buyOrder = await agent.platforms.polymarket.marketOrder({
    tokenId: "86192057611122246511563653509192966169513312957180910360241289053249649036697",
    size: 3,  // Spend $3
    side: "BUY"
  });

  if (buyOrder.success && buyOrder.data) {
    await agent.log(`Order ID: ${buyOrder.data.orderInfo.orderId}`);
    await agent.log(`Price: $${buyOrder.data.orderInfo.priceUsd}`);
    await agent.log(`Total: $${buyOrder.data.orderInfo.totalPriceUsd}`);
  } else {
    await agent.log(buyOrder.error_message || buyOrder.error, { error: true });
  }

  // Sell order - size is number of shares to sell
  const sellOrder = await agent.platforms.polymarket.marketOrder({
    tokenId: "86192057611122246511563653509192966169513312957180910360241289053249649036697",
    size: 5.5,  // Sell 5.5 shares
    side: "SELL"
  });
}

Redeem Positions

async function unwind(agent: AgentContext): Promise<void> {
  /**Redeem all settled positions.*/
  const redemption = await agent.platforms.polymarket.redeemPositions();

  if (redemption.success && redemption.data) {
    const successful = redemption.data.filter(r => r.success);
    await agent.log(`Redeemed ${successful.length} positions`);
  } else {
    await agent.log(redemption.error_message || redemption.error, { error: true });
  }
}

Hyperliquid Trading

Trade perpetual futures (with leverage) and spot markets on Hyperliquid DEX.

Market Types:

  • Perp: Perpetual futures trading with leverage (use market: "perp" in order parameters)
  • Spot: Spot trading (use market: "spot" in order parameters)

Asset Naming for Spot:

  • Non-Hypercore-native assets use "Unit" prefix: UBTC (Unit BTC), UETH (Unit ETH)
  • Example: To trade BTC spot, use symbol UBTC-USDC

Account Information

async function run(agent: AgentContext): Promise<void> {
  // Check balances
  const balances = await agent.platforms.hyperliquid.balances();
  if (balances.success && balances.data) {
    await agent.log(`Account value: $${balances.data.perp.accountValue}`);
    await agent.log(`Withdrawable: $${balances.data.perp.withdrawable}`);
  }

  // View open positions
  const positions = await agent.platforms.hyperliquid.positions();
  if (positions.success && positions.data) {
    for (const pos of positions.data) {
      await agent.log(`${pos.symbol}: ${pos.side} ${pos.size} @ ${pos.entryPrice}`);
      await agent.log(`Unrealized PnL: $${pos.unrealizedPnl}`);
    }
  }
}

Place Orders

async function run(agent: AgentContext): Promise<void> {
  // Perp market order
  const perpOrder = await agent.platforms.hyperliquid.placeOrder({
    symbol: "BTC-USD",
    side: "buy",
    size: 0.0001,
    price: 110000,  // Acts as slippage limit for market orders
    market: "perp",
    type: "market"
  });

  if (perpOrder.success && perpOrder.data) {
    await agent.log(`Perp Order ${perpOrder.data.orderId}: ${perpOrder.data.status}`);
  } else {
    await agent.log(perpOrder.error, { error: true });
  }

  // Spot market order (for non-Hypercore-native assets like BTC)
  const spotOrder = await agent.platforms.hyperliquid.placeOrder({
    symbol: "UBTC-USDC",  // Unit BTC
    side: "buy",
    size: 0.0001,
    price: 110000,
    market: "spot",  // Changed to spot
    type: "market"
  });

  if (spotOrder.success && spotOrder.data) {
    await agent.log(`Spot Order ${spotOrder.data.orderId}: ${spotOrder.data.status}`);
  } else {
    await agent.log(spotOrder.error, { error: true });
  }
}

Order Management

async function run(agent: AgentContext): Promise<void> {
  // Get open orders
  const openOrders = await agent.platforms.hyperliquid.openOrders();

  // Get specific order details
  const order = await agent.platforms.hyperliquid.order("12345");

  // Cancel an order
  const result = await agent.platforms.hyperliquid.deleteOrder("12345", "BTC-USD");

  // View historical orders
  const history = await agent.platforms.hyperliquid.orders();

  // Check order fills
  const fills = await agent.platforms.hyperliquid.orderFills();
}

Transfer Between Accounts

async function run(agent: AgentContext): Promise<void> {
  // Transfer USDC from spot to perp account
  const transfer = await agent.platforms.hyperliquid.transfer({
    amount: 1000.0,
    toPerp: true
  });

  if (transfer.success) {
    await agent.log("Transfer completed");
  }
}

📊 Transaction History

Get a list of asset changes for all confirmed transactions during your session. This is useful for tracking what assets have moved in and out of the agent's wallet.

Note: The system needs to index new transactions, so there may be a slight delay between when you execute a transaction and when the resulting asset changes are returned in this method. Make sure you are taking that into consideration if dealing with assets the agent just transacted with.

async function run(agent: AgentContext): Promise<void> {
  // Get all transaction history for this session
  const result = await agent.transactions();

  if (result.success && result.data) {
    await agent.log(`Found ${result.data.length} asset changes`);

    // Filter for outgoing transfers
    const outgoing = result.data.filter(
      (change) => change.from === agent.sessionWalletAddress
    );
    await agent.log(`Outgoing transfers: ${outgoing.length}`);

    // Calculate total USD value (where price data is available)
    const totalUsd = result.data
      .filter((c) => c.tokenUsdPrice)
      .reduce((sum, c) => {
        const amount = parseFloat(c.amount);
        const price = parseFloat(c.tokenUsdPrice!);
        return sum + amount * price;
      }, 0);
    await agent.log(`Total USD value: $${totalUsd.toFixed(2)}`);

    // View specific transaction details
    for (const change of result.data) {
      await agent.log(`${change.network}: ${change.from} → ${change.to}`);
      await agent.log(`  Amount: ${change.amount} ${change.tokenType}`);
      if (change.token) {
        await agent.log(`  Token: ${change.token}`);
      }
      await agent.log(`  Tx: ${change.transactionHash}`);
      await agent.log(`  Time: ${change.timestamp}`);
    }
  } else {
    await agent.log(result.error || "Failed to fetch transactions", { error: true });
  }
}

AssetChange Structure:

Each asset change in the response contains:

  • network - Network identifier (e.g., "ethereum:1", "solana")
  • transactionHash - Transaction hash
  • from - Sender address
  • to - Recipient address
  • amount - Amount transferred (as string to preserve precision)
  • token - Token contract address (null for native tokens)
  • tokenId - Token ID for NFTs (null for fungible tokens)
  • tokenType - Token type (e.g., "native", "ERC20", "ERC721")
  • tokenUsdPrice - Token price in USD at time of transaction (null if unavailable)
  • timestamp - Transaction timestamp

🚀 Sign & Send Transactions

Ethereum (any EVM chain)

async function run(agent: AgentContext): Promise<void> {
  // Self-send demo - send a small amount to yourself
  const response = await agent.signAndSend({
    network: "ethereum:1",
    request: {
      toAddress: agent.sessionWalletAddress,  // Send to self
      data: "0x",
      value: "100000000000000"  // 0.0001 ETH
    },
    message: "Self-send demo"
  });

  if (response.success) {
    await agent.log(`Transaction sent: ${response.txHash}`);
    if (response.transactionUrl) {
      await agent.log(`View: ${response.transactionUrl}`);
    }
  } else {
    await agent.log(response.error_message || response.error, { error: true });
  }
}

Solana

async function run(agent: AgentContext): Promise<void> {
  const response = await agent.signAndSend({
    network: "solana",
    request: {
      hexTransaction: "010001030a0b..."  // serialized VersionedTransaction
    }
  });

  if (response.success) {
    await agent.log(`Transaction: ${response.txHash}`);
  } else {
    await agent.log(response.error_message || response.error, { error: true });
  }
}

⚡ Error Handling

All SDK methods return response objects with .success and .error_message:

Uncaught Exceptions:

If your function throws an uncaught exception, the Agent SDK automatically:

  1. Logs the error to console (visible in local dev and cloud logs)
  2. Updates the job in the circuit backend with status='failed' and logs the error.
async function run(agent: AgentContext): Promise<void> {
  // This typo will be caught and logged automatically
  await agent.memmory.set("key", "value");  // TypeError

  // No need for try/catch around everything!
}

🛠️ Deployment

What You Write

Your agent code should look like this - just define your functions and add the boilerplate at the bottom:

import { Agent, AgentContext } from "@circuitorg/agent-sdk";

async function run(agent: AgentContext): Promise<void> {
  await agent.log("Hello from my agent!");
  // Your agent logic here
}

async function unwind(agent: AgentContext, positions: CurrentPosition[]): Promise<void> {
  await agent.log(`Cleaning up (unwind requested for ${positions.length} positions)...`);
}

// ============================================================================
// BOILERPLATE - Don't modify, this handles local testing AND Circuit deployment
// ============================================================================
const agent = new Agent({
  runFunction: run,
  unwindFunction: unwind
});

export default agent.getExport();

That's it! The boilerplate code automatically handles the rest

Deploy to Circuit

circuit publish

See the circuit cli docs for more details

Test Locally

🧪 Manual Instantiation (Testing)

For testing in scripts or Node.js REPL, you can manually create an AgentContext:

import { AgentContext } from "@circuitorg/agent-sdk";

// Create agent context with test data
// Tip: Get a real session ID and wallet from running 'circuit run -m local -x execute'
const SESSION_ID = 123; // <your-session-id>
const WALLET_ADDRESS = "your-wallet-address";

// Create sample position data - helpful for testing your agents treatment of different scenarios
// for example if you want to test some re-balancing logic, you can build a sample set of positions here
// In production, or when using the cli, these will be live values for the session
const SAMPLE_POSITION = {
  network: "ethereum:137",
  assetAddress: "0x4d97dcd97ec945f40cf65f87097ace5ea0476045",
  tokenId: "86192057611122246511563653509192966169513312957180910360241289053249649036697",
  avgUnitCost: "0.779812797920499100",
  currentQty: "41282044"
};

const agent = new AgentContext({
  sessionId: SESSION_ID,
  sessionWalletAddress: WALLET_ADDRESS,
  currentPositions: [SAMPLE_POSITION]
});

// Use it just like in production!
await agent.log("Testing agent functionality!");
await agent.memory.set("test_key", "test_value");

const result = await agent.memory.get("test_key");
if (result.success && result.data) {
  console.log(`Value: ${result.data.value}`);
}

Key Points:

  • Use real session IDs from the CLI for proper testing
  • All SDK methods work the same way as in production

📚 Working Examples

The SDK includes complete working examples to help you get started:

demo-agent.ts

An agent demonstrating all main features of the sdk:

  • Memory operations (set, get, list, delete)
  • Polymarket integration (market orders, position redemption)
  • Swidge
  • Self sending using sign and send
  • Unified logging patterns

This is a great starting point for building your own agent.

examples/kitchen-sink.ts

A comprehensive script showing all SDK features:

  • Manual AgentContext instantiation for testing
  • All memory operations with examples
  • Cross-chain swaps with Swidge (quotes, execution, price impact checks)
  • Polymarket operations (buy/sell orders, redemptions)
  • Custom transactions with signAndSend
  • Logging patterns (standard, error, debug)

Run this script to experiment with the SDK before deploying your agent.

🎯 Key Takeaways

  1. Single Interface: Everything you need is in the AgentContext object
  2. No Return Values: Functions return void - uncaught errors are handled automatically by circuit
  3. Unified Logging: agent.log() with simple debug and error flags
  4. Check .success: All SDK methods return response objects with .success and .error_message

📖 Additional Resources