@coinbase/agentkit
Version:
Coinbase AgentKit core primitives
473 lines (472 loc) • 18.9 kB
JavaScript
"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}`;
}
}