1inch-agent-kit
Version:
AI Agent Kit for 1inch - Connect any LLM to 1inch DeFi protocols
716 lines • 31 kB
JavaScript
"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