UNPKG

@coinbase/agentkit

Version:

Coinbase AgentKit core primitives

473 lines (472 loc) 18.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getSwapAmountsFromReceipt = exports.getSwapAmountsFromLog = exports.memecoinToEthWithPermit2 = exports.ethToMemecoin = exports.getAmountWithSlippage = void 0; exports.buyFlaunchCoin = buyFlaunchCoin; const viem_1 = require("viem"); const constants_1 = require("./constants"); const network_1 = require("../../network"); const viem_2 = require("viem"); const getAmountWithSlippage = (amount, slippage, swapType) => { if (amount == null) { return 0n; } const absAmount = amount < 0n ? -amount : amount; const slippageMultiplier = swapType === "EXACT_IN" ? BigInt(1e18) - (0, viem_1.parseEther)(slippage) : BigInt(1e18) + (0, viem_1.parseEther)(slippage); return (absAmount * slippageMultiplier) / BigInt(1e18); }; exports.getAmountWithSlippage = getAmountWithSlippage; const ETH = viem_1.zeroAddress; const ethToMemecoin = (params) => { const flETH = constants_1.FLETHAddress[params.chainId]; const flETHHooks = constants_1.FLETHHooksAddress[params.chainId]; const flaunchHooks = constants_1.FlaunchPositionManagerV1_1Address[params.chainId]; // Determine actions based on swapType const v4Actions = ("0x" + (params.swapType === "EXACT_IN" ? constants_1.V4Actions.SWAP_EXACT_IN : constants_1.V4Actions.SWAP_EXACT_OUT) + constants_1.V4Actions.SETTLE_ALL + constants_1.V4Actions.TAKE_ALL); // Initialize variables for path and v4Params let path; let v4Params; // Configure path and parameters based on swapType if (params.swapType === "EXACT_IN") { if (params.amountIn == null || params.amountOutMin == null) { throw new Error("amountIn and amountOutMin are required for EXACT_IN swap"); } // Path for 'EXACT_IN' swap path = [ { intermediateCurrency: flETH, fee: 0, tickSpacing: 60, hooks: flETHHooks, hookData: "0x", }, { intermediateCurrency: params.memecoin, fee: 0, tickSpacing: 60, hooks: flaunchHooks, hookData: (0, viem_1.encodeAbiParameters)([{ type: "address", name: "referrer" }], [params.referrer ?? viem_1.zeroAddress]), }, ]; // Parameters for 'EXACT_IN' swap v4Params = (0, viem_1.encodeAbiParameters)(constants_1.IV4RouterAbiExactInput, [ { currencyIn: ETH, path: path, amountIn: params.amountIn, amountOutMinimum: params.amountOutMin, }, ]); } else { if (params.amountOut == null || params.amountInMax == null) { throw new Error("amountOut and amountInMax are required for EXACT_OUT swap"); } // Path for 'EXACT_OUT' swap path = [ { fee: 0, tickSpacing: 60, hookData: "0x", hooks: flETHHooks, intermediateCurrency: ETH, }, { fee: 0, tickSpacing: 60, hooks: flaunchHooks, intermediateCurrency: flETH, hookData: (0, viem_1.encodeAbiParameters)([{ type: "address", name: "referrer" }], [params.referrer ?? viem_1.zeroAddress]), }, ]; // Parameters for 'EXACT_OUT' swap v4Params = (0, viem_1.encodeAbiParameters)(constants_1.IV4RouterAbiExactOutput, [ { currencyOut: params.memecoin, path: path, amountOut: params.amountOut, amountInMaximum: params.amountInMax, }, ]); } // Common parameters for both swap types const settleParams = (0, viem_1.encodeAbiParameters)([ { type: "address", name: "currency", }, { type: "uint256", name: "maxAmount", }, ], [ ETH, params.swapType === "EXACT_IN" ? (params.amountIn ?? viem_1.maxUint256) : (params.amountInMax ?? viem_1.maxUint256), ]); const takeParams = (0, viem_1.encodeAbiParameters)([ { type: "address", name: "currency", }, { type: "uint256", name: "minAmount", }, ], [ params.memecoin, params.swapType === "EXACT_IN" ? (params.amountOutMin ?? viem_1.maxUint256) : (params.amountOut ?? viem_1.maxUint256), ]); // Encode router data const v4RouterData = (0, viem_1.encodeAbiParameters)([ { type: "bytes", name: "actions" }, { type: "bytes[]", name: "params" }, ], [v4Actions, [v4Params, settleParams, takeParams]]); // Commands for Universal Router const urCommands = ("0x" + constants_1.URCommands.V4_SWAP + constants_1.URCommands.SWEEP); const sweepInput = (0, viem_1.encodeAbiParameters)([ { type: "address", name: "token" }, { type: "address", name: "recipient" }, { type: "uint160", name: "amountIn" }, ], [ETH, params.sender, 0n]); // Encode calldata for Universal Router const inputs = [v4RouterData, sweepInput]; const urExecuteCalldata = (0, viem_1.encodeFunctionData)({ abi: constants_1.UNIVERSAL_ROUTER_ABI, functionName: "execute", args: [urCommands, inputs], }); return { calldata: urExecuteCalldata, commands: urCommands, inputs, }; }; exports.ethToMemecoin = ethToMemecoin; // @notice Before calling the UniversalRouter the user must have: // Given the Permit2 contract allowance to spend the memecoin const memecoinToEthWithPermit2 = (params) => { const flETH = constants_1.FLETHAddress[params.chainId]; const flETHHooks = constants_1.FLETHHooksAddress[params.chainId]; const flaunchHooks = constants_1.FlaunchPositionManagerV1_1Address[params.chainId]; const v4Actions = ("0x" + constants_1.V4Actions.SWAP_EXACT_IN + constants_1.V4Actions.SETTLE_ALL + constants_1.V4Actions.TAKE_ALL); const v4ExactInputParams = (0, viem_1.encodeAbiParameters)(constants_1.IV4RouterAbiExactInput, [ { currencyIn: params.memecoin, path: [ { intermediateCurrency: flETH, fee: 0, tickSpacing: 60, hooks: flaunchHooks, hookData: (0, viem_1.encodeAbiParameters)([ { type: "address", name: "referrer", }, ], [params.referrer ?? viem_1.zeroAddress]), }, { intermediateCurrency: ETH, fee: 0, tickSpacing: 60, hooks: flETHHooks, hookData: "0x", }, ], amountIn: params.amountIn, amountOutMinimum: params.ethOutMin, }, ]); const settleParams = (0, viem_1.encodeAbiParameters)([ { type: "address", name: "currency", }, { type: "uint256", name: "maxAmount", }, ], [params.memecoin, params.amountIn]); const takeParams = (0, viem_1.encodeAbiParameters)([ { type: "address", name: "currency", }, { type: "uint256", name: "minAmount", }, ], [ETH, params.ethOutMin]); const v4RouterData = (0, viem_1.encodeAbiParameters)([ { type: "bytes", name: "actions" }, { type: "bytes[]", name: "params" }, ], [v4Actions, [v4ExactInputParams, settleParams, takeParams]]); if (params.signature && params.permitSingle) { const urCommands = ("0x" + constants_1.URCommands.PERMIT2_PERMIT + constants_1.URCommands.V4_SWAP); const permit2PermitInput = (0, viem_1.encodeAbiParameters)([ { type: "tuple", components: [ { type: "tuple", components: [ { type: "address", name: "token" }, { type: "uint160", name: "amount" }, { type: "uint48", name: "expiration" }, { type: "uint48", name: "nonce" }, ], name: "details", }, { type: "address", name: "spender" }, { type: "uint256", name: "sigDeadline" }, ], name: "PermitSingle", }, { type: "bytes", name: "signature" }, ], [params.permitSingle, params.signature]); const inputs = [permit2PermitInput, v4RouterData]; const urExecuteCalldata = (0, viem_1.encodeFunctionData)({ abi: constants_1.UNIVERSAL_ROUTER_ABI, functionName: "execute", args: [urCommands, inputs], }); return { calldata: urExecuteCalldata, commands: urCommands, inputs, }; } else { const urCommands = ("0x" + constants_1.URCommands.V4_SWAP); const inputs = [v4RouterData]; const urExecuteCalldata = (0, viem_1.encodeFunctionData)({ abi: constants_1.UNIVERSAL_ROUTER_ABI, functionName: "execute", args: [urCommands, inputs], }); return { calldata: urExecuteCalldata, commands: urCommands, inputs, }; } }; exports.memecoinToEthWithPermit2 = memecoinToEthWithPermit2; const getSwapAmountsFromLog = ({ filteredPoolSwapEvent, coinAddress, chainId, }) => { const { flAmount0, flAmount1, flFee0, flFee1, ispAmount0, ispAmount1, ispFee0, ispFee1, uniAmount0, uniAmount1, uniFee0, uniFee1, } = filteredPoolSwapEvent; const currency0Delta = flAmount0 + ispAmount0 + uniAmount0; const currency1Delta = flAmount1 + ispAmount1 + uniAmount1; const currency0Fees = flFee0 + ispFee0 + uniFee0; const currency1Fees = flFee1 + ispFee1 + uniFee1; let feesIsInFLETH; let swapType; const flETHIsCurrencyZero = coinAddress > constants_1.FLETHAddress[chainId]; if (flETHIsCurrencyZero) { swapType = currency0Delta < 0 ? "BUY" : "SELL"; feesIsInFLETH = currency0Fees < 0; } else { swapType = currency1Delta < 0 ? "BUY" : "SELL"; feesIsInFLETH = currency1Fees < 0; } const absCurrency0Delta = currency0Delta < 0 ? -currency0Delta : currency0Delta; const absCurrency1Delta = currency1Delta < 0 ? -currency1Delta : currency1Delta; const absCurrency0Fees = currency0Fees < 0 ? -currency0Fees : currency0Fees; const absCurrency1Fees = currency1Fees < 0 ? -currency1Fees : currency1Fees; const fees = { isInFLETH: feesIsInFLETH, amount: flETHIsCurrencyZero ? feesIsInFLETH ? absCurrency0Fees : absCurrency1Fees : feesIsInFLETH ? absCurrency1Fees : absCurrency0Fees, }; if (swapType === "BUY") { return { coinsBought: flETHIsCurrencyZero ? absCurrency1Delta - (!fees.isInFLETH ? fees.amount : 0n) : absCurrency0Delta - (!fees.isInFLETH ? fees.amount : 0n), ethSold: flETHIsCurrencyZero ? absCurrency0Delta - (fees.isInFLETH ? fees.amount : 0n) : absCurrency1Delta - (fees.isInFLETH ? fees.amount : 0n), }; } else { return { coinsSold: flETHIsCurrencyZero ? absCurrency1Delta - (!fees.isInFLETH ? fees.amount : 0n) : absCurrency0Delta - (!fees.isInFLETH ? fees.amount : 0n), ethBought: flETHIsCurrencyZero ? absCurrency0Delta - (fees.isInFLETH ? fees.amount : 0n) : absCurrency1Delta - (fees.isInFLETH ? fees.amount : 0n), }; } }; exports.getSwapAmountsFromLog = getSwapAmountsFromLog; const getSwapAmountsFromReceipt = ({ receipt, coinAddress, chainId, }) => { const filteredPoolSwapEvent = receipt.logs .map(log => { try { if (log.address.toLowerCase() !== constants_1.FlaunchPositionManagerV1_1Address[chainId].toLowerCase()) { return null; } const event = (0, viem_1.decodeEventLog)({ abi: constants_1.POSITION_MANAGERV1_1_ABI, data: log.data, topics: log.topics, }); return event.eventName === "PoolSwap" ? event.args : null; } catch { return null; } }) .filter((event) => event !== null)[0]; return (0, exports.getSwapAmountsFromLog)({ filteredPoolSwapEvent, coinAddress, chainId, }); }; exports.getSwapAmountsFromReceipt = getSwapAmountsFromReceipt; /** * Buys a flaunch coin using ETH input. * * @param walletProvider - The wallet provider instance for blockchain interactions * @param coinAddress - The address of the coin to buy * @param swapType - The type of swap to perform * @param swapParams - The parameters for the swap * @param swapParams.amountIn - The amount of ETH to spend (for EXACT_IN) * @param swapParams.amountOut - The amount of coins to buy (for EXACT_OUT) * @param slippagePercent - The slippage percentage * @returns A promise that resolves to a string describing the transaction result */ async function buyFlaunchCoin(walletProvider, coinAddress, swapType, swapParams, slippagePercent) { const network = walletProvider.getNetwork(); const chainId = network.chainId; const networkId = network.networkId; if (!chainId || !networkId) { throw new Error("Chain ID is not set."); } try { let amountIn; let amountOutMin; let amountOut; let amountInMax; if (swapType === "EXACT_IN") { amountIn = (0, viem_1.parseEther)(swapParams.amountIn); const quoteResult = await walletProvider.getPublicClient().simulateContract({ address: constants_1.QuoterAddress[chainId], abi: constants_1.QUOTER_ABI, functionName: "quoteExactInput", args: [ { exactAmount: amountIn, exactCurrency: viem_1.zeroAddress, // ETH path: [ { fee: 0, tickSpacing: 60, hookData: "0x", hooks: constants_1.FLETHHooksAddress[chainId], intermediateCurrency: constants_1.FLETHAddress[chainId], }, { fee: 0, tickSpacing: 60, hooks: constants_1.FlaunchPositionManagerV1_1Address[chainId], hookData: "0x", intermediateCurrency: coinAddress, }, ], }, ], }); amountOutMin = (0, exports.getAmountWithSlippage)(quoteResult.result[0], // amountOut (slippagePercent / 100).toFixed(18).toString(), swapType); } else { // EXACT_OUT amountOut = (0, viem_1.parseEther)(swapParams.amountOut); const quoteResult = await walletProvider.getPublicClient().simulateContract({ address: constants_1.QuoterAddress[chainId], abi: constants_1.QUOTER_ABI, functionName: "quoteExactOutput", args: [ { path: [ { intermediateCurrency: viem_1.zeroAddress, fee: 0, tickSpacing: 60, hookData: "0x", hooks: constants_1.FLETHHooksAddress[chainId], }, { intermediateCurrency: constants_1.FLETHAddress[chainId], fee: 0, tickSpacing: 60, hooks: constants_1.FlaunchPositionManagerV1_1Address[chainId], hookData: "0x", }, ], exactCurrency: coinAddress, exactAmount: amountOut, }, ], }); amountInMax = (0, exports.getAmountWithSlippage)(quoteResult.result[0], // amountIn (slippagePercent / 100).toFixed(18).toString(), swapType); } const { commands, inputs } = (0, exports.ethToMemecoin)({ sender: walletProvider.getAddress(), memecoin: coinAddress, chainId: Number(chainId), referrer: viem_1.zeroAddress, swapType, amountIn, amountOutMin, amountOut, amountInMax, }); const data = (0, viem_1.encodeFunctionData)({ abi: constants_1.UNIVERSAL_ROUTER_ABI, functionName: "execute", args: [commands, inputs], }); const hash = await walletProvider.sendTransaction({ to: constants_1.UniversalRouterAddress[chainId], data, value: swapType === "EXACT_IN" ? amountIn : amountInMax, }); const receipt = await walletProvider.waitForTransactionReceipt(hash); const swapAmounts = (0, exports.getSwapAmountsFromReceipt)({ receipt, coinAddress: coinAddress, chainId: Number(chainId), }); const coinSymbol = await walletProvider.readContract({ address: coinAddress, abi: constants_1.ERC20_ABI, functionName: "symbol", }); return `Bought ${(0, viem_2.formatEther)(swapAmounts.coinsBought)} $${coinSymbol} for ${(0, viem_2.formatEther)(swapAmounts.ethSold)} ETH\n Tx hash: [${hash}](${network_1.NETWORK_ID_TO_VIEM_CHAIN[networkId].blockExplorers?.default.url}/tx/${hash})`; } catch (error) { return `Error buying coin: ${error}`; } }