UNPKG

@shogun-sdk/money-legos

Version:

Shogun Money Legos: clients and types for quotes, memes, prices, balances, fees, validations, etc.

284 lines 12.5 kB
import { SHOGUN_MULTICALL_V1_ABI } from '../config/abis/shogunMulticallV1.abi.js'; import { encodeFunctionData } from 'viem'; import { ethers } from 'ethers'; import { FourMeme } from '../services/index.js'; import { compareAddresses } from '../utils/address.js'; import { NATIVE_TOKEN } from '../config/addresses.js'; import { getQuote, getTokenData } from '../lib/index.js'; import { ExternalCallMode } from '../types/externalCall.js'; import { ExternalCallType } from '../types/externalCall.js'; import { SHOGUN_MULTICALL_V1_ADDRESS, FOUR_MEME_V2_BUYER_ADDRESS, ZERO_ADDRESS, ZERO_HASH, } from '../constants/index.js'; import { BSC_CHAIN_ID, FOUR_MEME_TOKEN_MANAGER_2_LITE_ABI, FOUR_MEME_V2_BUYER_ABI, SOLANA_CHAIN_ID, } from '../config/index.js'; import { collectAffiliateFeeBeforeSwap, formatTokenAmount } from '../utils/index.js'; import { calculateAffiliateFeeAndUpdateAmountIn } from '../utils/index.js'; export async function tryHandleFourMemeQuote(apiKey, apiUrl, params) { let fourMemeClient; try { fourMemeClient = await FourMeme.create(); } catch { return { isHandled: true, error: 'Could not fetch quote. All BSC RPC connections failed.' }; } const [isSrcTokenFourMeme, isDestTokenFourMeme] = await Promise.all([ fourMemeClient.tokenManagerHelperV3.getTokenInfo(params.srcToken), fourMemeClient.tokenManagerHelperV3.getTokenInfo(params.destToken), ]); // if none of the tokens is a FourMeme token then its not a FourMeme swap if (!isSrcTokenFourMeme?.version && !isDestTokenFourMeme?.version) { return { isHandled: false }; } if (isSrcTokenFourMeme?.version && isDestTokenFourMeme?.version) { return { isHandled: true, error: 'Could not fetch quote. Both source and destination are FourMeme tokens.' }; } const { fourMemeTokenInfo, fourMemeToken } = (isSrcTokenFourMeme?.version ? { fourMemeTokenInfo: isSrcTokenFourMeme, fourMemeToken: params.srcToken } : { fourMemeTokenInfo: isDestTokenFourMeme, fourMemeToken: params.destToken }); const tokenValidationResult = await validateFourMemeToken(fourMemeTokenInfo); if (!tokenValidationResult.isValid) { return tokenValidationResult.result; } return handleFourMemeQuote(apiKey, apiUrl, params, fourMemeClient, fourMemeToken, fourMemeTokenInfo); } async function validateFourMemeToken(tokenInfo) { // if the token is already listed on PancakeSwap, we fetch the quote from the Shogun API if (tokenInfo.liquidityAdded) { return { isValid: false, result: { isHandled: false } }; } if (tokenInfo.quote !== ethers.ZeroAddress) { return { isValid: false, result: { isHandled: true, error: 'Could not fetch quote. Quote token is not BNB.' } }; } // V1 tokens are not supported (created before September 5, 2024) if (tokenInfo.version !== BigInt(2)) { return { isValid: false, result: { isHandled: true, error: 'Could not fetch quote. V1 Four.meme tokens are not supported. Please use V2 tokens.', }, }; } return { isValid: true }; } async function handleFourMemeQuote(apiKey, apiUrl, params, fourMemeClient, fourMemeToken, fourMemeTokenInfo) { if (compareAddresses(params.destToken, fourMemeToken)) { return handleFourMemeBuyQuote(apiKey, apiUrl, params, fourMemeClient, fourMemeToken, fourMemeTokenInfo); } else { return handleFourMemeSellQuote(params, fourMemeClient, fourMemeToken, fourMemeTokenInfo); } } async function handleFourMemeBuyQuote(apiKey, apiUrl, params, fourMemeClient, tokenAddress, tokenInfo) { if (params.srcChain === BSC_CHAIN_ID && compareAddresses(params.srcToken, NATIVE_TOKEN.ETH)) { return handleFourMemeBuyQuoteWithBNBSrcToken(params, fourMemeClient, tokenAddress, tokenInfo); } else { return handleFourMemeBuyQuoteWithNonBNBSrcToken(apiKey, apiUrl, params, fourMemeClient, tokenAddress, tokenInfo); } } async function handleFourMemeBuyQuoteWithBNBSrcToken(params, fourMemeClient, tokenAddress, tokenInfo) { const tokenManager = new ethers.Contract(tokenInfo.tokenManager, FOUR_MEME_TOKEN_MANAGER_2_LITE_ABI); const calldataArray = []; let affiliateFeeData; const isAffiliateFeeDataProvided = params.affiliateFee && params.affiliateWallet; if (isAffiliateFeeDataProvided) { affiliateFeeData = calculateAffiliateFeeAndUpdateAmountIn(params); collectAffiliateFeeBeforeSwap(calldataArray, params, affiliateFeeData.formattedAffiliateFeeData); } const tryBuyResult = await fourMemeClient.tokenManagerHelperV3.tryBuy({ tokenAddress, nativeAmount: affiliateFeeData?.updatedAmountIn ?? BigInt(params.amount), }); if (!tryBuyResult) { return { isHandled: true, error: 'Could not fetch quote. Error estimating amount out.' }; } const estimatedTokenAmount = tryBuyResult.estimatedAmount; const minTokenAmountToReceive = (estimatedTokenAmount * BigInt(10000 - params.slippage * 100)) / BigInt(10000); const BNBSwapAmountWithoutFee = affiliateFeeData ? affiliateFeeData.updatedAmountIn : BigInt(params.amount); // BNB -> FM swap calldata const swapCalldata = { params: ZERO_HASH, target: tokenManager.target, msgValue: BNBSwapAmountWithoutFee, data: tokenManager.interface.encodeFunctionData('buyTokenAMAP(address,address,uint256,uint256)', [ tokenAddress, params.senderAddress, BNBSwapAmountWithoutFee, minTokenAmountToReceive, ]), }; calldataArray.push(swapCalldata); const inputTokenData = await getTokenData(params.srcToken, params.srcChain); const outputTokenData = await getTokenData(tokenAddress, params.destChain); const quote = { routes: [], inputAmount: { address: params.srcToken, decimals: inputTokenData.decimals ?? 18, name: inputTokenData.name ?? '', symbol: inputTokenData.symbol ?? '', value: params.amount, chainId: params.srcChain, }, outputAmount: { address: tokenAddress, decimals: outputTokenData.decimals ?? 18, name: outputTokenData.name ?? '', symbol: outputTokenData.symbol ?? '', value: estimatedTokenAmount.toString(), chainId: params.destChain, }, calldatas: { chainId: params.srcChain, from: params.senderAddress, value: params.amount, data: encodeFunctionData({ abi: SHOGUN_MULTICALL_V1_ABI, functionName: 'multicall', args: [calldataArray, ZERO_ADDRESS, params.senderAddress, BigInt(0)], }), to: SHOGUN_MULTICALL_V1_ADDRESS, }, ...(affiliateFeeData?.formattedAffiliateFeeData ?? { affiliateFee: {} }), }; return { isHandled: true, quote }; } async function handleFourMemeBuyQuoteWithNonBNBSrcToken(apiKey, apiUrl, params, fourMemeClient, tokenAddress, tokenInfo) { if (params.srcChain === SOLANA_CHAIN_ID) { return { isHandled: true, error: 'Could not fetch quote. Solana is not supported as source chain.' }; } const intermediateQuoteBody = { ...params, destToken: NATIVE_TOKEN.ETH, }; let intermediateQuote; try { intermediateQuote = await getQuote({ key: apiKey, url: apiUrl, }, intermediateQuoteBody); if (!intermediateQuote) { return { isHandled: true, error: 'Could not fetch quote. Error fetching quote.' }; } if (intermediateQuote.error) { return { isHandled: true, error: `Could not fetch quote. ${intermediateQuote.error}`, }; } } catch (error) { return { isHandled: true, error: `Could not fetch quote. ${error}` }; } const expectedBnbAmount = BigInt(intermediateQuote.outputAmount.value); const tryBuyResult = await fourMemeClient.tokenManagerHelperV3.tryBuy({ tokenAddress, nativeAmount: expectedBnbAmount, }); if (!tryBuyResult) { return { isHandled: true, error: 'Could not fetch quote. Error estimating amount out.' }; } const expectedAmountOut = tryBuyResult.estimatedAmount; const expectedAmountOutMin = (expectedAmountOut * (BigInt(10_000) - BigInt(params.slippage * 100))) / BigInt(10_000); const v2Buyer = new ethers.Contract(FOUR_MEME_V2_BUYER_ADDRESS, FOUR_MEME_V2_BUYER_ABI); const externalCall = { type: ExternalCallType.EVM, to: v2Buyer.target, data: v2Buyer.interface.encodeFunctionData('buyTokenWithBNB', [ tokenInfo.tokenManager, tokenAddress, params.senderAddress, expectedAmountOutMin, ]), allowFailure: params.srcChain !== params.destChain, fallbackAddress: params.senderAddress, mode: ExternalCallMode.ApproveAndCall, }; try { const finalQuote = await getQuote({ key: apiKey, url: apiUrl, }, { ...intermediateQuoteBody, externalCall: JSON.stringify(externalCall), destinationAddress: v2Buyer.target, }); if (!finalQuote) { return { isHandled: true, error: `Could not fetch quote. Error fetching quote.`, }; } if (finalQuote.error) { return { isHandled: true, error: `Could not fetch quote. ${finalQuote.error}`, }; } return { isHandled: true, quote: finalQuote }; } catch (error) { return { isHandled: true, error: `Could not fetch quote. ${error}` }; } } async function handleFourMemeSellQuote(params, fourMemeClient, tokenAddress, tokenInfo) { if (!compareAddresses(params.destToken, NATIVE_TOKEN.ETH) || params.destChain !== BSC_CHAIN_ID) { return { isHandled: true, error: 'Could not fetch quote. Destination token must be BNB.' }; } const tokenManager = new ethers.Contract(tokenInfo.tokenManager, FOUR_MEME_TOKEN_MANAGER_2_LITE_ABI); params.amount = formatTokenAmount(params.amount); const trySellResult = await fourMemeClient.tokenManagerHelperV3.trySell({ tokenAddress, amount: BigInt(params.amount), }); if (!trySellResult) { return { isHandled: true, error: 'Could not fetch quote. Error estimating amount out.' }; } const expectedAmountOut = trySellResult.funds; const expectedAmountOutMin = (expectedAmountOut * BigInt(10000 - params.slippage * 100)) / BigInt(10000); const isAffiliateFeeDataProvided = params.affiliateFee && params.affiliateWallet; const data = isAffiliateFeeDataProvided ? tokenManager.interface.encodeFunctionData('sellToken(uint256, address, uint256, uint256, uint256, address)', [ 0, tokenAddress, params.amount, expectedAmountOutMin, Number(params.affiliateFee) * 100, params.affiliateWallet, ]) : tokenManager.interface.encodeFunctionData('sellToken(uint256, address, uint256, uint256)', [ 0, tokenAddress, params.amount, expectedAmountOutMin, ]); const inputTokenData = await getTokenData(tokenAddress, params.srcChain); const outputTokenData = await getTokenData(NATIVE_TOKEN.ETH, params.destChain); const quote = { routes: [], inputAmount: { address: tokenAddress, decimals: inputTokenData.decimals ?? 18, name: inputTokenData.name ?? '', symbol: inputTokenData.symbol ?? '', value: params.amount, chainId: params.srcChain, }, outputAmount: { address: NATIVE_TOKEN.ETH, decimals: outputTokenData.decimals ?? 18, name: outputTokenData.name ?? '', symbol: outputTokenData.symbol ?? '', value: expectedAmountOut.toString(), chainId: params.destChain, }, calldatas: { chainId: params.srcChain, from: params.senderAddress, value: '0', data, to: tokenManager.target, }, }; return { isHandled: true, quote }; } //# sourceMappingURL=fourMeme.js.map