UNPKG

@funkit/fun-relay

Version:
429 lines (417 loc) 14.1 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // index.ts var index_exports = {}; __export(index_exports, { FUN_RELAY_REFERRER: () => FUN_RELAY_REFERRER, FUN_RELAY_REVENUE_WALLET: () => FUN_RELAY_REVENUE_WALLET, LogLevel: () => import_relay_sdk2.LogLevel, RELAY_TERMINAL_STATUSES: () => RELAY_TERMINAL_STATUSES, convertFunToRelayTokenAddress: () => convertFunToRelayTokenAddress, executeRelayQuote: () => executeRelayQuote, getRelayAssetPriceInfo: () => getRelayAssetPriceInfo, getRelayClient: () => getRelayClient, getRelayExecutionInfo: () => getRelayExecutionInfo, getRelayExecutionRefundState: () => getRelayExecutionRefundState, getRelayExecutionState: () => getRelayExecutionState, getRelayQuote: () => getRelayQuote, initializeRelayClient: () => initializeRelayClient, isRelayExecutionTerminalStatus: () => isRelayExecutionTerminalStatus, parseRelayFees: () => parseRelayFees }); module.exports = __toCommonJS(index_exports); // src/client.ts var import_relay_sdk = require("@reservoir0x/relay-sdk"); function initializeRelayClient({ chains, logger, ...options }) { return (0, import_relay_sdk.createClient)({ ...options, baseApiUrl: import_relay_sdk.MAINNET_RELAY_API, chains: chains.map(import_relay_sdk.convertViemChainToRelayChain), logger: (message) => { logger.info("[RelayClient]:", message); } }); } function getRelayClient() { const client = (0, import_relay_sdk.getClient)(); if (!client) { throw new Error("Relay client is not defined"); } return client; } // src/types.ts var import_relay_sdk2 = require("@reservoir0x/relay-sdk"); // src/constants.ts var FUNKIT_NATIVE_TOKEN = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"; var RELAY_NATIVE_TOKEN = "0x0000000000000000000000000000000000000000"; var FUN_RELAY_REVENUE_WALLET = "0xb61562d83aEC43a050A06BED12Ac2bD8f9BFfd5E"; var FUN_RELAY_REFERRER = "funxyz"; var RELAY_QUOTE_CHECKOUT_TIME_BUFFER_MS = 25e3; var RELAY_TERMINAL_STATUSES = [ "refund" /* REFUND */, "failure" /* FAILURE */, "success" /* SUCCESS */ ]; // src/execution.ts var import_relay_sdk3 = require("@reservoir0x/relay-sdk"); async function executeRelayQuote({ logger, onConfirmed, onError, onProgress, relayQuote, walletClient }) { await getRelayClient().actions.execute({ quote: relayQuote, wallet: walletClient, onProgress: async ({ steps, fees, breakdown, currentStep, currentStepItem, txHashes, details, error }) => { const logPrefix = "onRelayProgress"; logger.info(`${logPrefix}:params`, { steps, currentStep, fees, breakdown, currentStepItem, txHashes, details, error }); onProgress?.(currentStep ?? null); if (error) { logger.info(`${logPrefix}:errorDetected`, error); await onError(error).catch((e) => { logger.error(`${logPrefix}:onErrorFailed`, e); }); } else if ( // We can redirect as soon as all the required user steps are done // https://fun-xyz.slack.com/archives/C08MQ85QB2N/p1748381169792379 txHashes && txHashes.length > 0 && txHashes.length === steps.length ) { logger.info(`${logPrefix}:completed`, txHashes); await onConfirmed(txHashes[0].txHash).catch((e) => { logger.error(`${logPrefix}:onConfirmedFailed`, e); }); } } }); } async function getRelayExecutionInfo(requestId) { const url = `${import_relay_sdk3.MAINNET_RELAY_API}/intents/status/v2?requestId=${requestId}`; const response = await fetch(url); if (!response.ok) { throw Error(response.statusText); } const data = await response.json(); return data; } // src/fees.ts async function getFunRelayFees({ fromTokenAddress, fromChainId, toTokenAddress, toChainId, estUsdValue, clientId }) { const url = `https://frog.fun.xyz/api/fee?fromTokenAddress=${fromTokenAddress}&fromChainId=${fromChainId}&toTokenAddress=${toTokenAddress}&toChainId=${toChainId}&estUsdValue=${estUsdValue}&clientId=${clientId}`; const response = await fetch(url); if (!response.ok) { throw Error(response.statusText); } const data = await response.json(); return data; } function parseRelayFees({ fees, logger }) { const relayGasFeesUsd = Number.parseFloat(fees?.gas?.amountUsd || "0"); const funLpFeesUsd = Number.parseFloat(fees?.app?.amountUsd || "0"); const funLpFeesFromAmount = Number.parseFloat( fees?.app?.amountFormatted || "0" ); const funLpFeesFromAmountBaseUnit = BigInt(fees?.app?.amount || "0"); const relayLpFeesUsd = Number.parseFloat(fees?.relayer?.amountUsd || "0") + Number.parseFloat(fees?.relayerGas?.amountUsd || "0") + Number.parseFloat(fees?.relayerService?.amountUsd || "0"); const relayLpFeesFromAmount = Number.parseFloat(fees?.relayer?.amountFormatted || "0") + Number.parseFloat(fees?.relayerGas?.amountFormatted || "0") + Number.parseFloat(fees?.relayerService?.amountFormatted || "0"); const relayLpFeesFromAmountBaseUnit = BigInt(fees?.relayer?.amount || "0") + BigInt(fees?.relayerGas?.amount || "0") + BigInt(fees?.relayerService?.amount || "0"); const totalLpFeesUsd = funLpFeesUsd + relayLpFeesUsd; const totalLpFeesFromAmount = funLpFeesFromAmount + relayLpFeesFromAmount; const totalLpFeesFromAmountBaseUnit = funLpFeesFromAmountBaseUnit + relayLpFeesFromAmountBaseUnit; logger.info("parseRelayFees", { relayGasFeesUsd, funLpFeesUsd, funLpFeesFromAmount, funLpFeesFromAmountBaseUnit, relayLpFeesUsd, relayLpFeesFromAmount, relayLpFeesFromAmountBaseUnit, totalLpFeesUsd, totalLpFeesFromAmount, totalLpFeesFromAmountBaseUnit }); return { relayGasFeesUsd, totalLpFeesUsd, totalFeesUsd: relayGasFeesUsd + totalLpFeesUsd, totalFeesFromAmount: totalLpFeesFromAmount, totalFeesFromAmountBaseUnit: totalLpFeesFromAmountBaseUnit }; } // src/price.ts var import_relay_sdk4 = require("@reservoir0x/relay-sdk"); async function getRelayAssetPriceInfo({ chainId, address }) { const url = `${import_relay_sdk4.MAINNET_RELAY_API}/currencies/token/price?chainId=${chainId}&address=${address}`; const response = await fetch(url); if (!response.ok) { throw Error(response.statusText); } const data = await response.json(); return data; } // src/quote.ts var import_viem = require("viem"); // src/currency.ts var import_relay_sdk5 = require("@reservoir0x/relay-sdk"); async function getRelayAssetInfo({ chainId, address }) { const url = `${import_relay_sdk5.MAINNET_RELAY_API}/currencies/v2`; const body = { chainIds: [chainId], address }; const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) }); if (!response.ok) { throw Error(response.statusText); } const data = await response.json(); return data?.[0]; } // src/utils.ts function convertFunToRelayTokenAddress(address) { if (address.toLowerCase() === FUNKIT_NATIVE_TOKEN.toLowerCase()) { return RELAY_NATIVE_TOKEN; } return address; } function getRelayExecutionRefundState(info) { switch (info.status) { case "refund": return "REFUNDED" /* REFUNDED */; default: return void 0; } } function getRelayExecutionState(info) { switch (info.status) { case "success": return "COMPLETED" /* COMPLETED */; case "failure": case "refund": return "CHECKOUT_ERROR" /* CHECKOUT_ERROR */; default: return "PENDING_RECEIVAL" /* PENDING_RECEIVAL */; } } function isRelayExecutionTerminalStatus(info) { return RELAY_TERMINAL_STATUSES.includes(info.status); } // src/quote.ts async function getQuoteEstUsdValue({ tokenChainId, tokenAddress, tokenAmountBaseUnit, logger }) { try { const [tokenPriceRes, tokenInfoRes] = await Promise.all([ getRelayAssetPriceInfo({ chainId: tokenChainId, address: convertFunToRelayTokenAddress(tokenAddress) }), getRelayAssetInfo({ chainId: tokenChainId, address: convertFunToRelayTokenAddress(tokenAddress) }) ]); const price = tokenPriceRes.price; const decimals = tokenInfoRes.decimals; const absoluteAmount = BigInt(tokenAmountBaseUnit) / BigInt(10 ** decimals); return Number(price) * Number(absoluteAmount); } catch (err) { logger.error("getQuoteEstUsdValueError", { message: `Failed to get USD value for token ${tokenAddress} on chain ${tokenChainId}: ${err instanceof Error ? err.message : JSON.stringify(err)}. Falling back to 0 USD.` }); return 0; } } async function getRelayQuote({ logger, ...params }) { const { actionParams, fromChainId, fromTokenAddress, options, recipientAddress, toChainId, toTokenAddress, userAddress, clientId } = params; try { const relayClient = getRelayClient(); const txs = actionParams?.map((action) => { const data = (0, import_viem.encodeFunctionData)({ abi: action.contractAbi, args: action.functionArgs, functionName: action.functionName }); return { data, to: action.contractAddress, value: String(action.value ?? 0n) }; }); const isExactIn = params.tradeType === "EXACT_INPUT"; const queryTokenAmountBaseUnit = isExactIn ? params.fromTokenAmountBaseUnit : params.toTokenAmountBaseUnit; const queryTokenAddress = isExactIn ? fromTokenAddress : toTokenAddress; const queryTokenChainId = isExactIn ? fromChainId : toChainId; const estUsdValue = await getQuoteEstUsdValue({ tokenChainId: queryTokenChainId, tokenAddress: queryTokenAddress, tokenAmountBaseUnit: queryTokenAmountBaseUnit.toString(), logger }); const feeInfo = await getFunRelayFees({ fromTokenAddress, fromChainId, toTokenAddress, toChainId, estUsdValue: Number(estUsdValue) || 0, clientId }); const appFeeBp = feeInfo.b + feeInfo.v; logger.info("getRelayQuoteParams", { ...params, queryTokenAmountBaseUnit, txs, estUsdValue, feeInfo, appFeeBp }); const relayQuote = await relayClient.actions.getQuote({ chainId: Number(fromChainId), toChainId: Number(toChainId), currency: convertFunToRelayTokenAddress(fromTokenAddress), toCurrency: convertFunToRelayTokenAddress(toTokenAddress), amount: queryTokenAmountBaseUnit.toString(), recipient: recipientAddress, tradeType: params.tradeType, user: userAddress, ...txs && { txs }, options: { referrer: clientId ? `${FUN_RELAY_REFERRER}|${clientId}` : FUN_RELAY_REFERRER, appFees: [ { fee: appFeeBp.toString(), recipient: FUN_RELAY_REVENUE_WALLET } ], ...options } }); logger.info("relayQuote", relayQuote); const estSubtotalUsd = Number( relayQuote.details?.currencyOut?.amountUsd || 0 ); const estTotalUsd = Number(relayQuote.details?.currencyIn?.amountUsd || 0); const fromAmountString = relayQuote.details?.currencyIn?.amountFormatted || "0"; const fromAmountBaseUnitString = relayQuote.details?.currencyIn?.amount || "0"; const relayRequestId = relayQuote.steps[0]?.requestId; if (!relayRequestId) { throw new Error("Relay quote does not contain a requestId"); } if (!relayQuote.details?.currencyIn || !relayQuote.details?.currencyOut) { throw new Error("Relay quote does not contain trade details"); } const { relayGasFeesUsd, totalLpFeesUsd, totalFeesUsd, totalFeesFromAmount, totalFeesFromAmountBaseUnit } = parseRelayFees({ fees: relayQuote.fees, logger }); const estSubtotalFromAmount = (Number.parseFloat(fromAmountString) - totalFeesFromAmount).toString(); const estSubtotalFromAmountBaseUnit = (BigInt(fromAmountBaseUnitString) - totalFeesFromAmountBaseUnit).toString(); return { quoteId: relayRequestId, estCheckoutTimeMs: (relayQuote.details.timeEstimate || 0) * 1e3 + RELAY_QUOTE_CHECKOUT_TIME_BUFFER_MS, estFeesFromAmount: totalFeesFromAmount.toString(), estFeesFromAmountBaseUnit: totalFeesFromAmountBaseUnit.toString(), estFeesUsd: totalFeesUsd, estMarketMakerGasUsd: relayGasFeesUsd, estSubtotalFromAmount, estSubtotalFromAmountBaseUnit, estSubtotalUsd, estTotalFromAmount: fromAmountString, estTotalFromAmountBaseUnit: fromAmountBaseUnitString, estTotalUsd, finalToAmountBaseUnit: relayQuote.details.currencyOut.amount ?? "0", // TODO: Or do we want 'minimumAmount'? fromTokenAddress, lpFeePercentage: 0, // TODO: ? lpFeeUsd: totalLpFeesUsd, metadata: { fromAmountBaseUnit: isExactIn ? queryTokenAmountBaseUnit.toString() : relayQuote.details.currencyIn.amount ?? "0", relayQuote, toAmountBaseUnit: isExactIn ? relayQuote.details.currencyOut.amount ?? "0" : queryTokenAmountBaseUnit.toString() } }; } catch (err) { throw new Error( `An error occurred trying to generate a relay quote: ${err instanceof Error ? err.message : JSON.stringify(err)}` ); } } //# sourceMappingURL=index.js.map