Skip to main content

Overview

Tools are functions that AI agents can call to interact with external systems. The ADK-TS createTool function enables you to create type-safe tools with schema validation.

Tool Structure

Every tool has three main components:
  1. name: Unique identifier for the tool
  2. description: Explanation of what the tool does (used by the LLM)
  3. fn: The actual function implementation
  4. schema (optional): Zod schema for input validation

Basic Tool Creation

import { createTool } from "@iqai/adk";
import { z } from "zod";

export const simple_tool = createTool({
  name: "simple_tool",
  description: "A simple tool that greets the user.",
  fn: async () => {
    return "Hello from the tool!";
  }
});

Tool with Parameters

import { createTool } from "@iqai/adk";
import { z } from "zod";

export const greet_user = createTool({
  name: "greet_user",
  description: "Greets a user by name.",
  schema: z.object({
    name: z.string()
  }),
  fn: async ({ name }) => {
    return `Hello, ${name}!`;
  }
});

Blockchain Read Tools

Tools that read data from smart contracts without modifying state.

Reading Vault State

src/agents/sub-agents/strategy-sentinel-agent/tools.ts
import { createTool } from "@iqai/adk";
import { VaultABI } from "../../shared/abi";
import { chain_read, toStringBN } from "../../shared/utils/chain";
import { format18 } from "../../shared/utils/bigint";
import { env } from "../../../env";

export const get_vault_state = createTool({
  name: "get_vault_state",
  description: "Reads the vault's global state.",
  fn: async () => {
    const [
      totalAssets,
      totalSupply,
      totalManaged,
    ] = await Promise.all([
      chain_read(env.VAULT_ADDRESS, VaultABI.abi, "totalAssets", []),
      chain_read(env.VAULT_ADDRESS, VaultABI.abi, "totalSupply", []),
      chain_read(env.VAULT_ADDRESS, VaultABI.abi, "totalManagedAssets", []),
    ]);

    const raw = {
      totalAssets: totalAssets.toString(),
      totalSupply: totalSupply.toString(),
      totalManaged: totalManaged.toString(),
    };

    const human = {
      totalAssets: format18(raw.totalAssets),
      totalSupply: format18(totalSupply),
      totalManaged: format18(raw.totalManaged),
    };

    return toStringBN({ raw, human });
  }
});

Reading Strategy States

export const get_strategy_states = createTool({
  name: "get_strategy_states",
  description: "Fetches strategy addresses, their balances, and target BPS allocations from the Strategy Router.",
  fn: async () => {
    // Call the router function
    const [strategies, balances, targets] = await chain_read(
      env.ROUTER_ADDRESS,
      StrategyRouterABI.abi,
      "getPortfolioState",
      []
    );

    // Transform into structured objects
    const result = strategies.map((strat: string, i: number) => {
      const rawBal = balances[i].toString();
      const rawTarget = targets[i].toString();

      return {
        strategy: strat,
        raw: {
          balance: rawBal,
          targetBps: rawTarget,
        },
        human: {
          balance: format18(rawBal),
          targetPercent: (Number(rawTarget) / 100).toFixed(2) + "%",
        },
      };
    });

    return JSON.stringify(result, null, 2);
  }
});

Reading User Balances

export const get_user_balances = createTool({
  name: "get_user_balances",
  description: "Fetches vault share balance and withdrawable amount for a user.",
  schema: z.object({
    user: z.string()
  }),
  fn: async ({ user }) => {
    const [
      balance,
      assets
    ] = await Promise.all([
      chain_read(env.VAULT_ADDRESS, VaultABI.abi, "balanceOf", [user]),
      chain_read(env.VAULT_ADDRESS, VaultABI.abi, "convertToAssets", [
        await chain_read(env.VAULT_ADDRESS, VaultABI.abi, "balanceOf", [user])
      ])
    ]);

    const raw = {
      shares: balance.toString(),
      withdrawable: assets.toString(),
    };

    const human = {
      shares: format18(raw.shares),
      withdrawable: format18(raw.withdrawable),
    };

    return toStringBN({ raw, human });
  }
});

Blockchain Write Tools

Tools that execute transactions on the blockchain.

Vault Deposit

import { parseUnits } from "../../shared/utils/bigint";
import { chain_write } from "../../shared/utils/chain";

export const vault_deposit = createTool({
  name: "vault_deposit",
  description: "Deposit LINK into vault.",
  schema: z.object({
    amount: z.string()
  }),
  fn: async ({ amount }) => {
    const tx = await chain_write(
      env.VAULT_ADDRESS,
      VaultABI.abi,
      "deposit",
      [parseUnits(amount)]
    );
    return tx.hash;
  }
});

Vault Withdraw

export const vault_withdraw = createTool({
  name: "vault_withdraw",
  description: "Withdraw shares from vault.",
  schema: z.object({
    shares: z.string()
  }),
  fn: async ({ shares }) => {
    const tx = await chain_write(
      env.VAULT_ADDRESS,
      VaultABI.abi,
      "withdraw",
      [shares]
    );
    return tx.hash;
  }
});

Rebalance Vault

export const rebalance_vault = createTool({
  name: "rebalance_vault",
  description: "Triggers vault rebalance().",
  fn: async () => {
    const tx = await chain_write(
      env.ROUTER_ADDRESS,
      StrategyRouterABI.abi,
      "rebalance",
      []
    );
    return tx.hash;
  }
});

Harvest Strategy

export const harvest_strategy = createTool({
  name: "harvest_strategy",
  description: "Calls harvestAll().",
  fn: async () => {
    const tx = await chain_write(
      env.ROUTER_ADDRESS,
      StrategyRouterABI.abi,
      "harvestAll",
      []
    );
    return tx.hash;
  }
});

API Integration Tools

Fetching Token Prices

export const get_token_prices = createTool({
  name: "get_token_prices",
  description: "Fetches real-time LINK and WETH prices from CoinGecko API. Returns LINK and WETH prices in USD, and calculates LINK price per WETH ratio.",
  fn: async () => {
    try {
      // Fetch real prices from CoinGecko API
      const response = await fetch(
        "https://api.coingecko.com/api/v3/simple/price?ids=chainlink,weth&vs_currencies=usd&include_24hr_change=true"
      );

      if (!response.ok) {
        throw new Error(`CoinGecko API error: ${response.statusText}`);
      }

      const data = await response.json() as {
        chainlink?: { usd: number; usd_24h_change?: number };
        weth?: { usd: number; usd_24h_change?: number };
      };

      // Extract prices in USD
      const linkPriceUSD = data.chainlink?.usd || 0;
      const wethPriceUSD = data.weth?.usd || 0;
      const link24hChange = data.chainlink?.usd_24h_change || 0;
      const weth24hChange = data.weth?.usd_24h_change || 0;

      if (linkPriceUSD === 0 || wethPriceUSD === 0) {
        throw new Error("Failed to fetch valid prices from CoinGecko");
      }

      // Calculate LINK price per WETH
      const linkPricePerWETH = wethPriceUSD / linkPriceUSD;

      // Convert to 18 decimal format (scaled by 1e18)
      const linkPricePerWETH_scaled = BigInt(Math.floor(linkPricePerWETH * 1e18));
      const wethPrice_scaled = BigInt(Math.floor(wethPriceUSD * 1e18));
      const linkPriceUSD_scaled = BigInt(Math.floor(linkPriceUSD * 1e18));

      const raw = {
        linkPriceUSD: linkPriceUSD_scaled.toString(),
        wethPriceUSD: wethPrice_scaled.toString(),
        linkPricePerWETH: linkPricePerWETH_scaled.toString(),
      };

      const human = {
        linkPriceUSD: linkPriceUSD.toFixed(2),
        wethPriceUSD: wethPriceUSD.toFixed(2),
        linkPricePerWETH: linkPricePerWETH.toFixed(6),
        link24hChange: link24hChange.toFixed(2) + "%",
        weth24hChange: weth24hChange.toFixed(2) + "%",
        interpretation: `1 WETH = ${linkPricePerWETH.toFixed(2)} LINK`,
      };

      return { raw, human, source: "CoinGecko API (real market prices)" };
    } catch (error: any) {
      throw new Error(`Failed to fetch real prices: ${error.message}`);
    }
  }
});

Risk Management Tools

Check Liquidation Risk

export const check_liquidation_risk = createTool({
  name: "check_liquidation_risk",
  description: "Checks leverage strategy liquidation risk.",
  fn: async () => {
    const [
      deposited,
      borrowed
    ] = await Promise.all([
      chain_read(env.STRATEGY_LEVERAGE_ADDRESS, StrategyLeverageABI.abi, "deposited", []),
      chain_read(env.STRATEGY_LEVERAGE_ADDRESS, StrategyLeverageABI.abi, "borrowedWETH", []),
    ]);

    const dep = Number(format18(deposited.toString()));
    const bor = Number(format18(borrowed.toString()));

    const ltv = bor / dep;

    return {
      ltv,
      safe: ltv < 0.70,
      warning: ltv >= 0.70 && ltv < 0.80,
      critical: ltv >= 0.80,
    };
  }
});

Auto Deleverage

export const auto_deleverage = createTool({
  name: "auto_deleverage",
  description: "Repay debt to reduce liquidation risk.",
  fn: async () => {
    const tx = await chain_write(
      env.ROUTER_ADDRESS,
      StrategyRouterABI.abi,
      "triggerDeleverage",
      [env.STRATEGY_LEVERAGE_ADDRESS, 10]
    );
    return tx.hash;
  }
});

Stateful Tools with ToolContext

Tools can maintain state across invocations using ToolContext.
import { createTool, ToolContext } from "@iqai/adk";

export const get_vault_apy = createTool({
  name: "get_vault_apy",
  description: "Calculates the vault APY based on TVL growth.",
  fn: async (_params: {}, toolContext: ToolContext) => {
    // Load previous state from context
    const stateKey = "session.vault_apy_state";
    const state = toolContext.state[stateKey] as {
      lastTVL?: number;
      lastTimestamp?: number;
    } | undefined;

    const lastTVL = state?.lastTVL ? Number(state.lastTVL) : 0;
    const lastTs = state?.lastTimestamp ? Number(state.lastTimestamp) : 0;
    const now = Math.floor(Date.now() / 1000);

    // Read current TVL
    const tvl = Number(
      await chain_read(env.VAULT_ADDRESS, VaultABI.abi, "totalManagedAssets", [])
    );

    // First run - store baseline state
    if (lastTVL === 0 || lastTs === 0) {
      toolContext.state[stateKey] = {
        lastTVL: tvl,
        lastTimestamp: now
      };

      return {
        apy: 0,
        readable: "0%",
        message: "APY baseline set (first run).",
        tvl
      };
    }

    let dt: number = now - lastTs;
    if (dt <= 0) dt = 1;

    const growth = (tvl - lastTVL) / lastTVL;
    const YEAR = 365 * 24 * 3600;

    const apy = (growth / dt) * YEAR;
    const readable = `${(apy * 100).toFixed(2)}%`;

    // Update state in context with new values
    toolContext.state[stateKey] = {
      lastTVL: tvl,
      lastTimestamp: now
    };

    return {
      apy,
      readable,
      tvl,
      growth,
      dt
    };
  }
});

Complex Parameter Tools

Update Strategy Weights

export const update_strategy_target_weights = createTool({
  name: "update_strategy_target_weights",
  description: "Updates target allocation weights for strategies in basis points (10000 = 100%). Must sum to 10000. Example: [8000, 2000] means 80% to first strategy, 20% to second.",
  schema: z.object({
    leverageStrategyBps: z.number().min(0).max(10000).describe("Target weight for leverage strategy in basis points (0-10000)"),
    aaveStrategyBps: z.number().min(0).max(10000).describe("Target weight for Aave strategy in basis points (0-10000)")
  }),
  fn: async ({ leverageStrategyBps, aaveStrategyBps }) => {
    if (leverageStrategyBps + aaveStrategyBps !== 10000) {
      throw new Error("Target weights must sum to 10000 (100%)");
    }

    const strategies = [env.STRATEGY_LEVERAGE_ADDRESS, env.STRATEGY_AAVE_ADDRESS];
    const bps = [leverageStrategyBps, aaveStrategyBps];

    const tx = await chain_write(
      env.ROUTER_ADDRESS,
      StrategyRouterABI.abi,
      "setStrategies",
      [strategies, bps]
    );
    return { tx: tx.hash, newWeights: { leverage: leverageStrategyBps, aave: aaveStrategyBps } };
  }
});

Update Leverage Parameters

export const update_leverage_params = createTool({
  name: "update_leverage_params",
  description: "Updates leverage strategy parameters: maxDepth (1-6, max loop iterations) and borrowFactor (0-8000, parts-per-10000, e.g., 6000 = 60% of collateral borrowed per loop).",
  schema: z.object({
    maxDepth: z.number().min(1).max(6).describe("Maximum leverage loop iterations (1-6)"),
    borrowFactor: z.number().min(0).max(8000).describe("Borrow factor in basis points (0-8000, e.g., 6000 = 60%)")
  }),
  fn: async ({ maxDepth, borrowFactor }) => {
    const tx = await chain_write(
      env.STRATEGY_LEVERAGE_ADDRESS,
      StrategyLeverageABI.abi,
      "setLeverageParams",
      [maxDepth, borrowFactor]
    );
    return {
      tx: tx.hash,
      newParams: {
        maxDepth,
        borrowFactor: borrowFactor / 100 + "%"
      }
    };
  }
});

Utility Helpers

Chain Read Helper

src/agents/shared/utils/chain.ts
import { ethers } from "ethers";
import { env } from "../../../env";

const provider = new ethers.JsonRpcProvider(env.RPC_URL, {
  chainId: 11155111,
  name: "sepolia",
  ensNetwork: undefined,
});

const signer = new ethers.Wallet(env.PRIVATE_KEY, provider);

export async function chain_read(contract: string, abi: any, method: string, args: any[] = []) {
  const c = new ethers.Contract(contract, abi, provider);
  return await c[method](...args);
}

export async function chain_write(contract: string, abi: any, method: string, args: any[] = []) {
  const c = new ethers.Contract(contract, abi, signer);

  // Estimate gas with 20% buffer
  let gasLimit;
  try {
    const estimate = await c[method].estimateGas(...args);
    gasLimit = estimate * 12n / 10n; // +20%
  } catch (err) {
    console.error("Gas estimation error:", err);
    throw err;
  }

  // Get current network fees
  const feeData = await provider.getFeeData();

  const tx = await c[method](...args, {
    gasLimit,
    maxFeePerGas: feeData.maxFeePerGas,
    maxPriorityFeePerGas: feeData.maxPriorityFeePerGas,
  });

  // Wait for confirmation
  const receipt = await tx.wait();

  return {
    hash: tx.hash,
    from: tx.from,
    to: tx.to,
    nonce: tx.nonce,
    status: receipt.status,
    gasUsed: receipt.gasUsed.toString(),
  };
}

export function toStringBN(value: any): any {
  if (typeof value === "bigint") return value.toString();
  if (Array.isArray(value)) return value.map(v => toStringBN(v));
  if (value && typeof value === "object") {
    const out: any = {};
    for (const k in value) out[k] = toStringBN(value[k]);
    return out;
  }
  return value;
}

export { provider };

BigInt Helpers

src/agents/shared/utils/bigint.ts
export function format18(value: string | bigint): string {
  const bn = BigInt(value);
  const divisor = BigInt(10 ** 18);
  const whole = bn / divisor;
  const remainder = bn % divisor;
  const fraction = remainder.toString().padStart(18, '0').slice(0, 6);
  return `${whole}.${fraction}`;
}

export function parseUnits(value: string, decimals: number = 18): bigint {
  const [whole, fraction = ''] = value.split('.');
  const paddedFraction = fraction.padEnd(decimals, '0').slice(0, decimals);
  return BigInt(`${whole}${paddedFraction}`);
}

Best Practices

1. Clear Descriptions

Provide detailed descriptions to help the LLM understand when to use each tool:
description: "Fetches real-time LINK and WETH prices from CoinGecko API (real market prices). Returns LINK and WETH prices in USD, and calculates LINK price per WETH ratio."

2. Type Safety

Use Zod schemas for type-safe parameter validation:
schema: z.object({
  amount: z.string().describe("Amount to deposit in LINK"),
  user: z.string().describe("Wallet address of the user")
})

3. Error Handling

Handle errors gracefully and provide informative messages:
try {
  const result = await chain_read(...);
  return result;
} catch (error: any) {
  throw new Error(`Failed to read from contract: ${error.message}`);
}

4. Return Structured Data

Return both raw and human-readable data:
return {
  raw: {
    totalAssets: totalAssets.toString(),
    totalSupply: totalSupply.toString(),
  },
  human: {
    totalAssets: format18(totalAssets.toString()),
    totalSupply: format18(totalSupply.toString()),
  }
};

5. Gas Estimation

Always estimate gas before sending transactions:
const estimate = await c[method].estimateGas(...args);
const gasLimit = estimate * 12n / 10n; // +20% buffer

Next Steps

Building Agents

Learn how to integrate tools into custom agents

Automation

Set up automated tool execution with cron jobs

ADK-TS Integration

Understand the ADK-TS integration patterns

Smart Contracts

Learn about the smart contracts your tools interact with

Build docs developers (and LLMs) love