UNPKG

1inch-agent-kit

Version:

AI Agent Kit for 1inch - Connect any LLM to 1inch DeFi protocols

716 lines 31 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getActiveOrders = getActiveOrders; exports.getEscrowFactory = getEscrowFactory; exports.getQuote = getQuote; exports.buildOrder = buildOrder; exports.submitOrder = submitOrder; exports.submitManyOrders = submitManyOrders; exports.submitSecret = submitSecret; exports.executeCrossChainSwap = executeCrossChainSwap; exports.fusionPlusAPI = fusionPlusAPI; const fetcher_1 = require("../../utils/fetcher"); const logger_1 = require("../../utils/logger"); const wallet_1 = require("../../utils/wallet"); // Import the official 1inch Fusion+ SDK const { SDK, NetworkEnum, PresetEnum, HashLock } = require("@1inch/cross-chain-sdk"); const { PrivateKeyProviderConnector } = require("@1inch/fusion-sdk"); const { randomBytes } = require("crypto"); // Initialize the SDK let sdk = null; // Helper function to serialize BigInt values function serializeBigInt(obj) { if (obj === null || obj === undefined) { return obj; } if (typeof obj === 'bigint') { return obj.toString(); } if (Array.isArray(obj)) { return obj.map(serializeBigInt); } if (typeof obj === 'object') { const result = {}; for (const [key, value] of Object.entries(obj)) { result[key] = serializeBigInt(value); } return result; } return obj; } // Custom blockchain provider that can work with both local and frontend wallets class HybridBlockchainProvider { constructor() { this.walletContext = wallet_1.walletManager.getWalletContext(); } async signTypedData(typedData) { const connectedWallet = this.walletContext.wallet; if (!connectedWallet) { throw new Error("No wallet connected. Please connect your wallet first."); } if (connectedWallet.privateKey) { // Local wallet with private key - use ethers to sign const { ethers } = require("ethers"); const wallet = new ethers.Wallet(connectedWallet.privateKey); return await wallet.signTypedData(typedData.domain, { [typedData.primaryType]: typedData.types[typedData.primaryType] }, typedData.message); } else { // Frontend wallet (like MetaMask) - throw error to trigger frontend signing throw new Error("FRONTEND_SIGNING_REQUIRED: Typed data needs to be signed by frontend wallet"); } } async getAddress() { const connectedWallet = this.walletContext.wallet; if (!connectedWallet) { throw new Error("No wallet connected. Please connect your wallet first."); } return connectedWallet.address; } } function initializeSDK() { if (!sdk) { const apiKey = process.env.ONEINCH_API_KEY; if (!apiKey) { throw new Error("1inch API key is required. Set ONEINCH_API_KEY environment variable."); } const connectedWallet = wallet_1.walletManager.getWalletContext().wallet; if (!connectedWallet) { throw new Error("No wallet connected. Please connect your wallet first."); } let blockchainProvider; if (connectedWallet.privateKey) { // Local wallet with private key - use the SDK's PrivateKeyProviderConnector blockchainProvider = new PrivateKeyProviderConnector(connectedWallet.privateKey, null); logger_1.logger.info('Using local wallet with private key for SDK'); } else { // Frontend wallet - use our custom provider blockchainProvider = new HybridBlockchainProvider(); logger_1.logger.info('Using frontend wallet with custom provider for SDK'); } try { sdk = new SDK({ url: "https://api.1inch.dev/fusion-plus", authKey: apiKey, blockchainProvider, }); logger_1.logger.info('SDK initialized successfully'); } catch (error) { logger_1.logger.error('SDK initialization error:', error); throw error; } } return sdk; } // Helper function to generate secrets function generateSecrets(count) { return Array.from({ length: count }, () => "0x" + randomBytes(32).toString("hex")); } // Helper function to create hash lock function createHashLock(secrets) { if (secrets.length === 1) { return HashLock.forSingleFill(secrets[0]); } else { return HashLock.forMultipleFills(HashLock.getMerkleLeaves(secrets)); } } // Helper function to get secret hashes function getSecretHashes(secrets) { return secrets.map((s) => HashLock.hashSecret(s)); } // Network enum mapping const NETWORK_MAP = { 1: NetworkEnum.ETHEREUM, 10: NetworkEnum.OPTIMISM, 137: NetworkEnum.POLYGON, 42161: NetworkEnum.ARBITRUM, 56: NetworkEnum.BSC, }; // Preset enum mapping const PRESET_MAP = { fast: PresetEnum.fast, medium: PresetEnum.medium, slow: PresetEnum.slow, }; /** * Get cross chain swap active orders */ async function getActiveOrders(params) { const fetcher = new fetcher_1.OneInchFetcher(process.env.ONEINCH_API_KEY); const queryParams = new URLSearchParams(); if (params.page) queryParams.append('page', params.page.toString()); if (params.limit) queryParams.append('limit', params.limit.toString()); if (params.srcChain) queryParams.append('srcChain', params.srcChain.toString()); if (params.dstChain) queryParams.append('dstChain', params.dstChain.toString()); const url = `/fusion-plus/orders/v1.0/order/active${queryParams.toString() ? '?' + queryParams.toString() : ''}`; return await fetcher.get(url); } /** * Get actual escrow factory contract address */ async function getEscrowFactory(params) { const fetcher = new fetcher_1.OneInchFetcher(process.env.ONEINCH_API_KEY); const url = `/fusion-plus/orders/v1.0/order/escrow?chainId=${params.chainId}`; return await fetcher.get(url); } /** * Get quote details based on input data using SDK */ async function getQuote(params) { const sdk = initializeSDK(); const srcChainId = NETWORK_MAP[params.srcChain]; const dstChainId = NETWORK_MAP[params.dstChain]; if (!srcChainId || !dstChainId) { throw new Error(`Unsupported chain: ${params.srcChain} or ${params.dstChain}`); } logger_1.logger.info('Getting quote with SDK:', { amount: params.amount, srcChainId, dstChainId, srcTokenAddress: params.srcTokenAddress, dstTokenAddress: params.dstTokenAddress, walletAddress: params.walletAddress }); try { const quote = await sdk.getQuote({ amount: params.amount, srcChainId, dstChainId, enableEstimate: params.enableEstimate, srcTokenAddress: params.srcTokenAddress, dstTokenAddress: params.dstTokenAddress, walletAddress: params.walletAddress, }); logger_1.logger.info('Quote received:', { srcTokenAmount: quote.srcTokenAmount, dstTokenAmount: quote.dstTokenAmount, presets: Object.keys(quote.presets) }); return serializeBigInt(quote); } catch (error) { logger_1.logger.error('SDK getQuote error details:', { error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, params: { amount: params.amount, srcChainId, dstChainId, srcTokenAddress: params.srcTokenAddress, dstTokenAddress: params.dstTokenAddress, walletAddress: params.walletAddress } }); throw error; } } /** * Build order using SDK */ async function buildOrder(params) { const sdk = initializeSDK(); const srcChainId = NETWORK_MAP[params.srcChain]; const dstChainId = NETWORK_MAP[params.dstChain]; if (!srcChainId || !dstChainId) { throw new Error(`Unsupported chain: ${params.srcChain} or ${params.dstChain}`); } const preset = params.preset ? PRESET_MAP[params.preset] : PresetEnum.fast; logger_1.logger.info('Building order with SDK:', { srcChainId, dstChainId, preset, walletAddress: params.walletAddress }); // Generate secrets based on the preset const secretsCount = params.quote.presets[preset]?.secretsCount || 1; const secrets = generateSecrets(secretsCount); const hashLock = createHashLock(secrets); const secretHashes = getSecretHashes(secrets); logger_1.logger.info('Generated secrets and hash lock:', { secretsCount, secretHashesCount: secretHashes.length }); // Create order using SDK const { hash, quoteId, order } = await sdk.createOrder(params.quote, { walletAddress: params.walletAddress, hashLock, preset, source: params.source || "1inch-agent-kit", secretHashes, }); logger_1.logger.info('Order created successfully:', { hash, quoteId }); return serializeBigInt({ typedData: order, // The SDK returns the order directly orderHash: hash, extension: "0x", // SDK handles this internally secrets, secretHashes, quoteId }); } /** * Submit order using SDK */ async function submitOrder(params) { const sdk = initializeSDK(); const srcChainId = NETWORK_MAP[params.srcChainId]; if (!srcChainId) { throw new Error(`Unsupported chain: ${params.srcChainId}`); } logger_1.logger.info('Submitting order with SDK:', { srcChainId, quoteId: params.quoteId, secretHashesCount: params.secretHashes?.length || 0 }); try { // Check if the order has a build method (SDK order object) if (params.order && typeof params.order.build === 'function') { // This is a proper SDK order object with build method const orderInfo = await sdk.submitOrder(srcChainId, params.order, params.quoteId, params.secretHashes || []); logger_1.logger.info('Order submitted successfully:', orderInfo); return serializeBigInt(orderInfo); } else { // This is likely a plain order object, we need to handle it differently logger_1.logger.info('Order object does not have build method, attempting alternative submission'); // For orders without build method, we need to use the signature-based submission if (!params.signature || params.signature === "0x") { throw new Error('FRONTEND_SIGNING_REQUIRED: Order needs to be signed by frontend wallet'); } // Extract the actual order data - SDK order objects have nested structures let orderData = params.order; // Navigate through the nested structure to find the actual order data if (params.order && params.order.inner) { logger_1.logger.info('Extracting order data from inner property'); orderData = params.order.inner; // If inner still has nested structure, go deeper if (orderData && orderData.inner) { logger_1.logger.info('Extracting order data from inner.inner property'); orderData = orderData.inner; } } // Now map the SDK order structure to the API-expected OrderInput format const mappedOrderData = { salt: orderData._salt?.toString() || orderData.salt?.toString() || '0', makerAsset: orderData.makerAsset || '0x0000000000000000000000000000000000000000', takerAsset: orderData.takerAsset || '0x0000000000000000000000000000000000000000', maker: orderData.maker || '0x0000000000000000000000000000000000000000', receiver: orderData.receiver || '0x0000000000000000000000000000000000000000', makingAmount: orderData.makingAmount?.toString() || '0', takingAmount: orderData.takingAmount?.toString() || '0', makerTraits: orderData.makerTraits?.toString() || '0' }; logger_1.logger.info('Mapped order data to API format:', { originalStructure: { keys: Object.keys(orderData), _salt: orderData._salt?.toString(), makerAsset: orderData.makerAsset, takerAsset: orderData.takerAsset }, mappedStructure: mappedOrderData }); logger_1.logger.info('Order data structure:', { originalKeys: Object.keys(params.order), extractedKeys: Object.keys(orderData), mappedKeys: Object.keys(mappedOrderData), hasInner: !!params.order.inner }); // Use the fetcher for direct API submission with signature const fetcher = new fetcher_1.OneInchFetcher(process.env.ONEINCH_API_KEY); const url = `/fusion-plus/relayer/v1.0/submit`; // Format the data according to SignedOrderInput interface const submitData = { order: mappedOrderData, // Use the properly mapped order data srcChainId: params.srcChainId, signature: params.signature, extension: params.extension, quoteId: params.quoteId, secretHashes: params.secretHashes || [] }; logger_1.logger.info('Submitting order data:', { orderKeys: Object.keys(mappedOrderData), orderValues: mappedOrderData, srcChainId: params.srcChainId, signatureLength: params.signature.length, quoteId: params.quoteId, secretHashesCount: (params.secretHashes || []).length }); const result = await fetcher.post(url, submitData); logger_1.logger.info('Order submitted successfully via API:', result); return serializeBigInt(result); } } catch (error) { // Check if this is a frontend signing request if (error instanceof Error && error.message.includes('FRONTEND_SIGNING_REQUIRED')) { logger_1.logger.info('Frontend signing required for order submission'); // Return the data needed for frontend signing return { requiresFrontendSigning: true, order: params.order, srcChainId: params.srcChainId, quoteId: params.quoteId, secretHashes: params.secretHashes || [], extension: params.extension, error: 'FRONTEND_SIGNING_REQUIRED: Order needs to be signed by frontend wallet' }; } // Re-throw other errors throw error; } } /** * Submit many cross chain orders that resolvers will be able to fill */ async function submitManyOrders(params) { const fetcher = new fetcher_1.OneInchFetcher(process.env.ONEINCH_API_KEY); const url = `/fusion-plus/relayer/v1.0/submit/many`; return await fetcher.post(url, params.orderHashes); } /** * Submit a secret for order fill after SrcEscrow and DstEscrow deployed and DstChain finality lock passed */ async function submitSecret(params) { const sdk = initializeSDK(); logger_1.logger.info('Submitting secret with SDK:', { orderHash: params.orderHash }); await sdk.submitSecret(params.orderHash, params.secret); logger_1.logger.info('Secret submitted successfully'); return serializeBigInt({ success: true }); } /** * Execute a complete cross-chain swap using Fusion+ (combines quote and place order) */ async function executeCrossChainSwap(params) { const sdk = initializeSDK(); const srcChainId = NETWORK_MAP[params.srcChain]; const dstChainId = NETWORK_MAP[params.dstChain]; if (!srcChainId || !dstChainId) { throw new Error(`Unsupported chain: ${params.srcChain} or ${params.dstChain}`); } const preset = params.preset ? PRESET_MAP[params.preset] : PresetEnum.fast; logger_1.logger.info('Executing cross-chain swap:', { srcChainId, dstChainId, preset, walletAddress: params.walletAddress, amount: params.amount }); try { // Step 1: Get Quote logger_1.logger.info('Step 1: Getting quote...'); const quote = await sdk.getQuote({ amount: params.amount, srcChainId, dstChainId, enableEstimate: true, srcTokenAddress: params.srcTokenAddress, dstTokenAddress: params.dstTokenAddress, walletAddress: params.walletAddress, }); logger_1.logger.info('Quote received:', { srcTokenAmount: quote.srcTokenAmount, dstTokenAmount: quote.dstTokenAmount, presets: Object.keys(quote.presets) }); // Step 2: Generate secrets and hash lock (based on documentation) logger_1.logger.info('Step 2: Generating secrets and hash lock...'); const secretsCount = quote.getPreset().secretsCount; const secrets = Array.from({ length: secretsCount }).map(() => "0x" + randomBytes(32).toString("hex")); const secretHashes = secrets.map((s) => HashLock.hashSecret(s)); logger_1.logger.info('Generated secrets and hash lock:', { secretsCount, secretHashesCount: secretHashes.length }); // Create HashLock based on documentation const hashLock = secretsCount === 1 ? HashLock.forSingleFill(secrets[0]) : HashLock.forMultipleFills(secretHashes.map((secretHash, i) => { const { solidityPackedKeccak256 } = require("ethers"); return solidityPackedKeccak256(["uint64", "bytes32"], [i, secretHash.toString()]); })); // Step 3: Place Order (using the correct SDK method from documentation) logger_1.logger.info('Step 3: Placing order using SDK placeOrder method...'); try { const orderResponse = await sdk.placeOrder(quote, { walletAddress: params.walletAddress, hashLock, secretHashes, preset, source: params.source || "1inch-agent-kit" }); logger_1.logger.info('Order placed successfully:', orderResponse); // Return the complete result return serializeBigInt({ success: true, quote: serializeBigInt(quote), orderHash: orderResponse.orderHash, quoteId: orderResponse.quoteId || quote.quoteId, secrets: secrets, secretHashes: secretHashes, orderResponse: serializeBigInt(orderResponse) }); } catch (error) { // Check if this is a frontend signing request if (error instanceof Error && error.message.includes('FRONTEND_SIGNING_REQUIRED')) { logger_1.logger.info('Frontend signing required for order placement'); // Return the data needed for frontend signing return { requiresFrontendSigning: true, success: false, error: 'FRONTEND_SIGNING_REQUIRED: Order needs to be signed by frontend wallet', quote: serializeBigInt(quote), secrets: secrets, secretHashes: secretHashes, hashLock: serializeBigInt(hashLock), preset, walletAddress: params.walletAddress, source: params.source || "1inch-agent-kit", srcChainId: params.srcChain, dstChainId: params.dstChain, // Add the orderInput structure that the frontend expects orderInput: { salt: '0', makerAsset: params.srcTokenAddress, takerAsset: params.dstTokenAddress, maker: params.walletAddress, receiver: params.walletAddress, makingAmount: params.amount.toString(), takingAmount: '0', makerTraits: '0' }, typedData: { domain: { name: '1inch Fusion', version: '1', chainId: params.srcChain, verifyingContract: '0x1111111254fb6c44bAC0beD2854e76F90643097d' }, types: { Order: [ { name: 'salt', type: 'uint256' }, { name: 'makerAsset', type: 'address' }, { name: 'takerAsset', type: 'address' }, { name: 'maker', type: 'address' }, { name: 'receiver', type: 'address' }, { name: 'makingAmount', type: 'uint256' }, { name: 'takingAmount', type: 'uint256' }, { name: 'makerTraits', type: 'uint256' } ] }, primaryType: 'Order', message: { salt: '0', makerAsset: params.srcTokenAddress, takerAsset: params.dstTokenAddress, maker: params.walletAddress, receiver: params.walletAddress, makingAmount: params.amount.toString(), takingAmount: '0', makerTraits: '0' } } }; } // Re-throw other errors throw error; } } catch (error) { logger_1.logger.error('Cross-chain swap execution error:', { error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, params: { srcChain: params.srcChain, dstChain: params.dstChain, srcTokenAddress: params.srcTokenAddress, dstTokenAddress: params.dstTokenAddress, amount: params.amount, walletAddress: params.walletAddress } }); throw error; } } /** * Main fusionPlusAPI function that handles all Fusion+ operations */ async function fusionPlusAPI(params) { try { // Validate endpoint parameter if (!params.endpoint) { throw new Error('endpoint parameter is required'); } switch (params.endpoint) { case 'getActiveOrders': return await getActiveOrders({ page: params.page, limit: params.limit, srcChain: params.srcChain, dstChain: params.dstChain }); case 'getEscrowFactory': if (!params.chainId) { throw new Error('chainId parameter is required for getEscrowFactory'); } return await getEscrowFactory({ chainId: params.chainId }); case 'getQuote': if (!params.srcChain) { throw new Error('srcChain parameter is required for getQuote'); } if (!params.dstChain) { throw new Error('dstChain parameter is required for getQuote'); } if (!params.srcTokenAddress) { throw new Error('srcTokenAddress parameter is required for getQuote'); } if (!params.dstTokenAddress) { throw new Error('dstTokenAddress parameter is required for getQuote'); } if (!params.amount) { throw new Error('amount parameter is required for getQuote'); } if (!params.walletAddress) { throw new Error('walletAddress parameter is required for getQuote'); } if (params.enableEstimate === undefined) { throw new Error('enableEstimate parameter is required for getQuote'); } return await getQuote({ srcChain: params.srcChain, dstChain: params.dstChain, srcTokenAddress: params.srcTokenAddress, dstTokenAddress: params.dstTokenAddress, amount: params.amount, walletAddress: params.walletAddress, enableEstimate: params.enableEstimate, fee: params.fee, isPermit2: params.isPermit2, permit: params.permit }); case 'buildOrder': if (!params.srcChain) { throw new Error('srcChain parameter is required for buildOrder'); } if (!params.dstChain) { throw new Error('dstChain parameter is required for buildOrder'); } if (!params.srcTokenAddress) { throw new Error('srcTokenAddress parameter is required for buildOrder'); } if (!params.dstTokenAddress) { throw new Error('dstTokenAddress parameter is required for buildOrder'); } if (!params.amount) { throw new Error('amount parameter is required for buildOrder'); } if (!params.walletAddress) { throw new Error('walletAddress parameter is required for buildOrder'); } if (!params.quote) { throw new Error('quote parameter is required for buildOrder'); } return await buildOrder({ srcChain: params.srcChain, dstChain: params.dstChain, srcTokenAddress: params.srcTokenAddress, dstTokenAddress: params.dstTokenAddress, amount: params.amount, walletAddress: params.walletAddress, quote: params.quote, secretsHashList: params.secretsHashList || [], fee: params.fee, source: params.source, isPermit2: params.isPermit2, isMobile: params.isMobile, feeReceiver: params.feeReceiver, permit: params.permit, preset: params.preset }); case 'submitOrder': if (!params.order) { throw new Error('order parameter is required for submitOrder'); } if (!params.srcChainId) { throw new Error('srcChainId parameter is required for submitOrder'); } if (!params.quoteId) { throw new Error('quoteId parameter is required for submitOrder'); } return await submitOrder({ order: params.order, // This should be the complex order object from createOrder srcChainId: params.srcChainId, signature: params.signature || "0x", // SDK handles signing internally extension: params.extension || "0x", quoteId: params.quoteId, secretHashes: params.secretHashes }); case 'submitManyOrders': if (!params.orderHashes) { throw new Error('orderHashes parameter is required for submitManyOrders'); } return await submitManyOrders({ orderHashes: params.orderHashes }); case 'submitSecret': if (!params.secret) { throw new Error('secret parameter is required for submitSecret'); } if (!params.orderHash) { throw new Error('orderHash parameter is required for submitSecret'); } return await submitSecret({ secret: params.secret, orderHash: params.orderHash }); case 'executeCrossChainSwap': if (!params.srcChain) { throw new Error('srcChain parameter is required for executeCrossChainSwap'); } if (!params.dstChain) { throw new Error('dstChain parameter is required for executeCrossChainSwap'); } if (!params.srcTokenAddress) { throw new Error('srcTokenAddress parameter is required for executeCrossChainSwap'); } if (!params.dstTokenAddress) { throw new Error('dstTokenAddress parameter is required for executeCrossChainSwap'); } if (!params.amount) { throw new Error('amount parameter is required for executeCrossChainSwap'); } if (!params.walletAddress) { throw new Error('walletAddress parameter is required for executeCrossChainSwap'); } if (params.preset === undefined) { throw new Error('preset parameter is required for executeCrossChainSwap'); } return await executeCrossChainSwap({ srcChain: params.srcChain, dstChain: params.dstChain, srcTokenAddress: params.srcTokenAddress, dstTokenAddress: params.dstTokenAddress, amount: params.amount, walletAddress: params.walletAddress, preset: params.preset, source: params.source }); default: throw new Error(`Unknown endpoint: ${params.endpoint}`); } } catch (error) { throw new Error(`Fusion+ API error: ${error instanceof Error ? error.message : String(error)}`); } } //# sourceMappingURL=index.js.map