UNPKG

edwin-sdk

Version:

SDK for integrating AI agents with DeFi protocols

1,429 lines (1,405 loc) 151 kB
'use strict'; var viem = require('viem'); var accounts = require('viem/accounts'); var viemChains = require('viem/chains'); var winston = require('winston'); var fs = require('fs'); var path = require('path'); var os = require('os'); var ethers = require('ethers'); var web3_js = require('@solana/web3.js'); var splTokenRegistry = require('@solana/spl-token-registry'); var bs58 = require('bs58'); var axios2 = require('axios'); var contractHelpers = require('@aave/contract-helpers'); var aaveAddressBook = require('@bgd-labs/aave-address-book'); var zod = require('zod'); var DLMM = require('@meteora-ag/dlmm'); var anchor = require('@coral-xyz/anchor'); var coreSdk = require('@story-protocol/core-sdk'); var ccxt = require('ccxt'); var hyperliquid$1 = require('hyperliquid'); var heliusSdk = require('helius-sdk'); var tools = require('@langchain/core/tools'); function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; } function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var viemChains__namespace = /*#__PURE__*/_interopNamespace(viemChains); var winston__default = /*#__PURE__*/_interopDefault(winston); var fs__default = /*#__PURE__*/_interopDefault(fs); var path__default = /*#__PURE__*/_interopDefault(path); var os__default = /*#__PURE__*/_interopDefault(os); var bs58__default = /*#__PURE__*/_interopDefault(bs58); var axios2__default = /*#__PURE__*/_interopDefault(axios2); var DLMM__default = /*#__PURE__*/_interopDefault(DLMM); var ccxt__namespace = /*#__PURE__*/_interopNamespace(ccxt); // src/core/wallets/wallet.ts var EdwinWallet = class { }; var transports = []; if (process.env.EDWIN_MCP_MODE !== "true") { transports.push( new winston__default.default.transports.Console({ format: winston__default.default.format.combine( winston__default.default.format.colorize(), winston__default.default.format.timestamp(), winston__default.default.format.printf(({ timestamp, level, message }) => { return `${timestamp} [${level.toUpperCase()}] ${message}`; }) ) }) ); } if (process.env.EDWIN_FILE_LOGGING === "true" || process.env.EDWIN_MCP_MODE === "true") { try { const homeDir = os__default.default.homedir(); if (!homeDir || homeDir === "/") { throw new Error("Could not determine home directory for logs"); } const baseDir = path__default.default.join(homeDir, ".edwin"); const logsDir = path__default.default.join(baseDir, "logs"); const logFile = path__default.default.join(logsDir, "edwin.log"); fs__default.default.mkdirSync(baseDir, { recursive: true, mode: 493 }); fs__default.default.mkdirSync(logsDir, { recursive: true, mode: 493 }); transports.push( new winston__default.default.transports.File({ filename: logFile, format: winston__default.default.format.combine(winston__default.default.format.timestamp(), winston__default.default.format.json()), maxsize: 5242880, // 5MB maxFiles: 5, tailable: true }) ); } catch (error) { if (process.env.EDWIN_MCP_MODE === "true") { throw new Error(`Failed to set up required file logging for MCP mode: ${error}`); } console.warn("Failed to set up file logging:", error); } } var edwinLogger = winston__default.default.createLogger({ level: process.env.LOG_LEVEL || "debug", exitOnError: false, levels: winston__default.default.config.npm.levels, format: winston__default.default.format.combine( winston__default.default.format.timestamp(), winston__default.default.format.printf(({ timestamp, level, message }) => { return `${timestamp} [${level.toUpperCase()}] ${message}`; }) ), transports }); var logger_default = edwinLogger; // src/core/wallets/evm_wallet/evm_public_key_wallet.ts var erc20Abi = [ { name: "balanceOf", type: "function", inputs: [{ name: "owner", type: "address" }], outputs: [{ name: "balance", type: "uint256" }], stateMutability: "view" }, { name: "decimals", type: "function", inputs: [], outputs: [{ name: "", type: "uint8" }], stateMutability: "view" } ]; var NATIVE_ETH_ADDRESS = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; var EdwinEVMPublicKeyWallet = class _EdwinEVMPublicKeyWallet extends EdwinWallet { constructor(publicKey) { super(); this.currentChain = "mainnet"; this.chains = { ...viemChains__namespace }; this.setChains = (chains) => { if (!chains) { return; } Object.keys(chains).forEach((chain) => { this.chains[chain] = chains[chain]; }); }; this.setCurrentChain = (chain) => { this.currentChain = chain; }; this.createHttpTransport = (chainName) => { const chain = this.chains[chainName]; if (chain.rpcUrls.custom) { return viem.http(chain.rpcUrls.custom.http[0]); } return viem.http(chain.rpcUrls.default.http[0]); }; this.walletAddress = publicKey; } getAddress() { return this.walletAddress; } getCurrentChain() { return this.chains[this.currentChain]; } getPublicClient(chainName) { const transport = this.createHttpTransport(chainName); const publicClient = viem.createPublicClient({ chain: this.chains[chainName], transport }); return publicClient; } getChainConfigs(chainName) { const chain = viemChains__namespace[chainName]; if (!chain?.id) { throw new Error("Invalid chain name"); } return chain; } async getBalance() { return this.getBalanceOfWallet(this.getAddress(), this.currentChain); } async getWalletBalanceForChain(chainName) { try { const client = this.getPublicClient(chainName); if (!this.walletAddress) { throw new Error("Account not set"); } const balance = await client.getBalance({ address: this.walletAddress }); return viem.formatUnits(balance, 18); } catch (error) { logger_default.error("Error getting wallet balance:", error); return null; } } addChain(chain) { this.setChains(chain); } switchChain(chainName, customRpcUrl) { if (!this.chains[chainName]) { const chain = _EdwinEVMPublicKeyWallet.genChainFromName(chainName, customRpcUrl); this.addChain({ [chainName]: chain }); } this.setCurrentChain(chainName); } static genChainFromName(chainName, customRpcUrl) { const baseChain = viemChains__namespace[chainName]; if (!baseChain?.id) { throw new Error("Invalid chain name"); } if (!customRpcUrl) { return baseChain; } const customRpc = { http: [customRpcUrl] }; if ("webSocket" in baseChain.rpcUrls.default && baseChain.rpcUrls.default.webSocket) { customRpc.webSocket = Array.from(baseChain.rpcUrls.default.webSocket); } return { ...baseChain, rpcUrls: { ...baseChain.rpcUrls, custom: customRpc } }; } /** * Get token balance for a specific token address on a given chain * @param chainName The chain name (e.g., 'base', 'mainnet') * @param tokenAddress The token contract address or symbol * @returns Formatted token balance as a string */ async getTokenBalance(chainName, tokenAddress) { try { const balance = await this.getBalanceOfWallet(this.getAddress(), chainName, tokenAddress); return balance.toString(); } catch (error) { logger_default.error("Error getting token balance:", error); throw error; } } /** * Get balance of any wallet address on any EVM chain * @param walletAddress The wallet address to check * @param chainName The chain to query (e.g., 'mainnet', 'base', etc) * @param tokenAddress Optional ERC20 token address (if not provided, returns native token balance) * @returns Balance of the wallet */ async getBalanceOfWallet(walletAddress, chainName = this.currentChain, tokenAddress) { try { const client = this.getPublicClient(chainName); if (!tokenAddress || tokenAddress === NATIVE_ETH_ADDRESS) { const balance2 = await client.getBalance({ address: walletAddress }); return Number(viem.formatUnits(balance2, 18)); } let decimals = 18; try { decimals = await client.readContract({ address: tokenAddress, abi: erc20Abi, functionName: "decimals" }); } catch (error) { logger_default.warn(`Could not get decimals for token ${tokenAddress}, defaulting to 18. Error: ${error}`); } const balance = await client.readContract({ address: tokenAddress, abi: erc20Abi, functionName: "balanceOf", args: [walletAddress] }); if (typeof balance !== "bigint") { throw new Error(`Invalid balance returned from contract. Expected bigint, got ${typeof balance}`); } return Number(viem.formatUnits(balance, decimals)); } catch (error) { logger_default.error(`Error getting balance for wallet ${walletAddress}:`, error); throw new Error(`Failed to get balance for wallet ${walletAddress}: ${error}`); } } }; var EdwinEVMWallet = class extends EdwinEVMPublicKeyWallet { constructor(privateKey) { const account = accounts.privateKeyToAccount(privateKey); super(account.address); this.account = account; this.evmPrivateKey = privateKey; } /** * Get the wallet client for a specific chain */ getWalletClient(chainName) { const transport = this.createHttpTransport(chainName); const walletClient = viem.createWalletClient({ chain: this.chains[chainName], transport, account: this.account }); return walletClient; } /** * Get an ethers.js wallet instance */ getEthersWallet(walletClient, provider) { const ethers_wallet = new ethers.ethers.Wallet(this.evmPrivateKey, provider); return ethers_wallet; } /** * Get the account (private key) */ getSigner() { return this.account; } }; // src/utils/index.ts var INITIAL_DELAY = 1e3; async function withRetry(operation, context, maxRetries = 3) { let lastError; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await operation(); } catch (error) { lastError = error; const isTimeout = error instanceof Error && (error.message.toLowerCase().includes("timeout") || error.message.toLowerCase().includes("connectionerror")); if (!isTimeout) { throw error; } if (attempt === maxRetries) { logger_default.error(`${context} failed after ${maxRetries} attempts:`, error); throw new Error(`${context} failed after ${maxRetries} retries: ${lastError.message}`); } const delay = INITIAL_DELAY * attempt; logger_default.warn(`${context} attempt ${attempt} failed, retrying in ${delay}ms:`, error); await new Promise((resolve) => setTimeout(resolve, delay)); } } throw lastError; } // src/core/wallets/solana_wallet/base_client.ts var NATIVE_SOL_MINT = "So11111111111111111111111111111111111111112"; var BaseSolanaWalletClient = class { constructor(publicKey) { this.publicKey = typeof publicKey === "string" ? new web3_js.PublicKey(publicKey) : publicKey; } /** * Get wallet address as string */ getAddress() { return this.publicKey.toBase58(); } /** * Get Solana connection */ getConnection(customRpcUrl, commitment = "confirmed") { return new web3_js.Connection( customRpcUrl || process.env.SOLANA_RPC_URL || "https://api.mainnet-beta.solana.com", commitment ); } /** * Get token address by symbol */ async getTokenAddress(symbol) { const tokens = await new splTokenRegistry.TokenListProvider().resolve(); const tokenList = tokens.filterByChainId(101).getList(); const token = tokenList.find((t) => t.symbol.toLowerCase() === symbol.toLowerCase()); return token ? token.address : null; } /** * Get balance of the current wallet */ async getBalance(mintAddress, commitment = "confirmed") { return this.getBalanceOfWallet(this.getAddress(), mintAddress, commitment); } /** * Get balance of any wallet */ async getBalanceOfWallet(walletAddress, mintAddress, commitment = "confirmed") { try { const connection = this.getConnection(); const publicKey = new web3_js.PublicKey(walletAddress); if (!mintAddress || mintAddress === NATIVE_SOL_MINT) { return await connection.getBalance(publicKey, commitment) / web3_js.LAMPORTS_PER_SOL; } const tokenMint = new web3_js.PublicKey(mintAddress); const tokenAccounts = await connection.getTokenAccountsByOwner(publicKey, { mint: tokenMint }); if (tokenAccounts.value.length === 0) { return 0; } const tokenAccount = tokenAccounts.value[0]; const tokenAccountBalance = await connection.getTokenAccountBalance(tokenAccount.pubkey, commitment); return tokenAccountBalance.value.uiAmount || 0; } catch (error) { logger_default.error(`Error getting balance for wallet ${walletAddress}:`, error); throw new Error(`Failed to get balance for wallet ${walletAddress}: ${error}`); } } /** * Get token balance change from a transaction */ async getTransactionTokenBalanceChange(signature, mint, commitment = "confirmed") { let actualOutputAmount; const connection = this.getConnection(); const txInfo = await withRetry( () => connection.getParsedTransaction(signature, { maxSupportedTransactionVersion: 0, commitment }), "Get parsed transaction" ); if (!txInfo || !txInfo.meta) { throw new Error("Could not fetch transaction details"); } if (mint === NATIVE_SOL_MINT) { const accountKeys = txInfo.transaction.message.accountKeys; const walletIndex = accountKeys.findIndex((key) => key.pubkey.toString() === this.getAddress()); if (walletIndex === -1) { throw new Error("Wallet not found in transaction account keys"); } const preLamports = txInfo.meta.preBalances[walletIndex]; const postLamports = txInfo.meta.postBalances[walletIndex]; const fee = txInfo.meta.fee; const lamportsReceived = postLamports - preLamports + fee; actualOutputAmount = lamportsReceived / web3_js.LAMPORTS_PER_SOL; } else { const preTokenBalances = txInfo.meta.preTokenBalances || []; const postTokenBalances = txInfo.meta.postTokenBalances || []; const findBalance = (balances) => balances.find( (balance) => balance.owner && balance.mint && balance.owner === this.getAddress() && balance.mint === mint ); const preBalanceEntry = findBalance(preTokenBalances); const postBalanceEntry = findBalance(postTokenBalances); const preBalance = preBalanceEntry?.uiTokenAmount.uiAmount ?? 0; const postBalance = postBalanceEntry?.uiTokenAmount.uiAmount ?? 0; actualOutputAmount = (postBalance || 0) - (preBalance || 0); } return actualOutputAmount; } }; var JitoJsonRpcClient = class { constructor(baseUrl, uuid) { this.baseUrl = baseUrl; this.uuid = uuid; this.client = axios2__default.default.create({ headers: { "Content-Type": "application/json" } }); } async sendRequest(endpoint, method, params = []) { const url = `${this.baseUrl}${endpoint}`; const data = { jsonrpc: "2.0", id: 1, method, params }; try { const response = await this.client.post(url, data); return response.data; } catch (error) { if (axios2__default.default.isAxiosError(error)) { logger_default.error(`HTTP error: ${error.message}`); throw error; } else { logger_default.error(`Unexpected error: ${error}`); throw new Error("An unexpected error occurred"); } } } async getTipAccounts() { const endpoint = this.uuid ? `/bundles?uuid=${this.uuid}` : "/bundles"; return this.sendRequest(endpoint, "getTipAccounts"); } async getRandomTipAccount() { const tipAccountsResponse = await this.getTipAccounts(); if (tipAccountsResponse.result && Array.isArray(tipAccountsResponse.result) && tipAccountsResponse.result.length > 0) { const randomIndex = Math.floor(Math.random() * tipAccountsResponse.result.length); return tipAccountsResponse.result[randomIndex]; } throw new Error("No tip accounts available"); } async sendBundle(params) { const endpoint = this.uuid ? `/bundles?uuid=${this.uuid}` : "/bundles"; return this.sendRequest(endpoint, "sendBundle", params); } async sendTxn(params, bundleOnly = false) { let endpoint = "/transactions"; const queryParams = []; if (bundleOnly) { queryParams.push("bundleOnly=true"); } if (this.uuid) { queryParams.push(`uuid=${this.uuid}`); } if (queryParams.length > 0) { endpoint += `?${queryParams.join("&")}`; } return this.sendRequest(endpoint, "sendTransaction", params); } async getInFlightBundleStatuses(bundleIds) { const endpoint = this.uuid ? `/bundles?uuid=${this.uuid}` : "/bundles"; return this.sendRequest(endpoint, "getInflightBundleStatuses", [bundleIds]); } async getBundleStatuses(bundleIds) { const endpoint = this.uuid ? `/bundles?uuid=${this.uuid}` : "/bundles"; return this.sendRequest(endpoint, "getBundleStatuses", [bundleIds]); } async confirmInflightBundle(bundleId, timeoutMs = 6e4) { const start = Date.now(); while (Date.now() - start < timeoutMs) { try { const response = await this.getInFlightBundleStatuses([bundleId]); if (response.result?.value && Array.isArray(response.result.value) && response.result.value.length > 0) { const bundleStatus = response.result.value[0]; logger_default.info(`Bundle status: ${bundleStatus.status}, Landed slot: ${bundleStatus.landed_slot}`); if (bundleStatus.status === "Failed") { return bundleStatus; } else if (bundleStatus.status === "Landed") { const detailedStatus = await this.getBundleStatuses([bundleId]); if (detailedStatus.result?.value && Array.isArray(detailedStatus.result.value) && detailedStatus.result.value.length > 0) { return detailedStatus.result.value[0]; } return bundleStatus; } } else { logger_default.info("No status returned for the bundle. It may be invalid or very old."); } } catch (error) { logger_default.error("Error checking bundle status:", error); } await new Promise((resolve) => setTimeout(resolve, 2e3)); } logger_default.info(`Bundle ${bundleId} has not reached a final state within ${timeoutMs}ms`); return { status: "Pending" }; } }; // src/core/wallets/solana_wallet/clients/keypair/index.ts var KeypairClient = class extends BaseSolanaWalletClient { constructor(privateKey) { const keypair = web3_js.Keypair.fromSecretKey(bs58__default.default.decode(privateKey)); super(keypair.publicKey); this.keypair = keypair; } /** * Get the underlying Keypair */ getKeypair() { return this.keypair; } /** * Sign a transaction with the wallet's keypair */ async signTransaction(transaction) { if (transaction instanceof web3_js.Transaction) { transaction.partialSign(this.keypair); } else { transaction.sign([this.keypair]); } return transaction; } /** * Sign multiple transactions with the wallet's keypair */ async signAllTransactions(transactions) { for (const tx of transactions) { await this.signTransaction(tx); } return transactions; } /** * Sign a message with the wallet's keypair * Note: This is a simplified implementation for compatibility */ async signMessage(_message) { return this.keypair.secretKey.slice(0, 64); } /** * Wait for transaction confirmation */ async waitForConfirmationGracefully(connection, signature, timeout = 12e4) { const startTime = Date.now(); while (Date.now() - startTime < timeout) { const { value } = await connection.getSignatureStatus(signature, { searchTransactionHistory: true }); if (value) { if (value.err) { throw new Error(`Transaction failed: ${JSON.stringify(value.err)}`); } if (value.confirmationStatus === "confirmed" || value.confirmationStatus === "finalized") { return value; } } await new Promise((resolve) => setTimeout(resolve, 2e3)); } throw new Error("Transaction confirmation timed out"); } /** * Send a transaction using Jito's low latency transaction send API. * Supports both legacy Transaction and VersionedTransaction. */ async sendTransaction(connection, transaction, signers = []) { const isVersioned = "version" in transaction; if (isVersioned) { await this.prepareVersionedTransaction(transaction, signers); } else { await this.prepareLegacyTransaction(connection, transaction, signers); } return this.sendViaJito(transaction); } /** * Prepare a versioned transaction for sending */ async prepareVersionedTransaction(transaction, signers) { const allSigners = signers.length > 0 ? [...signers, this.keypair] : [this.keypair]; if (!transaction.signatures[0]) { transaction.sign(allSigners); } else if (signers.length > 0) { transaction.sign(signers); } } /** * Prepare a legacy transaction for sending */ async prepareLegacyTransaction(connection, transaction, signers) { const jitoClient = new JitoJsonRpcClient( process.env.JITO_RPC_URL || "https://mainnet.block-engine.jito.wtf/api/v1", process.env.JITO_UUID ); const jitoTipAccount = new web3_js.PublicKey(await jitoClient.getRandomTipAccount()); transaction.add( web3_js.SystemProgram.transfer({ fromPubkey: this.publicKey, toPubkey: jitoTipAccount, lamports: 1e3 // 0.000001 SOL tip }) ); const { blockhash } = await connection.getLatestBlockhash("finalized"); transaction.recentBlockhash = blockhash; transaction.feePayer = this.publicKey; const allSigners = [this.keypair, ...signers]; transaction.sign(...allSigners); } /** * Send transaction via Jito API */ async sendViaJito(transaction) { const serializedTx = transaction.serialize(); const base64Tx = Buffer.from(serializedTx).toString("base64"); const jitoRpcUrl = process.env.JITO_RPC_URL || "https://mainnet.block-engine.jito.wtf"; const jitoApiEndpoint = `${jitoRpcUrl}/api/v1/transactions`; const isVersioned = "version" in transaction; const sendOptions = isVersioned ? { encoding: "base64", maxRetries: 3, skipPreflight: true } : { encoding: "base64" }; const response = await fetch(jitoApiEndpoint, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "sendTransaction", params: [base64Tx, sendOptions] }) }); const data = await response.json(); if (data.error) { throw new Error(data.error.message); } return data.result; } }; // src/core/wallets/solana_wallet/clients/phantom/index.ts var PhantomClient = class extends BaseSolanaWalletClient { constructor(provider) { if (!provider.solana || !provider.solana.publicKey) { throw new Error("Phantom wallet is not connected"); } super(provider.solana.publicKey); this.provider = provider; } /** * Sign a transaction using Phantom wallet */ async signTransaction(transaction) { if (!this.provider.solana) { throw new Error("Phantom wallet is not connected"); } return this.provider.solana.signTransaction(transaction); } /** * Sign multiple transactions using Phantom wallet */ async signAllTransactions(transactions) { if (!this.provider.solana) { throw new Error("Phantom wallet is not connected"); } return this.provider.solana.signAllTransactions(transactions); } /** * Sign a message using Phantom wallet */ async signMessage(message) { if (!this.provider.solana) { throw new Error("Phantom wallet is not connected"); } const { signature } = await this.provider.solana.signMessage(message); return signature; } /** * Send a transaction using Phantom wallet * Note: In Phantom's case, we don't need Connection or additional signers * as Phantom handles the sending internally */ async sendTransaction(_connection, transaction, _signers) { if (!this.provider.solana) { throw new Error("Phantom wallet is not connected"); } return this.provider.solana.sendTransaction(transaction); } /** * Wait for transaction confirmation */ async waitForConfirmationGracefully(connection, signature, timeout = 12e4) { const startTime = Date.now(); while (Date.now() - startTime < timeout) { const { value } = await connection.getSignatureStatus(signature, { searchTransactionHistory: true }); if (value) { if (value.err) { logger_default.error(`Transaction failed: ${JSON.stringify(value.err)}`); return { err: value.err }; } if (value.confirmationStatus === "confirmed" || value.confirmationStatus === "finalized") { return value; } } await new Promise((resolve) => setTimeout(resolve, 2e3)); } const timeoutError = new Error("Transaction confirmation timed out"); logger_default.error("Transaction confirmation timed out"); return { err: timeoutError }; } }; // src/core/wallets/solana_wallet/clients/publickey/index.ts var PublicKeyClient = class extends BaseSolanaWalletClient { constructor(publicKey) { super(publicKey); } /** * Not supported in public key client - throws error */ async signTransaction(_transaction) { throw new Error("Cannot sign transactions with a read-only PublicKeyClient"); } /** * Not supported in public key client - throws error */ async signAllTransactions(_transactions) { throw new Error("Cannot sign transactions with a read-only PublicKeyClient"); } /** * Not supported in public key client - throws error */ async signMessage(_message) { throw new Error("Cannot sign messages with a read-only PublicKeyClient"); } /** * Not supported in public key client - throws error */ async sendTransaction(_connection, _transaction, _signers) { throw new Error("Cannot send transactions with a read-only PublicKeyClient"); } /** * Not supported in public key client - throws error */ async waitForConfirmationGracefully(_connection, _signature, _timeout) { throw new Error("Cannot wait for confirmation with a read-only PublicKeyClient"); } }; // src/core/wallets/solana_wallet/factory.ts var SolanaWalletFactory = { /** * Create a KeypairClient from a private key * @param privateKey Base58-encoded private key string * @returns Keypair wallet client instance */ fromPrivateKey(privateKey) { return new KeypairClient(privateKey); }, /** * Create a PublicKeyClient from a public key * @param publicKey PublicKey object or base58-encoded string * @returns PublicKey wallet client for read-only operations */ fromPublicKey(publicKey) { return new PublicKeyClient(publicKey); }, /** * Create a PhantomClient from a Phantom provider * @param provider Phantom wallet provider instance * @returns Phantom wallet client instance */ fromPhantom(provider) { return new PhantomClient(provider); } }; function canSign(client) { return !(client instanceof PublicKeyClient); } // src/core/classes/edwinPlugin.ts var EdwinPlugin = class { constructor(name, toolProviders) { this.name = name; this.toolProviders = toolProviders; this.tools = []; } getToolsArray() { return this.tools; } }; // src/core/classes/edwinToolProvider.ts var EdwinService = class { }; // src/plugins/aave/aaveService.ts var AaveService = class extends EdwinService { constructor(wallet) { super(); this.supportedChains = ["base", "baseSepolia", "sepolia", "polygon", "arbitrum"]; this.wallet = wallet; } async getPortfolio() { return ""; } getAaveChain(chain) { const matchedChain = this.supportedChains.find((c) => c.toLowerCase() === chain.toLowerCase()); if (!matchedChain) { throw new Error(`Chain ${chain} is not supported by Aave protocol`); } return matchedChain; } /** * Set up the Aave pool and necessary wallet connections */ async setupPool(chain, asset) { const aaveChain = this.getAaveChain(chain); this.wallet.switchChain(aaveChain); const walletClient = this.wallet.getWalletClient(aaveChain); const provider = new ethers.providers.JsonRpcProvider(walletClient.transport.url); const ethers_wallet = this.wallet.getEthersWallet(walletClient, provider); ethers_wallet.connect(provider); const addressBook = this.getAddressBook(aaveChain); const pool = new contractHelpers.Pool(ethers_wallet.provider, { POOL: addressBook.POOL, WETH_GATEWAY: addressBook.WETH_GATEWAY }); const assetKey = Object.keys(addressBook.ASSETS).find((key) => key.toLowerCase() === asset.toLowerCase()); if (!assetKey) { throw new Error(`Unsupported asset: ${asset}`); } if (!addressBook.ASSETS[assetKey]) { throw new Error(`Unsupported asset: ${asset}`); } const reserve = addressBook.ASSETS[assetKey].UNDERLYING; if (!reserve) { throw new Error(`Unsupported asset: ${asset}`); } return { pool, wallet: ethers_wallet, provider: ethers_wallet.provider, userAddress: walletClient.account?.address, reserveAddress: reserve }; } /** * Execute transactions and handle results */ async executeTransactions(txs, setup, actionType, amount, asset) { if (!txs || txs.length === 0) { throw new Error(`No transaction generated from Aave Pool for ${actionType}`); } logger_default.info(`Submitting ${actionType} transactions`); const txReceipts = []; for (const tx of txs) { const receipt = await this.submitTransaction(setup.provider, setup.wallet, tx); txReceipts.push(receipt); } const finalTx = txReceipts[txReceipts.length - 1]; return `Successfully ${actionType === "supply" ? "supplied" : "withdrew"} ` + amount + " " + asset + ` ${actionType === "supply" ? "to" : "from"} Aave, transaction signature: ` + finalTx.transactionHash; } getAddressBook(chain) { switch (chain.toLowerCase()) { case "base": return aaveAddressBook.AaveV3Base; case "basesepolia": return aaveAddressBook.AaveV3BaseSepolia; case "ethereum": return aaveAddressBook.AaveV3Ethereum; case "sepolia": return aaveAddressBook.AaveV3Sepolia; case "polygon": return aaveAddressBook.AaveV3Polygon; case "arbitrum": return aaveAddressBook.AaveV3Arbitrum; case "bnb": return aaveAddressBook.AaveV3BNB; default: throw new Error(`No Aave address book available for chain: ${chain}`); } } async submitTransaction(provider, wallet, tx) { try { const extendedTxData = await tx.tx(); const { from, ...txData } = extendedTxData; const txResponse = await wallet.sendTransaction(txData); const receipt = await txResponse.wait(); return receipt; } catch (error) { const aaveError = error; if (aaveError.code === "UNPREDICTABLE_GAS_LIMIT") { const reason = aaveError.error?.body ? JSON.parse(aaveError.error.body).error.message : aaveError.reason; throw new Error(`Transaction failed: ${reason}`); } throw error; } } async supply(params) { const { chain, amount, asset } = params; if (!asset) throw new Error("Asset is required"); if (!amount) throw new Error("Amount is required"); try { const setup = await this.setupPool(chain, asset); const supplyParams = { user: setup.userAddress, reserve: setup.reserveAddress, amount: String(amount) }; const txs = await setup.pool.supply(supplyParams); return await this.executeTransactions(txs, setup, "supply", amount, asset); } catch (error) { logger_default.error("Aave supply error:", error); const message = error instanceof Error ? error.message : String(error); throw new Error(`Aave supply failed: ${message}`); } } async withdraw(params) { const { chain, amount, asset } = params; if (!asset) throw new Error("Asset is required"); if (!amount) throw new Error("Amount is required"); try { const setup = await this.setupPool(chain, asset); const withdrawParams = { user: setup.userAddress, reserve: setup.reserveAddress, amount: String(amount) }; const txs = await setup.pool.withdraw(withdrawParams); return await this.executeTransactions(txs, setup, "withdraw", amount, asset); } catch (error) { logger_default.error("Aave withdraw error:", error); const message = error instanceof Error ? error.message : String(error); throw new Error(`Aave withdraw failed: ${message}`); } } }; // src/core/utils/createParameterSchema.ts function createParameterSchema(schema) { return { schema, type: {} }; } // src/plugins/aave/parameters.ts var SupplyParametersSchema = createParameterSchema( zod.z.object({ chain: zod.z.string().min(1).describe("The chain to supply assets on"), asset: zod.z.string().min(1).describe("The asset to supply"), amount: zod.z.number().positive().describe("The amount to supply") }) ); var WithdrawParametersSchema = createParameterSchema( zod.z.object({ chain: zod.z.string().min(1).describe("The chain to withdraw assets from"), asset: zod.z.string().min(1).describe("The asset to withdraw"), amount: zod.z.number().positive().describe("The amount to withdraw") }) ); // src/plugins/aave/aavePlugin.ts var AavePlugin = class extends EdwinPlugin { constructor(wallet) { super("aave", [new AaveService(wallet)]); this.supportsChain = (chain) => chain.type === "evm"; } getTools() { return { ...this.getPublicTools(), ...this.getPrivateTools() }; } getPublicTools() { return {}; } getPrivateTools() { const aaveService = this.toolProviders.find((provider) => provider instanceof AaveService); return { aaveSupply: { name: "aave_supply", description: "Supply assets to Aave protocol", schema: SupplyParametersSchema.schema, execute: async (params) => { return await aaveService.supply(params); } }, aaveWithdraw: { name: "aave_withdraw", description: "Withdraw assets from Aave protocol", schema: WithdrawParametersSchema.schema, execute: async (params) => { return await aaveService.withdraw(params); } } }; } }; var aave = (wallet) => new AavePlugin(wallet); // src/plugins/lido/lidoProtocol.ts var LidoProtocol = class extends EdwinService { constructor(wallet) { super(); this.supportedChains = ["mainnet"]; this.wallet = wallet; } async getPortfolio() { return ""; } async stake(params) { const { amount } = params; throw new Error(`Not implemented. Params: ${amount}`); } async unstake(params) { const { amount } = params; throw new Error(`Not implemented. Params: ${amount}`); } async claimRewards(params) { const { asset, amount } = params; throw new Error(`Not implemented. Params: ${asset}, ${amount}`); } }; var StakeParametersSchema = createParameterSchema( zod.z.object({ asset: zod.z.string().min(1).describe("The asset to stake"), amount: zod.z.number().positive().describe("The amount to stake") }) ); // src/plugins/lido/lidoPlugin.ts var LidoPlugin = class extends EdwinPlugin { constructor(wallet) { super("lido", [new LidoProtocol(wallet)]); this.supportsChain = (chain) => chain.type === "evm"; } getTools() { return { ...this.getPublicTools(), ...this.getPrivateTools() }; } getPublicTools() { return {}; } getPrivateTools() { const lidoProtocol = this.toolProviders.find((provider) => provider instanceof LidoProtocol); return { lidoStake: { name: "lido_stake", description: "Stake ETH in Lido", schema: StakeParametersSchema.schema, execute: async (params) => { return await lidoProtocol.stake(params); } }, lidoUnstake: { name: "lido_unstake", description: "Unstake ETH from Lido", schema: StakeParametersSchema.schema, execute: async (params) => { return await lidoProtocol.unstake(params); } }, lidoClaimRewards: { name: "lido_claim_rewards", description: "Claim staking rewards from Lido", schema: StakeParametersSchema.schema, execute: async (params) => { return await lidoProtocol.claimRewards(params); } } }; } }; var lido = (wallet) => new LidoPlugin(wallet); var LuloProtocol = class { constructor(wallet) { this.supportedChains = ["solana"]; this.wallet = wallet; } async getPortfolio() { return ""; } async supply(params) { try { if (!canSign(this.wallet)) { throw new Error("Supply operation requires a wallet with signing capabilities"); } if (!process.env.FLEXLEND_API_KEY) { throw new Error("FLEXLEND_API_KEY is not set (For lulo.fi)"); } if (!params.amount) { throw new Error("Amount is required"); } const response = await fetch(`https://api.flexlend.fi/generate/account/deposit?priorityFee=50000`, { method: "POST", headers: { "Content-Type": "application/json", "x-wallet-pubkey": this.wallet.publicKey.toBase58(), "x-api-key": process.env.FLEXLEND_API_KEY }, body: JSON.stringify({ owner: this.wallet.publicKey.toBase58(), mintAddress: params.asset, // This should be the mint address of the asset depositAmount: params.amount.toString() }) }); logger_default.info(response); const responseData = await response.json(); const transactionMeta = responseData.data.transactionMeta; const luloTxn = web3_js.VersionedTransaction.deserialize(Buffer.from(transactionMeta[0].transaction, "base64")); const connection = this.wallet.getConnection(); const { blockhash } = await connection.getLatestBlockhash(); luloTxn.message.recentBlockhash = blockhash; this.wallet.signTransaction(luloTxn); const signature = await connection.sendTransaction(luloTxn, { preflightCommitment: "confirmed", maxRetries: 3 }); const latestBlockhash = await connection.getLatestBlockhash(); await connection.confirmTransaction({ signature, blockhash: latestBlockhash.blockhash, lastValidBlockHeight: latestBlockhash.lastValidBlockHeight }); return "Successfully supplied " + params.amount + " " + params.asset + " to Lulo.fi, transaction signature: " + signature; } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"; throw new Error(`Lulo supply failed: ${errorMessage}`); } } async withdraw(params) { try { if (!canSign(this.wallet)) { throw new Error("Withdraw operation requires a wallet with signing capabilities"); } const response = await fetch( `https://blink.lulo.fi/actions/withdraw?amount=${params.amount}&symbol=${params.asset}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ account: this.wallet.publicKey.toBase58() }) } ); const data = await response.json(); return "Successfully withdrew " + params.amount + " " + params.asset + " from Lulo.fi, transaction signature: " + data.signature; } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"; throw new Error(`Lulo withdraw failed: ${errorMessage}`); } } }; var SupplyParametersSchema2 = createParameterSchema( zod.z.object({ asset: zod.z.string().min(1).describe("The asset to supply"), amount: zod.z.number().positive().describe("The amount to supply") }) ); var WithdrawParametersSchema2 = createParameterSchema( zod.z.object({ asset: zod.z.string().min(1).describe("The asset to withdraw"), amount: zod.z.number().positive().describe("The amount to withdraw") }) ); // src/plugins/lulo/luloPlugin.ts var LuloPlugin = class extends EdwinPlugin { constructor(wallet) { super("lulo", [new LuloProtocol(wallet)]); this.supportsChain = (chain) => chain.type === "solana"; } getTools() { return { ...this.getPublicTools(), ...this.getPrivateTools() }; } getPublicTools() { return {}; } getPrivateTools() { const luloProtocol = this.toolProviders.find((provider) => provider instanceof LuloProtocol); return { luloSupply: { name: "lulo_supply", description: "Supply assets to Lulo protocol", schema: SupplyParametersSchema2.schema, execute: async (params) => { return await luloProtocol.supply(params); } }, luloWithdraw: { name: "lulo_withdraw", description: "Withdraw assets from Lulo protocol", schema: WithdrawParametersSchema2.schema, execute: async (params) => { return await luloProtocol.withdraw(params); } } }; } }; var lulo = (wallet) => new LuloPlugin(wallet); async function calculateAmounts(amount, amountB, activeBinPricePerToken, dlmmPool) { let totalXAmount; let totalYAmount; const getDecimals = (token) => { if (token.mint) { return token.mint.decimals; } else if ("decimal" in token) { if (typeof token.decimal === "number") { return token.decimal; } return 0; } else { return 0; } }; const tokenXDecimals = getDecimals(dlmmPool.tokenX); const tokenYDecimals = getDecimals(dlmmPool.tokenY); if (amount === "auto" && amountB === "auto") { throw new TypeError( "Amount for both first asset and second asset cannot be 'auto' for Meteora liquidity provision" ); } else if (!amount || !amountB) { throw new TypeError("Both amounts must be specified for Meteora liquidity provision"); } if (amount === "auto") { if (!isNaN(Number(amountB))) { totalXAmount = new anchor.BN(Number(amountB) / Number(activeBinPricePerToken) * 10 ** tokenXDecimals); totalYAmount = new anchor.BN(Number(amountB) * 10 ** tokenYDecimals); } else { throw new TypeError("Invalid amountB value for second token for Meteora liquidity provision"); } } else if (amountB === "auto") { if (!isNaN(Number(amount))) { totalXAmount = new anchor.BN(Number(amount) * 10 ** tokenXDecimals); totalYAmount = new anchor.BN(Number(amount) * Number(activeBinPricePerToken) * 10 ** tokenYDecimals); } else { throw new TypeError("Invalid amount value for first token for Meteora liquidity provision"); } } else if (!isNaN(Number(amount)) && !isNaN(Number(amountB))) { totalXAmount = new anchor.BN(Number(amount) * 10 ** tokenXDecimals); totalYAmount = new anchor.BN(Number(amountB) * 10 ** tokenYDecimals); } else { throw new TypeError("Both amounts must be numbers or 'auto' for Meteora liquidity provision"); } return [totalXAmount, totalYAmount]; } async function getParsedTransactionWithRetries(connection, signature) { for (let i = 0; i < 5; i++) { const txInfo = await connection.getParsedTransaction(signature, { maxSupportedTransactionVersion: 0 }); if (txInfo) { return txInfo; } if (i < 2) { await new Promise((resolve) => setTimeout(resolve, 1e3 * (i + 1))); } } throw new Error("Failed to get parsed transaction after 3 attempts"); } async function extractBalanceChanges(connection, signature, tokenXAddress, tokenYAddress) { const METEORA_DLMM_PROGRAM_ID = "LBUZKhRxPF3XUpBCjp4YzTKgLccjZhTSDM9YuVaPwxo"; const txInfo = await getParsedTransactionWithRetries(connection, signature); if (!txInfo || !txInfo.meta) { throw new Error("Transaction details not found or not parsed"); } const outerInstructions = txInfo.transaction.message.instructions; const innerInstructions = txInfo.meta.innerInstructions || []; const innerMap = {}; for (const inner of innerInstructions) { innerMap[inner.index] = inner.instructions; } const meteoraInstructionIndices = []; outerInstructions.forEach((ix, index) => { if (ix.programId?.toString() === METEORA_DLMM_PROGRAM_ID) { meteoraInstructionIndices.push(index); } }); if (meteoraInstructionIndices.length < 2) { throw new Error("Expected at least two Meteora instructions in the transaction"); } const removeLiquidityIndex = meteoraInstructionIndices[0]; const claimFeeIndex = meteoraInstructionIndices[1]; const decodeTokenTransfers = (instructions) => { const transfers = []; for (const ix of instructions) { if (ix.program === "spl-token" && ix.parsed?.type === "transferChecked") { transfers.push(ix.parsed.info); } } return transfers; }; const removeLiquidityTransfers = innerMap[removeLiquidityIndex] ? decodeTokenTransfers(innerMap[removeLiquidityIndex]) : []; const claimFeeTransfers = innerMap[claimFeeIndex] ? decodeTokenTransfers(innerMap[claimFeeIndex]) : []; const liquidityRemovedA = removeLiquidityTransfers.find((transfer) => transfer.mint === tokenXAddress)?.tokenAmount.uiAmount || 0; const liquidityRemovedB = removeLiquidityTransfers.find((transfer) => transfer.mint === tokenYAddress)?.tokenAmount.uiAmount || 0; const feesClaimedA = claimFeeTransfers.find((transfer) => transfer.mint === tokenXAddress)?.tokenAmount.uiAmount || 0; const feesClaimedB = claimFeeTransfers.find((transfer) => transfer.mint === tokenYAddress)?.tokenAmount.uiAmount || 0; return { liquidityRemoved: [liquidityRemovedA, liquidityRemovedB], feesClaimed: [feesClaimedA, feesClaimedB] }; } async function extractAddLiquidityTokenAmounts(innerInstructions) { const tokenAmounts = []; for (const innerInstruction of innerInstructions) { if (innerInstruction.instructions) { for (const instruction of innerInstruction.instructions) { if (instruction.parsed?.type === "transferChecked") { logger_default.debug(`Transfer info amounts: ${JSON.stringify(instruction.parsed.info.tokenAmount)}`); tokenAmounts.push(instruction.parsed.info.tokenAmount); } } } } return tokenAmounts; } async function verifyAddLiquidityTokenAmounts(connection, signature) { const txInfo = await getParsedTransactionWithRetries(connection, signature); if (!txInfo || !txInfo.meta) { throw new Error("Transaction details not found or not parsed"); } const innerInstructions = txInfo.meta.innerInstructions || []; return extractAddLiquidityTokenAmounts(innerInstructions); } // src/plugins/meteora/errors.ts var MeteoraStatisticalBugError = class extends Error { constructor(message, positionAddress) { super(message); this.positionAddress = positionAddress; this.name = "MeteoraStatisticalBugError"; this.positionAddress = positionAddress; } }; // src/errors.ts var InsufficientBalanceError = class extends Error { constructor(required, available, symbol) { super(`Insufficient ${symbol} balance. Required: ${required}, Available: ${available}`); this.required = required; this.available = available; this.symbol = symbol; this.name = "InsufficientBalanceError"; } }; // src/plugins/meteora/meteoraProtocol.ts var _MeteoraProtocol = class _MeteoraProtocol { constructor(wallet) { this.wallet = wallet; } async getPortfolio() { return ""; } async getPositionInfo(positionAddress) { try { const response = await fetch(`https://dlmm-api.meteora.ag/position_v2/${positionAddress}`); if (!response.ok) { throw new Error(`Failed to fetch position info: ${response.statusText}`); } return await response.json(); } catch (error) { logger_default.error("Error fetching Meteora position info:", error); const message = error instanceof Error ? error.message : String(error); throw new Error(`Failed to get Meteora position info: ${message}`); } } async getPools(params) { const { asset, assetB } = params; const limit = 10; if (!asset || !assetB) { throw new Error("Asset A and Asset B are required for Meteora getPools"); } const response = await fetch( `${_MeteoraProtocol.BASE_URL}/pair/all_with_pagination?search_term=${asset}-${assetB}&limit=${limit}` ); const result = aw