UNPKG

@agentek/tools

Version:

Blockchain tools for AI agents

190 lines 9.82 kB
"use strict"; 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