@agentek/tools
Version:
Blockchain tools for AI agents
190 lines • 9.82 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.intentSwap = void 0;
const zod_1 = require("zod");
const client_js_1 = require("../client.js");
const viem_1 = require("viem");
const chains_1 = require("viem/chains");
const zrouter_sdk_1 = require("zrouter-sdk");
const constants_js_1 = require("./constants.js");
const types_js_1 = require("./types.js");
const utils_js_1 = require("../utils.js");
const utils_js_2 = require("./utils.js");
const api_js_1 = require("./api.js");
const swapParameters = zod_1.z.object({
chainId: zod_1.z.number().default(1).describe("Chain ID (1 for Mainnet, 8453 for Base). Default: 1"),
tokenIn: types_js_1.SymbolOrTokenSchema.describe('Input token — either a symbol string (e.g. "USDT", "ETH") or an object { address, id? } for ERC6909 tokens'),
tokenOut: types_js_1.SymbolOrTokenSchema.describe('Output token — either a symbol string (e.g. "IZO", "WETH") or an object { address, id? } for ERC6909 tokens'),
amount: types_js_1.AmountSchema.describe("Amount in human-readable units (e.g. 1.5 or '1.5'). Refers to tokenIn for EXACT_IN, tokenOut for EXACT_OUT."),
side: zod_1.z.enum(["EXACT_IN", "EXACT_OUT"]).describe("EXACT_IN: specify the input amount and get the best output. EXACT_OUT: specify the desired output and get the required input."),
slippageBps: zod_1.z.number().int().min(0).max(10_000).default(50).describe("Max slippage in basis points (e.g. 50 = 0.50%, 100 = 1%). Default: 50"),
deadlineSeconds: zod_1.z.number().int().positive().default(300).describe("Transaction deadline in seconds from now (e.g. 300 = 5 minutes). Default: 300"),
owner: utils_js_1.addressSchema.optional().describe("The address that owns the input tokens (0x...). Defaults to the connected wallet."),
finalTo: utils_js_1.addressSchema.optional().describe("Address to receive the output tokens (0x...). Defaults to the owner address."),
router: utils_js_1.addressSchema.optional().describe("Override the zRouter contract address (0x...). Defaults to the canonical zRouter for the chain."),
});
exports.intentSwap = (0, client_js_1.createTool)({
name: "swap",
description: "Swap ERC20 or ERC6909 tokens via the zRouter. Automatically handles token approvals, finds the best route (including Matcha/0x aggregation), and executes the swap.",
supportedChains: constants_js_1.supportedChains,
parameters: swapParameters,
execute: async (client, args) => {
const chainId = args.chainId;
if (chainId !== chains_1.mainnet.id && chainId !== chains_1.base.id) {
throw new Error(`Unsupported chain ID ${chainId}. Supported: 1 (Mainnet), 8453 (Base).`);
}
const walletClient = client.getWalletClient(chainId);
const publicClient = client.getPublicClient(chainId);
const owner = args.owner ??
walletClient?.account?.address ??
(() => {
throw new Error("Owner address is required (connect a wallet or pass 'owner').");
})();
const finalTo = args.finalTo ?? owner;
// Resolve tokens
const [tIn, tOut] = await Promise.all([
(0, utils_js_2.resolveInputToToken)(args.tokenIn, chainId),
(0, utils_js_2.resolveInputToToken)(args.tokenOut, chainId),
]);
// Parse human amount -> base units (by side)
const humanAmount = typeof args.amount === "number" ? String(args.amount) : args.amount;
const baseAmount = args.side === "EXACT_IN" ? (0, utils_js_2.toBaseUnits)(humanAmount, tIn) : (0, utils_js_2.toBaseUnits)(humanAmount, tOut);
// Deadline/slippage
const deadline = BigInt(Math.floor(Date.now() / 1000) + args.deadlineSeconds);
// --- Try API first for routes (includes Matcha/0x aggregated quotes) ---
let steps = null;
const apiRoutes = await (0, api_js_1.fetchApiRoutes)({
chainId,
tokenIn: (0, utils_js_2.asToken)(tIn),
tokenOut: (0, utils_js_2.asToken)(tOut),
side: args.side,
amount: baseAmount,
owner,
slippageBps: args.slippageBps,
});
if (apiRoutes && apiRoutes.length > 0) {
steps = apiRoutes[0].steps;
}
// --- Fallback to SDK findRoute if API didn't return routes ---
if (!steps) {
const sdkSteps = await (0, zrouter_sdk_1.findRoute)(publicClient, {
tokenIn: (0, utils_js_2.asToken)(tIn),
tokenOut: (0, utils_js_2.asToken)(tOut),
side: args.side,
amount: baseAmount,
deadline,
owner,
slippageBps: args.slippageBps,
});
if (!sdkSteps?.length)
throw new Error("No route found for the requested swap.");
steps = sdkSteps;
}
const router = args.router ??
steps[0]?.router ??
(0, zrouter_sdk_1.getConfig)(chainId).router;
// --- Check if the best route is a direct Matcha swap ---
// Matcha routes have a single MATCHA step with a raw 0x transaction
// that should be executed directly (not through zRouter multicall)
const isMatchaRoute = steps.length === 1 && steps[0].kind === "MATCHA";
if (isMatchaRoute) {
const matchaStep = steps[0];
const tx = matchaStep.transaction;
// Build approval ops for the Matcha allowance target
const approvalOps = [];
const allowanceTarget = matchaStep.metadata?.allowanceTarget;
if (allowanceTarget) {
const approvalData = (0, viem_1.encodeFunctionData)({
abi: zrouter_sdk_1.erc20Abi,
functionName: "approve",
args: [allowanceTarget, viem_1.maxUint256],
});
approvalOps.push({
target: matchaStep.tokenIn.address,
value: "0",
data: approvalData,
});
}
// The raw 0x swap transaction
const swapOp = {
target: tx.to,
value: tx.value.toString(),
data: tx.data,
};
const ops = [...approvalOps, swapOp];
const pretty = `${args.side === "EXACT_IN" ? "Swap" : "Receive"} ${humanAmount} ${typeof args.tokenIn === "string" ? args.tokenIn.toUpperCase() : tIn.symbol ?? "TOKEN"} → ${typeof args.tokenOut === "string" ? args.tokenOut.toUpperCase() : tOut.symbol ?? "TOKEN"} (via Matcha)`;
if (!walletClient) {
return { intent: pretty, ops, chain: chainId };
}
const hash = await client.executeOps(ops, chainId);
return { intent: pretty, ops, chain: chainId, hash };
}
// --- Standard zRouter path: check approvals, build plan, multicall ---
// Use checkRouteApprovals() instead of plan.approvals (empty in SDK >= 0.0.27)
const approvals = await (0, zrouter_sdk_1.checkRouteApprovals)(publicClient, {
owner,
router,
steps,
});
const plan = await (0, zrouter_sdk_1.buildRoutePlan)(publicClient, {
owner,
router,
steps,
finalTo,
});
// Build approval ops from checkRouteApprovals result
const approvalOps = approvals.map((appr) => {
if (appr.kind === "ERC20_APPROVAL") {
const data = (0, viem_1.encodeFunctionData)({
abi: zrouter_sdk_1.erc20Abi,
functionName: "approve",
args: [appr.spender, viem_1.maxUint256],
});
return {
target: appr.token.address,
value: "0",
data: data,
};
}
if (appr.kind === "ERC6909_SET_OPERATOR") {
const data = (0, viem_1.encodeFunctionData)({
abi: zrouter_sdk_1.erc6909Abi,
functionName: "setOperator",
args: [appr.operator, true],
});
return {
target: appr.token.address,
value: "0",
data: data,
};
}
throw new Error(`Unsupported approval action: ${String(appr.kind)}`);
});
// Router call: single call or multicall
const routerCallOp = plan.calls.length === 1
? {
target: router,
value: plan.value.toString(),
data: plan.calls[0],
}
: {
target: router,
value: plan.value.toString(),
data: (0, viem_1.encodeFunctionData)({
abi: zrouter_sdk_1.zRouterAbi,
functionName: "multicall",
args: [plan.calls],
}),
};
const ops = [...approvalOps, routerCallOp];
const pretty = `${args.side === "EXACT_IN" ? "Swap" : "Receive"} ${humanAmount} ${typeof args.tokenIn === "string" ? args.tokenIn.toUpperCase() : tIn.symbol ?? "TOKEN"} → ${typeof args.tokenOut === "string" ? args.tokenOut.toUpperCase() : tOut.symbol ?? "TOKEN"}`;
// If no wallet connected, return intent + ops for external execution
if (!walletClient) {
return { intent: pretty, ops, chain: chainId };
}
// Execute via your client (will naturally run approvals first, then router)
const hash = await client.executeOps(ops, chainId);
return { intent: pretty, ops, chain: chainId, hash };
},
});
//# sourceMappingURL=intents.js.map