diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3960fa3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.env* +/node_modules +/dist +/docs diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000..9c3f8fa Binary files /dev/null and b/bun.lockb differ diff --git a/package.json b/package.json index 6204554..2c4d708 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,12 @@ { "name": "@0xgasless/agentkit", "description": "0xGasless Agentkit - Gasless transactions and account abstraction toolkit", - "repository": "https://github.com/0xgasless/agentkit", - "version": "0.0.3", - "author": { - "name": "Permissionless Puter", - "email": "puter@prmsnls.xyz", - "url": "https://bento.me/puter" + "repository": { + "type": "git", + "url": "git+https://github.com/0xgasless/agentkit.git" }, + "version": "0.0.3", + "author": "Permissionless Puter (https://bento.me/puter)", "license": "Apache-2.0", "main": "dist/index.js", "types": "dist/types/index.d.ts", @@ -16,10 +15,10 @@ ], "scripts": { "build": "tsc", + "test": "bun run src/test.ts", "lint": "biome lint --write .", "format": "biome format --write .", "check": "tsc --noEmit", - "test": "bunx jest --no-cache --testMatch='**/*_test.ts'", "test:dry-run": "bun install && bun ci && bun publish --dry-run", "test:e2e": "bunx jest --no-cache --testMatch=**/e2e.ts --coverageThreshold '{}'", "test:types": "tsd --files src/tests/types.test-d.ts", @@ -28,10 +27,16 @@ "docs:serve": "bunx serve ./docs", "dev": "bun link && concurrently \"tsc --watch\" \"tsc-alias -w\"", "build:types": "tsc --project ./tsconfig.json && tsc-alias -p ./tsconfig.json", - "deploy": "bun run format && bun run lint && bun run clean && bun run build:types && bun run docs" + "deploy": "bun run format && bun run lint && bun run clean && bun run build:types && bun run docs && npm publish --access public" }, "dependencies": { "@0xgasless/smart-account": "latest", + "@langchain/core": "^0.3.39", + "@uniswap/sdk-core": "^7.5.0", + "@uniswap/v3-periphery": "^1.4.4", + "@uniswap/v3-sdk": "^3.24.0", + "axios": "^1.7.9", + "dotenv": "^16.4.7", "merkletreejs": "^0.4.1", "viem": "2", "zod": "^3.23.8" @@ -53,5 +58,9 @@ "types": "./dist/types/index.d.ts", "default": "./dist/index.js" } - } + }, + "bugs": { + "url": "https://github.com/0xgasless/agentkit/issues" + }, + "homepage": "https://github.com/0xgasless/agentkit#readme" } diff --git a/src/actions.ts b/src/actions.ts index 179d589..9997e0b 100644 --- a/src/actions.ts +++ b/src/actions.ts @@ -4,13 +4,11 @@ import { parseEther, parseUnits, encodeFunctionData, - numberToHex, - concat, - size, - getContract, - PublicClient, } from "viem"; import { ZeroXgaslessSmartAccount, Transaction } from "@0xgasless/smart-account"; +import { Token, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core'; +import { SwapRouter, Trade, Route, FeeAmount } from '@uniswap/v3-sdk'; +import { getPool, getUniswapV3Pool, WRAPPED_NATIVE_TOKEN } from './uniswap'; import { getWalletBalance, @@ -20,7 +18,7 @@ import { fetchTokenDetails, } from "./services"; import { TokenABI } from "./constants"; -import { AgentkitAction, ActionSchemaAny } from "./agentkit"; +import { AgentkitAction, ActionSchemaAny, Agentkit } from "./agentkit"; const GET_BALANCE_PROMPT = ` This tool will get the balance of the smart account associated with the wallet. @@ -152,9 +150,8 @@ export async function smartTransfer( if (!receipt) { return "Transaction failed"; } - return `Successfully transferred ${args.amount} ${ - isEth ? "ETH" : `tokens from contract ${args.tokenAddress}` - } to ${args.destination}.\nTransaction hash: ${receipt.transactionHash}`; + return `Successfully transferred ${args.amount} ${isEth ? "ETH" : `tokens from contract ${args.tokenAddress}` + } to ${args.destination}.\nTransaction hash: ${receipt.transactionHash}`; } catch (error) { return `Error transferring the asset: ${error}`; } @@ -172,7 +169,7 @@ export class SmartTransferAction implements AgentkitAction { try { const walletAddress = await wallet.getAddress(); + console.log('Smart Account Address:', walletAddress); + const chainId = await wallet.rpcProvider.getChainId(); const isFromEth = args.fromTokenAddress.toLowerCase() === "eth"; - // Get token decimals and calculate amount with proper decimals - const decimals = isFromEth ? 18 : await getDecimals(wallet, args.fromTokenAddress); - const amountWithDecimals = parseUnits(args.amount, Number(decimals)).toString(); + // Get token decimals and details + const fromDecimals = isFromEth ? 18 : await getDecimals(wallet, args.fromTokenAddress); + const toDecimals = await getDecimals(wallet, args.toTokenAddress); + + console.log('Swap Details:', { + fromToken: args.fromTokenAddress, + toToken: args.toTokenAddress, + amount: args.amount, + fromDecimals, + toDecimals + }); - // Check and set allowance for Permit2 if needed - if (!isFromEth) { - const tokenContract = getContract({ - address: args.fromTokenAddress as `0x${string}`, - abi: TokenABI, - client: wallet.rpcProvider, - }); + // Create token instances + const fromToken = new Token( + chainId, + isFromEth ? WRAPPED_NATIVE_TOKEN[chainId] : args.fromTokenAddress, + Number(fromDecimals) + ); + const toToken = new Token(chainId, args.toTokenAddress, Number(toDecimals)); - const currentAllowance = (await tokenContract.read.allowance([ - walletAddress as `0x${string}`, - PERMIT2_ADDRESS as `0x${string}`, - ])) as bigint; + // Calculate amount with decimals + const amountIn = parseUnits(args.amount, Number(fromDecimals)); - if (currentAllowance < BigInt(amountWithDecimals)) { - const approveData = encodeFunctionData({ - abi: TokenABI, - functionName: "approve", - args: [PERMIT2_ADDRESS, amountWithDecimals], - }); + // Get pool with best liquidity + const { pool } = await getUniswapV3Pool(wallet, fromToken, toToken, chainId); - await sendTransaction(wallet, { - to: args.fromTokenAddress, - data: approveData, - value: 0n, - }); - } - } - - // Get quote with permit2 - const quoteParams = new URLSearchParams({ - chainId: chainId.toString(), - sellToken: isFromEth ? "ETH" : args.fromTokenAddress, - buyToken: args.toTokenAddress, - sellAmount: amountWithDecimals, - takerAddress: walletAddress, - slippagePercentage: (args.slippageTolerance / 10000).toString(), + // Create route and trade + const route = new Route([pool], fromToken, toToken); + const trade = await Trade.createUncheckedTrade({ + route, + inputAmount: CurrencyAmount.fromRawAmount(fromToken, amountIn.toString()), + outputAmount: CurrencyAmount.fromRawAmount(toToken, '0'), + tradeType: TradeType.EXACT_INPUT, }); - const quoteResponse = await axios.get( - `https://api.0x.org/swap/permit2/quote?${quoteParams.toString()}`, - { headers }, - ); - - let txData = quoteResponse.data.transaction.data; + // Prepare swap parameters + const slippageTolerance = new Percent(args.slippageTolerance, 10000); + const deadline = Math.floor(Date.now() / 1000) + 1800; // 30 minutes - // If permit2 signature is required, sign and append to transaction data - if (quoteResponse.data.permit2?.eip712) { - const signature = await wallet.signTypedData(quoteResponse.data.permit2.eip712); + const methodParameters = SwapRouter.swapCallParameters(trade, { + slippageTolerance, + recipient: walletAddress, + deadline, + }); - // Append signature length (32 bytes) and signature to transaction data - const signatureLengthHex = numberToHex(size(signature), { - signed: false, - size: 32, - }); + // Execute swap + const receipt = await sendTransaction(wallet, { + to: UNISWAP_V3_ROUTER, + data: methodParameters.calldata, + value: BigInt(isFromEth ? amountIn.toString() : 0), + }); - txData = concat([txData, signatureLengthHex as `0x${string}`, signature as `0x${string}`]); - } + // After successful swap, get transaction details + const txHash = receipt.transactionHash; + const receiptDetails = await wallet.rpcProvider.waitForTransactionReceipt({ + hash: txHash as `0x${string}` + }); - // Execute swap transaction with permit2 signature - const receipt = await sendTransaction(wallet, { - to: quoteResponse.data.transaction.to, - data: txData, - value: BigInt(isFromEth ? amountWithDecimals : 0), + // Log detailed transaction info + console.log('Transaction Details:', { + hash: receiptDetails.transactionHash, + from: receiptDetails.from, + to: receiptDetails.to, + status: receiptDetails.status === 'success' ? 'Success' : 'Failed', + logs: receiptDetails.logs.map(log => ({ + address: log.address, + topics: log.topics, + data: log.data + })) }); - return `Successfully swapped ${args.amount} ${isFromEth ? "ETH" : args.fromTokenAddress} - to ${args.toTokenAddress}. TX: ${receipt.transactionHash}`; + // Parse transfer events + const transfers = receiptDetails.logs + .filter(log => log.topics[0] === '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef') // Transfer event + .map(log => ({ + token: log.address, + from: `0x${log.topics[1]?.slice(26)}`, + to: `0x${log.topics[2]?.slice(26)}`, + amount: BigInt(log.data).toString() + })); + + console.log('Token Transfers:', transfers); + + return `Swap completed successfully!\n + Transaction Hash: ${receiptDetails.transactionHash}\n + From Token: ${args.fromTokenAddress}\n + To Token: ${args.toTokenAddress}\n + Amount: ${args.amount}\n + Transfers: ${JSON.stringify(transfers, null, 2)}`; + } catch (error) { - if (axios.isAxiosError(error) && error.response?.data?.reason) { - return `Swap failed: ${error.response.data.reason}`; - } - return `Swap failed: ${error instanceof Error ? error.message : error}`; + console.error('Swap error:', error); + return `Swap failed: ${error instanceof Error ? error.message : 'Unknown error'}`; } } @@ -406,6 +413,149 @@ export class GetTokenDetailsAction implements AgentkitAction { + try { + const address = await agentkit.getAddress(); + return `Smart Account Address: ${address}`; + } catch (error) { + return `Error getting smart account address: ${error}`; + } +} + +/** + * Get smart account address action. + */ +export class GetAddressAction implements AgentkitAction { + public name = "get_address"; + public description = GET_ADDRESS_PROMPT; + public argsSchema = GetAddressInput; + public func = async ( + wallet: ZeroXgaslessSmartAccount, + _args: z.infer, + ) => { + const address = await wallet.getAddress(); + return `Smart Account Address: ${address}`; + }; + public smartAccountRequired = true; +} + +const TOKEN_ANALYSIS_PROMPT = ` +This tool provides comprehensive token analysis using CoinMarketCap data. It can: +- Get latest cryptocurrency listings and market data +- Fetch detailed token information and quotes +- Show upcoming airdrops +- Display market pairs and trading information +- Get global market metrics + +Required Setup: +- Set CMC_API_KEY environment variable with your CoinMarketCap API key + +Input options: +- type: Type of analysis ('listings', 'quotes', 'info', 'airdrops', 'marketPairs', 'global') +- symbol: Token symbol (e.g., 'BTC', 'ETH') +- limit: Number of results (default: 10) + +Note: You must have a valid CoinMarketCap API key set in your environment variables as CMC_API_KEY. +`; + +export const TokenAnalysisInput = z + .object({ + type: z.enum(["listings", "quotes", "info", "airdrops", "marketPairs", "global"]), + symbol: z.string().optional(), + limit: z.number().optional().default(10), + }) + .strip() + .describe("Instructions for analyzing token data from CoinMarketCap"); + +interface CoinData { + name: string; + symbol: string; + quote: { + USD: { + price: number; + }; + }; +} + +/** + * Analyzes token data using CoinMarketCap API. + */ +export async function analyzeToken(args: z.infer): Promise { + try { + const api = axios.create({ + baseURL: "https://pro-api.coinmarketcap.com/v1", + headers: { + "X-CMC_PRO_API_KEY": process.env.CMC_API_KEY, + Accept: "application/json", + }, + }); + + switch (args.type) { + case "listings": { + const response = await api.get("/cryptocurrency/listings/latest", { + params: { limit: args.limit }, + }); + const listings = response.data.data.map( + (coin: CoinData) => `${coin.name} (${coin.symbol}): $${coin.quote.USD.price.toFixed(2)}`, + ); + return `Latest Cryptocurrency Listings:\n${listings.join("\n")}`; + } + + case "quotes": { + if (!args.symbol) throw new Error("Symbol is required for quotes"); + const response = await api.get("/cryptocurrency/quotes/latest", { + params: { symbol: args.symbol }, + }); + const quote = response.data.data[args.symbol].quote.USD; + return ` + ${args.symbol} Quote: + Price: $${quote.price.toFixed(2)} + 24h Change: ${quote.percent_change_24h.toFixed(2)}% + Market Cap: $${quote.market_cap.toFixed(0)} + Volume 24h: $${quote.volume_24h.toFixed(0)} + `; + } + + // Add other cases for different analysis types... + + default: + return `Unsupported analysis type: ${args.type}`; + } + } catch (error) { + if (error instanceof Error) { + return `Error analyzing token: ${error.message}`; + } + return "Unknown error occurred during token analysis"; + } +} + +/** + * Token analysis action. + */ +export class TokenAnalysisAction implements AgentkitAction { + public name = "analyze_token"; + public description = TOKEN_ANALYSIS_PROMPT; + public argsSchema = TokenAnalysisInput; + public func = analyzeToken; + public smartAccountRequired = true; +} + /** * Retrieves all AgentkitAction instances. * WARNING: All new AgentkitAction classes must be instantiated here to be discovered. @@ -419,6 +569,8 @@ export function getAllAgentkitActions(): AgentkitAction[] { new SwapAction(), new CreateWalletAction(), new GetTokenDetailsAction(), + new GetAddressAction(), + new TokenAnalysisAction(), ]; } diff --git a/src/agentkit.ts b/src/agentkit.ts index 40c301f..8369216 100644 --- a/src/agentkit.ts +++ b/src/agentkit.ts @@ -57,6 +57,7 @@ export interface SmartAgentOptions extends PublicAgentOptions { accountPath?: number; privateKey?: `0x${string}`; apiKey: string; + cmcApiKey?: string; } export class Agentkit { @@ -178,4 +179,8 @@ export class Agentkit { } return await this.smartAccount.getAddress(); } + + public async getProvider() { + return this.smartAccount?.rpcProvider; + } } diff --git a/src/langchain.ts b/src/langchain.ts index b72bc79..84c7605 100644 --- a/src/langchain.ts +++ b/src/langchain.ts @@ -72,13 +72,14 @@ type ActionSchemaAny = z.ZodObject; * export 0xGASLESS_API_KEY="your-0xgasless-api-key" * export 0xGASLESS_CHAIN_ID="your-0xgasless-chain-id" * export 0xGASLESS_PRIVATE_KEY="your-0xgasless-private-key" + * export CMC_API_KEY = "your-coinmarketcap-api-key"; + * * * Optional: * export 0xGASLESS_MNEMONIC_PHRASE="your-0xgasless-mnemonic-phrase" * export 0xGASLESS_RPC_URL="your-0xgasless-rpc-url" * ``` */ - export class AgentkitTool extends StructuredTool { /** * Schema definition for the tool's input diff --git a/src/services.ts b/src/services.ts index 4a42579..167107e 100644 --- a/src/services.ts +++ b/src/services.ts @@ -7,6 +7,7 @@ import { import { TokenABI } from "./constants"; import { generatePrivateKey } from "viem/accounts"; import { getContract } from "viem"; +import { Agentkit } from "./agentkit"; /** * get token details @@ -77,3 +78,62 @@ export async function fetchTokenDetails(wallet: ZeroXgaslessSmartAccount, tokenA chainId: wallet.rpcProvider.chain?.id ?? 0, } as TokenDetails; } + +export async function getCMCData(type: string, symbol?: string, limit: number = 10) { + const cmcApiKey = process.env.NEXT_PUBLIC_CMC_API_KEY; + if (!cmcApiKey) { + throw new Error("CMC API key is required for token analysis"); + } + + const baseUrl = "https://pro-api.coinmarketcap.com/v1"; + let endpoint = ""; + + switch (type) { + case "listings": + endpoint = `/cryptocurrency/listings/latest?limit=${limit}`; + break; + case "quotes": + if (!symbol) throw new Error("Symbol is required for quotes"); + endpoint = `/cryptocurrency/quotes/latest?symbol=${symbol}`; + break; + case "info": + if (!symbol) throw new Error("Symbol is required for info"); + endpoint = `/cryptocurrency/info?symbol=${symbol}`; + break; + case "marketPairs": + if (!symbol) throw new Error("Symbol is required for market pairs"); + endpoint = `/cryptocurrency/market-pairs/latest?symbol=${symbol}&limit=${limit}`; + break; + case "global": + endpoint = "/global-metrics/quotes/latest"; + break; + default: + throw new Error("Invalid analysis type"); + } + + const response = await fetch(`${baseUrl}${endpoint}`, { + headers: { + "X-CMC_PRO_API_KEY": cmcApiKey, + Accept: "application/json", + }, + }); + + if (!response.ok) { + throw new Error(`CMC API error: ${response.statusText}`); + } + + const data = await response.json(); + return data; +} + +export async function getSmartAccountAddress(agentkit: Agentkit): Promise { + try { + const address = await agentkit.getAddress(); + if (!address) { + throw new Error("Could not get smart account address"); + } + return address; + } catch (error) { + throw new Error(`Failed to get smart account address: ${error}`); + } +} diff --git a/src/test.ts b/src/test.ts new file mode 100644 index 0000000..eb75c07 --- /dev/null +++ b/src/test.ts @@ -0,0 +1,138 @@ +import { Agentkit } from "./agentkit"; +import dotenv from "dotenv"; +import { getAllAgentkitActions } from "./actions"; +import { UNISWAP_V3_ROUTER } from './uniswap'; +import { encodeFunctionData } from "viem"; +import { parseUnits } from "viem"; +import { TokenABI } from "./constants"; + +dotenv.config(); + +async function testActions() { + try { + console.log("Config:", { + hasPrivateKey: !!process.env.PRIVATE_KEY, + hasApiKey: !!process.env.API_KEY, + hasCmcApiKey: !!process.env.CMC_API_KEY, + }); + + // Initialize Agentkit + const agentkit = await Agentkit.configureWithWallet({ + privateKey: process.env.PRIVATE_KEY as `0x${string}`, + rpcUrl: process.env.RPC_URL as string, + apiKey: process.env.API_KEY as string, + chainID: Number(process.env.CHAIN_ID) || 8453, + cmcApiKey: process.env.CMC_API_KEY as string, + }); + + // Get all actions + const actions = getAllAgentkitActions(); + + // Test get_address + console.log("\nTesting get_address:"); + const addressResult = await agentkit.run(actions[5], actions[5].argsSchema.parse({})); + console.log(addressResult); + + // Test get_balance + console.log("\nTesting get_balance:"); + const balanceResult = await agentkit.run(actions[0], actions[0].argsSchema.parse({})); + console.log(balanceResult); + + // Test token_analysis + console.log("\nTesting token_analysis:"); + const analysisResult = await agentkit.run( + actions[6], + actions[6].argsSchema.parse({ + type: "listings", + limit: 5, + }), + ); + console.log(analysisResult); + + // Test USDT and USDC balances before swap + console.log("\nChecking balances before swap:"); + const beforeBalances = await agentkit.run( + actions[0], + actions[0].argsSchema.parse({ + tokenAddresses: [ + "0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7", // USDT + "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E" // USDC + ] + }) + ); + console.log("Before swap balances:", beforeBalances); + + // Check USDT balance first + console.log("\nChecking USDT balance before approval:"); + const usdtBalance = await agentkit.run( + actions[0], + actions[0].argsSchema.parse({ + tokenAddresses: ["0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7"] // USDT + }) + ); + console.log("USDT Balance:", usdtBalance); + + // Approve USDT first + console.log("\nApproving USDT..."); + const data = encodeFunctionData({ + abi: TokenABI, + functionName: "approve", + args: [ + UNISWAP_V3_ROUTER[43114], + parseUnits("0.01", 6) // USDT has 6 decimals, reduced amount + ], + }); + + const approveResult = await agentkit.run( + actions[1], // SmartTransferAction + actions[1].argsSchema.parse({ + tokenAddress: "0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7", // USDT + destination: "0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7", // USDT contract + amount: "0.01", // Reduced amount for testing + data: data + }) + ); + console.log("Approval result:", approveResult); + + // Wait for approval + await new Promise(resolve => setTimeout(resolve, 15000)); + + // Then proceed with swap if approval successful + if (!approveResult.includes("Error")) { + console.log("\nTesting swap:"); + const swapResult = await agentkit.run( + actions[2], + actions[2].argsSchema.parse({ + fromTokenAddress: "0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7", + toTokenAddress: "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E", + amount: "0.01", + slippageTolerance: 100 + }) + ); + console.log("Swap result:", swapResult); + } + + // Wait for swap to be confirmed + await new Promise(resolve => setTimeout(resolve, 15000)); + + // Test balances after swap + console.log("\nChecking balances after swap:"); + const afterBalances = await agentkit.run( + actions[0], + actions[0].argsSchema.parse({ + tokenAddresses: [ + "0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7", // USDT + "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E" // USDC + ] + }) + ); + console.log("After swap balances:", afterBalances); + } catch (error) { + console.error("Test failed:", error); + } +} + +// Run tests +testActions(); + +// "test": "bunx jest --no-cache --testMatch='**/*_test.ts'", diff --git a/src/uniswap.ts b/src/uniswap.ts new file mode 100644 index 0000000..9b2cf89 --- /dev/null +++ b/src/uniswap.ts @@ -0,0 +1,134 @@ +import { Token } from '@uniswap/sdk-core'; +import { Pool, FeeAmount } from '@uniswap/v3-sdk'; +import { ZeroXgaslessSmartAccount } from "@0xgasless/smart-account"; +import { getContract } from 'viem'; +import IUniswapV3Pool from '@uniswap/v3-core/artifacts/contracts/interfaces/IUniswapV3Pool.sol/IUniswapV3Pool.json'; +import IUniswapV3Factory from '@uniswap/v3-core/artifacts/contracts/interfaces/IUniswapV3Factory.sol/IUniswapV3Factory.json'; + + +export const UNISWAP_V3_FACTORY_ADDRESS: { [chainId: number]: string } = { + 1: '0x1F98431c8aD98523631AE4a59f267346ea31F984', + 43114: '0x740b1c1de25031C31FF4fC9A62f554A55cdC1baD', + 8453: '0x33128a8fC17869897dcE68Ed026d694621f6FDfD', +}; + +export const WRAPPED_NATIVE_TOKEN: { [chainId: number]: string } = { + 1: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', + 43114: '0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7', + 8453: '0x4200000000000000000000000000000000000006', +}; + +// Add Uniswap V3 Router addresses for each chain +export const UNISWAP_V3_ROUTER: { [chainId: number]: string } = { + 1: '0xE592427A0AEce92De3Edee1F18E0157C05861564', // Ethereum + 43114: '0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45', // Avalanche + 8453: '0x2626664c2603336E57B271c5C0b26F421741e481', // Base +}; + +interface Slot0Data { + sqrtPriceX96: bigint; + tick: number; +} + +export async function getUniswapV3Pool( + wallet: ZeroXgaslessSmartAccount, + tokenA: Token, + tokenB: Token, + chainId: number +): Promise<{ pool: Pool; fee: FeeAmount }> { + console.log('Searching pool for tokens:', { + tokenA: tokenA.address, + tokenB: tokenB.address, + chainId + }); + + const factoryAddress = UNISWAP_V3_FACTORY_ADDRESS[chainId]; + if (!factoryAddress) { + throw new Error(`Uniswap V3 not supported on chain ${chainId}`); + } + + const factory = getContract({ + address: factoryAddress as `0x${string}`, + abi: IUniswapV3Factory.abi, + client: wallet.rpcProvider, + }); + + for (const fee of [FeeAmount.MEDIUM, FeeAmount.LOW, FeeAmount.HIGH]) { + try { + console.log(`Trying fee tier: ${fee}`); + const poolAddress = await factory.read.getPool([ + tokenA.address, + tokenB.address, + fee + ]) as `0x${string}`; + + console.log('Found pool address:', poolAddress); + + if (poolAddress === '0x0000000000000000000000000000000000000000') { + console.log('No pool for this fee tier'); + continue; + } + + const pool = await getPool(wallet, poolAddress, tokenA, tokenB, fee); + return { pool, fee }; + } catch (error) { + console.error(`Error with fee ${fee}:`, error); + continue; + } + } + + throw new Error(`No active pool found for ${tokenA.symbol}/${tokenB.symbol}`); +} + +export async function getPool( + wallet: ZeroXgaslessSmartAccount, + poolAddress: string, + tokenA: Token, + tokenB: Token, + fee: FeeAmount +): Promise { + try { + const [token0, token1] = tokenA.sortsBefore(tokenB) ? [tokenA, tokenB] : [tokenB, tokenA]; + + console.log('Pool Address:', poolAddress); + console.log('Tokens:', { + token0: token0.address, + token1: token1.address, + decimals0: token0.decimals, + decimals1: token1.decimals + }); + + const poolContract = getContract({ + address: poolAddress as `0x${string}`, + abi: IUniswapV3Pool.abi, + client: wallet.rpcProvider, + }); + + const [slot0Array, liquidity] = await Promise.all([ + poolContract.read.slot0() as Promise<[bigint, number, ...unknown[]]>, + poolContract.read.liquidity() as Promise + ]); + + const slot0Data: Slot0Data = { + sqrtPriceX96: slot0Array[0], + tick: slot0Array[1] + }; + + console.log('Pool Data:', { + slot0: slot0Data, + liquidity + }); + + return new Pool( + token0, + token1, + fee, + slot0Data.sqrtPriceX96.toString(), + (liquidity as bigint).toString(), + slot0Data.tick + ); + } catch (error) { + console.error('Pool creation error:', error); + throw new Error(`Failed to create pool: ${error instanceof Error ? error.message : 'Unknown error'}`); + } +} \ No newline at end of file