UNPKG

sei-agent-kit

Version:

A package for building AI agents on the SEI blockchain

238 lines 10.7 kB
import { calculateOverlappingPrices, } from "@bancor/carbon-sdk/strategy-management"; import { approveToken, getTokenDecimals } from "../../utils"; import { Decimal } from "@bancor/carbon-sdk/utils"; const CARBON_NATIVE_SEI_ADDRESS = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; // Disposable/recurring defaults const DEFAULT_SPREAD_INNER = 0.01; // % const DEFAULT_SPREAD_OUTER = 0.05; // % export const UINT256_MAX = (1n << 256n) - 1n; // For infinite approvals export const carbonConfig = { carbonControllerAddress: "0xe4816658ad10bF215053C533cceAe3f59e1f1087", multiCallAddress: "0xcA11bde05977b3631167028862bE2a173976CA11", voucherAddress: "0xA4682A2A5Fe02feFF8Bd200240A41AD0E6EaF8d5", carbonBatcherAddress: "0x30dd96D6B693F78730C7C48b6849d9C44CAF39f0", }; async function fetchTokenPrice(tokenAddress) { try { const response = await fetch(`https://api.carbondefi.xyz/v1/sei/market-rate?address=${tokenAddress}&convert=usd`); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const { data } = await response.json(); if (!data || !data?.USD || isNaN(Number(data.USD))) { throw new Error("Invalid price data received from API"); } return new Decimal(data.USD); } catch (error) { throw new Error(`Failed to fetch price for token ${tokenAddress} : ${error}`); } } async function getMarketPrice(baseToken, quoteToken) { const baseTokenPrice = await fetchTokenPrice(baseToken); const quoteTokenPrice = await fetchTokenPrice(quoteToken); const marketPrice = quoteTokenPrice.eq(0) ? "0" : baseTokenPrice.div(quoteTokenPrice).toString(); return marketPrice; } async function getBuySellRange(baseToken, quoteToken, buyRange, sellRange) { if (buyRange && sellRange) { const [buyMin, buyMax] = Array.isArray(buyRange) ? buyRange : [buyRange, buyRange]; const [sellMin, sellMax] = Array.isArray(sellRange) ? sellRange : [sellRange, sellRange]; const buyRangeSorted = [buyMin, buyMax].sort((a, b) => new Decimal(a).sub(new Decimal(b)).toNumber()); const sellRangeSorted = [sellMin, sellMax].sort((a, b) => new Decimal(a).sub(new Decimal(b)).toNumber()); return { buyPriceLow: buyRangeSorted[0].toString(), buyPriceMarginal: buyRangeSorted[1].toString(), buyPriceHigh: buyRangeSorted[1].toString(), sellPriceLow: sellRangeSorted[0].toString(), sellPriceMarginal: sellRangeSorted[0].toString(), sellPriceHigh: sellRangeSorted[1].toString(), }; } // Fetch market prices if ranges are not provided try { const marketPrice = await getMarketPrice(baseToken, quoteToken); // Calculate ranges based on market price and spread const buyLow = new Decimal(marketPrice).mul(1 - DEFAULT_SPREAD_OUTER); const buyHigh = new Decimal(marketPrice).mul(1 - DEFAULT_SPREAD_INNER); const sellLow = new Decimal(marketPrice).mul(1 + DEFAULT_SPREAD_INNER); const sellHigh = new Decimal(marketPrice).mul(1 + DEFAULT_SPREAD_OUTER); return { buyPriceLow: buyLow.toString(), buyPriceMarginal: buyHigh.toString(), buyPriceHigh: buyHigh.toString(), sellPriceLow: sellLow.toString(), sellPriceMarginal: sellLow.toString(), sellPriceHigh: sellHigh.toString(), }; } catch (error) { console.error("Error fetching token prices:", error); throw new Error("Failed to calculate price ranges"); } } function validateDisposableParams(buyBudget, sellBudget, buyRange, sellRange) { if (buyBudget !== undefined && sellBudget !== undefined) { throw new Error("Disposable strategy can only have one of buyBudget or sellBudget defined"); } if (buyRange !== undefined && sellRange !== undefined) { throw new Error("Disposable strategy can only have one of buyRange or sellRange defined"); } if (buyRange === undefined && sellRange === undefined) { throw new Error("Disposable strategy must have either buyRange or sellRange defined"); } if (buyBudget === undefined && sellBudget === undefined) { throw new Error("Disposable strategy must have either buyBudget or sellBudget defined"); } if (buyBudget === undefined && buyRange !== undefined) { throw new Error("Disposable strategy has a buy budget but no buy range"); } if (sellBudget === undefined && sellRange !== undefined) { throw new Error("Disposable strategy has a sell budget but no sell range"); } } function validateRecurringParams(buyBudget, sellBudget) { if (!buyBudget && !sellBudget) { throw new Error("Recurring strategy requires at least one budget to be defined"); } } export async function getStrategyTypeParams(type, baseToken, quoteToken, buyBudget, sellBudget, buyRange, sellRange) { // Disposable strategy only has one buy or one sell order if (type === "disposable") { validateDisposableParams(buyBudget, sellBudget, buyRange, sellRange); const strategyTypeParams = await getBuySellRange(baseToken, quoteToken, buyRange ?? "0", sellRange ?? "0"); return { ...strategyTypeParams, buyBudget: buyBudget || "0", sellBudget: sellBudget || "0", }; } validateRecurringParams(buyBudget, sellBudget); const strategyTypeParams = await getBuySellRange(baseToken, quoteToken, buyRange, sellRange); return { ...strategyTypeParams, buyBudget: buyBudget || "0", sellBudget: sellBudget || "0", }; } async function getOverlappingPrices(baseToken, quoteToken, buyPriceLow, sellPriceHigh, marketPriceOverride, range) { const marketPrice = marketPriceOverride ?? (await getMarketPrice(baseToken, quoteToken)); if (buyPriceLow && sellPriceHigh) { return { buyPriceLow, sellPriceHigh, marketPrice, }; } if (!buyPriceLow && !sellPriceHigh) { const newBuyPriceLow = new Decimal(marketPrice) .mul(100 - range) .div(100) .toString(); const newSellPriceHigh = new Decimal(marketPrice) .mul(100 + range) .div(100) .toString(); return { buyPriceLow: newBuyPriceLow, sellPriceHigh: newSellPriceHigh, marketPrice, }; } const marketPriceBN = new Decimal(marketPrice); const sellPriceHighBN = new Decimal(sellPriceHigh || 0); const buyPriceLowBN = new Decimal(buyPriceLow || 0); // Only one of buyPriceLow or sellPriceHigh is undefined const newBuyPriceLow = buyPriceLow ?? marketPriceBN.sub(sellPriceHighBN.sub(marketPriceBN)).toString(); const newSellPriceHigh = sellPriceHigh ?? marketPriceBN.add(marketPriceBN.sub(buyPriceLowBN)).toString(); return { buyPriceLow: newBuyPriceLow, sellPriceHigh: newSellPriceHigh, marketPrice, }; } async function getOverlappingBudgets(carbonSDK, baseToken, quoteToken, buyPriceLow, sellPriceHigh, buyBudget, sellBudget, marketPrice, fee) { if (!buyBudget && !sellBudget) { throw new Error("Overlapping strategy requires at least one budget to be defined"); } // Overlapping is created around market price with the passed input range let overlappingBuyBudget; let overlappingSellBudget; // Calculate budgets based on which one is defined if (sellBudget) { overlappingBuyBudget = await carbonSDK.calculateOverlappingStrategyBuyBudget(baseToken, quoteToken, buyPriceLow, sellPriceHigh, marketPrice, String(fee), sellBudget); if (!buyBudget) { return { buyBudget: overlappingBuyBudget, sellBudget, }; } } if (buyBudget) { overlappingSellBudget = await carbonSDK.calculateOverlappingStrategySellBudget(baseToken, quoteToken, buyPriceLow, sellPriceHigh, marketPrice, String(fee), buyBudget); if (!sellBudget) { return { buyBudget, sellBudget: overlappingSellBudget, }; } } // If both budgets are defined, use the smaller one const overlappingSellBudgetDecimal = new Decimal(overlappingSellBudget || 0); const isBuyBudgetSmaller = overlappingSellBudgetDecimal.lt(new Decimal(sellBudget || 0)); const parsedBuyBudget = isBuyBudgetSmaller ? buyBudget : overlappingBuyBudget; const parsedSellBudget = isBuyBudgetSmaller ? overlappingSellBudget : sellBudget; return { buyBudget: parsedBuyBudget || "0", sellBudget: parsedSellBudget || "0", }; } export async function getOverlappingStrategyParams(carbonSDK, baseToken, quoteToken, buyPriceLowRaw, sellPriceHighRaw, buyBudgetRaw, sellBudgetRaw, fee, range, marketPriceOverride) { const { buyPriceLow: parsedBuyPriceLow, sellPriceHigh: parsedSellPriceHigh, marketPrice: parsedMarketPrice, } = await getOverlappingPrices(baseToken, quoteToken, buyPriceLowRaw, sellPriceHighRaw, marketPriceOverride, range); const { buyPriceLow, buyPriceHigh, buyPriceMarginal, sellPriceLow, sellPriceHigh, sellPriceMarginal, marketPrice: finalMarketPrice, } = calculateOverlappingPrices(parsedBuyPriceLow, parsedSellPriceHigh, parsedMarketPrice, String(fee)); const { buyBudget, sellBudget } = await getOverlappingBudgets(carbonSDK, baseToken, quoteToken, buyPriceLow, sellPriceHigh, buyBudgetRaw, sellBudgetRaw, finalMarketPrice, fee); return { buyPriceLow, buyPriceMarginal, buyPriceHigh, sellPriceLow, sellPriceMarginal, sellPriceHigh, buyBudget, sellBudget, }; } export const getAllTokenDecimals = async (agent, tokenAddress) => { if (tokenAddress === CARBON_NATIVE_SEI_ADDRESS) { return 18; } return getTokenDecimals(agent, tokenAddress); }; export const getCarbonTokenAddress = async (seiKit, tokenTicker) => { if (tokenTicker === "SEI") { return CARBON_NATIVE_SEI_ADDRESS; } const address = await seiKit.getTokenAddressFromTicker(tokenTicker); if (address === null) { throw new Error("Cannot find token address"); } return address; }; export const carbonERC20InfiniteApproval = (agent, tokenAddress, spender) => { if (tokenAddress !== CARBON_NATIVE_SEI_ADDRESS) { approveToken(agent, tokenAddress, spender, UINT256_MAX); } }; //# sourceMappingURL=utils.js.map