UNPKG

@ferra-xyz/aggregator

Version:
1,416 lines (1,395 loc) 46.2 kB
import { Transaction } from '@mysten/sui/transactions'; import { normalizeSuiObjectId } from '@mysten/sui/utils'; import { SuiClient } from '@mysten/sui/client'; /* AGG SDK - Dex Aggregator v0.0.1 */ var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); // src/errors/errors.ts var _AggPairsError = class _AggPairsError extends Error { constructor(message, errorCode) { super(message); this.message = message; this.errorCode = errorCode; } static isClmmpoolsErrorCode(e, code) { return e instanceof _AggPairsError && e.errorCode === code; } }; __name(_AggPairsError, "AggPairsError"); var AggPairsError = _AggPairsError; // src/modules/quoter.ts var _QuoterModule = class _QuoterModule { /** * Initialize the pair module with SDK instance * @param sdk - FerraAggregatorSDK instance */ constructor(sdk) { /** * Cache storage for pair data */ this._cache = {}; this._sdk = sdk; this.getBestQuotes = this.getBestQuotes.bind(this); } /** * Get the SDK instance * @returns FerraAggregatorSDK instance */ get sdk() { return this._sdk; } async getBestQuotes(params) { const { quoterUrl } = this._sdk.sdkOptions; if (!quoterUrl) { return []; } let apiResponse; const slippageTolerance = params.slippageTolerance === null || params.slippageTolerance === void 0 ? "" : `&slippageTolerance=${params.slippageTolerance}`; const quoteUrlApi = `${quoterUrl}?from=${params.from}&to=${params.to}&amount=${params.amount}` + slippageTolerance; try { apiResponse = await fetch(quoteUrlApi); } catch (fetchError) { throw new AggPairsError(`Failed to get pool list with liquidity from ${quoteUrlApi}.`, "InvalidQuoteUrl" /* InvalidQuoteUrl */); } let responseData; try { responseData = await apiResponse.json(); } catch (parseError) { throw new AggPairsError(`Failed to parse response from ${quoteUrlApi}.`, "InvalidQuoteUrl" /* InvalidQuoteUrl */); } if (responseData.code !== 200) { throw new AggPairsError( `Failed to get pool list from ${quoteUrlApi}. Status code is ${responseData.code}.`, "InvalidQuoteUrl" /* InvalidQuoteUrl */ ); } return responseData?.data; } }; __name(_QuoterModule, "QuoterModule"); var QuoterModule = _QuoterModule; // src/interfaces/IQuoter.ts var DexOrigins = /* @__PURE__ */ ((DexOrigins2) => { DexOrigins2["Ferra"] = "Ferra"; DexOrigins2["Cetus"] = "Cetus"; DexOrigins2["Turbos"] = "Turbos"; DexOrigins2["Navi"] = "Navi"; DexOrigins2["SuiSwap"] = "SuiSwap"; return DexOrigins2; })(DexOrigins || {}); var DexTypes = /* @__PURE__ */ ((DexTypes2) => { DexTypes2["AMM"] = "AMM"; DexTypes2["DAMM"] = "DAMM"; DexTypes2["DAMM2"] = "DAMM2"; DexTypes2["CLMM"] = "CLMM"; DexTypes2["DLMM"] = "DLMM"; return DexTypes2; })(DexTypes || {}); // src/utils/hex.ts var HEX_REGEXP = /^[-+]?[0-9A-Fa-f]+\.?[0-9A-Fa-f]*?$/; function addHexPrefix(hex) { return !hex.startsWith("0x") ? `0x${hex}` : hex; } __name(addHexPrefix, "addHexPrefix"); function removeHexPrefix(hex) { return hex.startsWith("0x") ? `${hex.slice(2)}` : hex; } __name(removeHexPrefix, "removeHexPrefix"); function shortString(str, start = 4, end = 4) { const slen = Math.max(start, 1); const elen = Math.max(end, 1); return `${str.slice(0, slen + 2)} ... ${str.slice(-elen)}`; } __name(shortString, "shortString"); function shortAddress(address, start = 4, end = 4) { return shortString(addHexPrefix(address), start, end); } __name(shortAddress, "shortAddress"); function checkAddress(address, options = { leadingZero: true }) { if (typeof address !== "string") { return false; } let str = address; if (options.leadingZero) { if (!address.startsWith("0x")) { return false; } str = str.substring(2); } return HEX_REGEXP.test(str); } __name(checkAddress, "checkAddress"); function toBuffer(v) { if (!Buffer.isBuffer(v)) { if (Array.isArray(v)) { v = Buffer.from(v); } else if (typeof v === "string") { if (exports.isHexString(v)) { v = Buffer.from(exports.padToEven(exports.stripHexPrefix(v)), "hex"); } else { v = Buffer.from(v); } } else if (typeof v === "number") { v = exports.intToBuffer(v); } else if (v === null || v === void 0) { v = Buffer.allocUnsafe(0); } else if (v.toArray) { v = Buffer.from(v.toArray()); } else { throw new AggPairsError(`Invalid type`, "InvalidType" /* InvalidType */); } } return v; } __name(toBuffer, "toBuffer"); function bufferToHex(buffer) { return addHexPrefix(toBuffer(buffer).toString("hex")); } __name(bufferToHex, "bufferToHex"); function hexToNumber(binaryData) { const buffer = new ArrayBuffer(4); const view = new DataView(buffer); for (let i = 0; i < binaryData.length; i++) { view.setUint8(i, binaryData.charCodeAt(i)); } const number = view.getUint32(0, true); return number; } __name(hexToNumber, "hexToNumber"); function utf8to16(str) { let out; let i; let c; let char2; let char3; out = ""; const len = str.length; i = 0; while (i < len) { c = str.charCodeAt(i++); switch (c >> 4) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: out += str.charAt(i - 1); break; case 12: case 13: char2 = str.charCodeAt(i++); out += String.fromCharCode((c & 31) << 6 | char2 & 63); break; case 14: char2 = str.charCodeAt(i++); char3 = str.charCodeAt(i++); out += String.fromCharCode((c & 15) << 12 | (char2 & 63) << 6 | (char3 & 63) << 0); break; } } return out; } __name(utf8to16, "utf8to16"); function hexToString(str) { let val = ""; const newStr = removeHexPrefix(str); const len = newStr.length / 2; for (let i = 0; i < len; i++) { val += String.fromCharCode(parseInt(newStr.substr(i * 2, 2), 16)); } return utf8to16(val); } __name(hexToString, "hexToString"); // src/utils/contracts.ts var EQUAL = 0; var LESS_THAN = 1; var GREATER_THAN = 2; function cmp(a, b) { if (a === b) { return EQUAL; } if (a < b) { return LESS_THAN; } return GREATER_THAN; } __name(cmp, "cmp"); function compare(symbolX, symbolY) { let i = 0; const len = symbolX.length <= symbolY.length ? symbolX.length : symbolY.length; const lenCmp = cmp(symbolX.length, symbolY.length); while (i < len) { const elemCmp = cmp(symbolX.charCodeAt(i), symbolY.charCodeAt(i)); i += 1; if (elemCmp !== 0) { return elemCmp; } } return lenCmp; } __name(compare, "compare"); function isSortedSymbols(symbolX, symbolY) { return compare(symbolX, symbolY) === LESS_THAN; } __name(isSortedSymbols, "isSortedSymbols"); function composeType(address, ...args) { const generics = Array.isArray(args[args.length - 1]) ? args.pop() : []; const chains = [address, ...args].filter(Boolean); let result = chains.join("::"); if (generics && generics.length) { result += `<${generics.join(", ")}>`; } return result; } __name(composeType, "composeType"); function extractAddressFromType(type) { return type.split("::")[0]; } __name(extractAddressFromType, "extractAddressFromType"); function extractStructTagFromType(type) { try { let _type = type.replace(/\s/g, ""); const genericsString = _type.match(/(<.+>)$/); const generics = genericsString?.[0]?.match(/(\w+::\w+::\w+)(?:<.*?>(?!>))?/g); if (generics) { _type = _type.slice(0, _type.indexOf("<")); const tag = extractStructTagFromType(_type); const structTag2 = { ...tag, type_arguments: generics.map((item) => extractStructTagFromType(item).source_address) }; structTag2.type_arguments = structTag2.type_arguments.map((item) => { return CoinAssist.isSuiCoin(item) ? item : extractStructTagFromType(item).source_address; }); structTag2.source_address = composeType(structTag2.full_address, structTag2.type_arguments); return structTag2; } const parts = _type.split("::"); const isSuiCoin = _type === GAS_TYPE_ARG || _type === GAS_TYPE_ARG_LONG; const structTag = { full_address: _type, address: isSuiCoin ? "0x2" : normalizeSuiObjectId(parts[0]), module: parts[1], name: parts[2], type_arguments: [], source_address: "" }; structTag.full_address = `${structTag.address}::${structTag.module}::${structTag.name}`; structTag.source_address = composeType(structTag.full_address, structTag.type_arguments); return structTag; } catch (error) { return { full_address: type, address: "", module: "", name: "", type_arguments: [], source_address: type }; } } __name(extractStructTagFromType, "extractStructTagFromType"); function normalizeCoinType(coinType) { return extractStructTagFromType(coinType).source_address; } __name(normalizeCoinType, "normalizeCoinType"); function fixSuiObjectId(value) { if (value.toLowerCase().startsWith("0x")) { return normalizeSuiObjectId(value); } return value; } __name(fixSuiObjectId, "fixSuiObjectId"); var fixCoinType = /* @__PURE__ */ __name((coinType, removePrefix = true) => { const arr = coinType.split("::"); const address = arr.shift(); let normalizeAddress = normalizeSuiObjectId(address); if (removePrefix) { normalizeAddress = removeHexPrefix(normalizeAddress); } return `${normalizeAddress}::${arr.join("::")}`; }, "fixCoinType"); function patchFixSuiObjectId(data) { for (const key in data) { const type = typeof data[key]; if (type === "object") { patchFixSuiObjectId(data[key]); } else if (type === "string") { const value = data[key]; if (value && !value.includes("::")) { data[key] = fixSuiObjectId(value); } } } } __name(patchFixSuiObjectId, "patchFixSuiObjectId"); // src/math/coin-assist.ts var COIN_TYPE = "0x2::coin::Coin"; var COIN_TYPE_ARG_REGEX = /^0x2::coin::Coin<(.+)>$/; var GAS_TYPE_ARG = "0x2::sui::SUI"; var GAS_TYPE_ARG_LONG = "0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI"; var _CoinAssist = class _CoinAssist { /** * Get the coin type argument from a SuiMoveObject. * * @param obj The SuiMoveObject to get the coin type argument from. * @returns The coin type argument, or null if it is not found. */ static getCoinTypeArg(obj) { const res = obj.type.match(COIN_TYPE_ARG_REGEX); return res ? res[1] : null; } /** * Get whether a SuiMoveObject is a SUI coin. * * @param obj The SuiMoveObject to check. * @returns Whether the SuiMoveObject is a SUI coin. */ static isSUI(obj) { const arg = _CoinAssist.getCoinTypeArg(obj); return arg ? _CoinAssist.getCoinSymbol(arg) === "SUI" : false; } /** * Get the coin symbol from a coin type argument. * * @param coinTypeArg The coin type argument to get the symbol from. * @returns The coin symbol. */ static getCoinSymbol(coinTypeArg) { return coinTypeArg.substring(coinTypeArg.lastIndexOf(":") + 1); } /** * Get the balance of a SuiMoveObject. * * @param obj The SuiMoveObject to get the balance from. * @returns The balance of the SuiMoveObject. */ static getBalance(obj) { return BigInt(obj.fields.balance); } /** * Get the total balance of a list of CoinAsset objects for a given coin address. * * @param objs The list of CoinAsset objects to get the total balance for. * @param coinAddress The coin address to get the total balance for. * @returns The total balance of the CoinAsset objects for the given coin address. */ static totalBalance(objs, coinAddress) { let balanceTotal = BigInt(0); objs.forEach((obj) => { if (coinAddress === obj.coinAddress) { balanceTotal += BigInt(obj.balance); } }); return balanceTotal; } /** * Get the ID of a SuiMoveObject. * * @param obj The SuiMoveObject to get the ID from. * @returns The ID of the SuiMoveObject. */ static getID(obj) { return obj.fields.id.id; } /** * Get the coin type from a coin type argument. * * @param coinTypeArg The coin type argument to get the coin type from. * @returns The coin type. */ static getCoinTypeFromArg(coinTypeArg) { return `${COIN_TYPE}<${coinTypeArg}>`; } /** * Get the CoinAsset objects for a given coin type. * * @param coinType The coin type to get the CoinAsset objects for. * @param allSuiObjects The list of all SuiMoveObjects. * @returns The CoinAsset objects for the given coin type. */ static getCoinAssets(coinType, allSuiObjects) { const coins = []; allSuiObjects.forEach((anObj) => { if (normalizeCoinType(anObj.coinAddress) === normalizeCoinType(coinType)) { coins.push(anObj); } }); return coins; } /** * Get whether a coin address is a SUI coin. * * @param coinAddress The coin address to check. * @returns Whether the coin address is a SUI coin. */ static isSuiCoin(coinAddress) { return extractStructTagFromType(coinAddress).full_address === GAS_TYPE_ARG; } /** * Select the CoinAsset objects from a list of CoinAsset objects that have a balance greater than or equal to a given amount. * * @param coins The list of CoinAsset objects to select from. * @param amount The amount to select CoinAsset objects with a balance greater than or equal to. * @param exclude A list of CoinAsset objects to exclude from the selection. * @returns The CoinAsset objects that have a balance greater than or equal to the given amount. */ static selectCoinObjectIdGreaterThanOrEqual(coins, amount, exclude = []) { const selectedResult = _CoinAssist.selectCoinAssetGreaterThanOrEqual(coins, amount, exclude); const objectArray = selectedResult.selectedCoins.map((item) => item.coinObjectId); const remainCoins = selectedResult.remainingCoins; const amountArray = selectedResult.selectedCoins.map((item) => item.balance.toString()); return { objectArray, remainCoins, amountArray }; } /** * Select the CoinAsset objects from a list of CoinAsset objects that have a balance greater than or equal to a given amount. * * @param coins The list of CoinAsset objects to select from. * @param amount The amount to select CoinAsset objects with a balance greater than or equal to. * @param exclude A list of CoinAsset objects to exclude from the selection. * @returns The CoinAsset objects that have a balance greater than or equal to the given amount. */ static selectCoinAssetGreaterThanOrEqual(coins, amount, exclude = []) { const sortedCoins = _CoinAssist.sortByBalance(coins.filter((c) => !exclude.includes(c.coinObjectId))); const total = _CoinAssist.calculateTotalBalance(sortedCoins); if (total < amount) { return { selectedCoins: [], remainingCoins: sortedCoins }; } if (total === amount) { return { selectedCoins: sortedCoins, remainingCoins: [] }; } let sum = BigInt(0); const selectedCoins = []; const remainingCoins = [...sortedCoins]; while (sum < total) { const target = amount - sum; const coinWithSmallestSufficientBalanceIndex = remainingCoins.findIndex((c) => c.balance >= target); if (coinWithSmallestSufficientBalanceIndex !== -1) { selectedCoins.push(remainingCoins[coinWithSmallestSufficientBalanceIndex]); remainingCoins.splice(coinWithSmallestSufficientBalanceIndex, 1); break; } const coinWithLargestBalance = remainingCoins.pop(); if (coinWithLargestBalance.balance > 0) { selectedCoins.push(coinWithLargestBalance); sum += coinWithLargestBalance.balance; } } return { selectedCoins: _CoinAssist.sortByBalance(selectedCoins), remainingCoins: _CoinAssist.sortByBalance(remainingCoins) }; } /** * Sort the CoinAsset objects by their balance. * * @param coins The CoinAsset objects to sort. * @returns The sorted CoinAsset objects. */ static sortByBalance(coins) { return coins.sort((a, b) => a.balance < b.balance ? -1 : a.balance > b.balance ? 1 : 0); } static sortByBalanceDes(coins) { return coins.sort((a, b) => a.balance > b.balance ? -1 : a.balance < b.balance ? 0 : 1); } /** * Calculate the total balance of a list of CoinAsset objects. * * @param coins The list of CoinAsset objects to calculate the total balance for. * @returns The total balance of the CoinAsset objects. */ static calculateTotalBalance(coins) { return coins.reduce((partialSum, c) => partialSum + c.balance, BigInt(0)); } }; __name(_CoinAssist, "CoinAssist"); var CoinAssist = _CoinAssist; // src/types/sui.ts var CLOCK_ADDRESS = "0x0000000000000000000000000000000000000000000000000000000000000006"; var AggQuoterModule = "lb_quoter"; var CoinInfoAddress = "0x1::coin::CoinInfo"; var CoinStoreAddress = "0x1::coin::CoinStore"; var getDefaultSuiInputType = /* @__PURE__ */ __name((value) => { if (typeof value === "string" && value.startsWith("0x")) { return "object"; } if (typeof value === "number" || typeof value === "bigint") { return "u64"; } if (typeof value === "boolean") { return "bool"; } throw new AggPairsError(`Unknown type for value: ${value}`, "InvalidType" /* InvalidType */); }, "getDefaultSuiInputType"); // src/utils/transaction-util.ts var _TransactionUtil = class _TransactionUtil { /** * Build transaction for swapping from CoinA to CoinB */ static buildSwapClmmFerraTransaction(tx, params) { const { packageId, globalConfig, poolId, coinTypeA, coinTypeB, amountIn, atob } = params; const [coinOut] = tx.moveCall({ target: atob ? `${packageId}::${this.FERRA_CLMM_MODULE_NAME}::swap_a2b` : `${packageId}::${this.FERRA_CLMM_MODULE_NAME}::swap_b2a`, typeArguments: [coinTypeA, coinTypeB], arguments: [ tx.object(globalConfig), tx.object(poolId), amountIn, tx.object(CLOCK_ADDRESS) ] }); return [tx, coinOut]; } static buildSwapDlmmFerraTransaction(tx, params) { const { packageId, globalConfig, pairId, coinTypeA, coinTypeB, amountIn, atob, minAmountOut = 0n } = params; const [coinOut] = tx.moveCall({ target: atob ? `${packageId}::${this.FERRA_DLMM_MODULE_NAME}::swap_a2b` : `${packageId}::${this.FERRA_DLMM_MODULE_NAME}::swap_b2a`, typeArguments: [coinTypeA, coinTypeB], arguments: [ tx.object(globalConfig), tx.object(pairId), amountIn, tx.pure.u64(minAmountOut ?? 0n), tx.object(CLOCK_ADDRESS) ] }); return [tx, coinOut]; } /** * Build a coin object with specific amount from user's coin assets * @param tx - Transaction object * @param coinAssets - Array of user's coin assets * @param coinType - Type of coin to build * @param amount - Amount needed * @returns Transaction result with coin object */ static buildCoinAmount(tx, coinAssets, coinType, amount) { if (CoinAssist.isSuiCoin(coinType)) { if (amount === BigInt(0)) { return _TransactionUtil.callMintZeroValueCoin(tx, coinType); } const [amountCoin] = tx.splitCoins(tx.gas, [tx.pure.u64(amount)]); return amountCoin; } const { targetCoin } = this.buildSpitTargeCoin(tx, amount, coinAssets, true); return targetCoin; } /** * Build a coin object by selecting and merging user's coins * @param tx - Transaction object * @param amount - Amount needed * @param coinAssets - Available coin assets * @param fixAmount - Whether to split exact amount * @returns Object with target coin and metadata */ static buildSpitTargeCoin(tx, amount, coinAssets, fixAmount) { const selectedCoinsResult = CoinAssist.selectCoinObjectIdGreaterThanOrEqual( coinAssets, amount ); const totalSelectedCoinAmount = selectedCoinsResult.amountArray.reduce((a, b) => Number(a) + Number(b), 0).toString(); const coinObjectIds = selectedCoinsResult.objectArray; const [primaryCoinA, ...mergeCoinAs] = coinObjectIds; const primaryCoinAObject = tx.object(primaryCoinA); let targetCoin = primaryCoinAObject; const tragetCoinAmount = selectedCoinsResult.amountArray.reduce((a, b) => Number(a) + Number(b), 0).toString(); let originalSplitedCoin; if (mergeCoinAs.length > 0) { tx.mergeCoins( primaryCoinAObject, mergeCoinAs.map((coin) => tx.object(coin)) ); } if (fixAmount && Number(totalSelectedCoinAmount) > Number(amount)) { targetCoin = tx.splitCoins(primaryCoinAObject, [tx.pure.u64(amount)]); originalSplitedCoin = primaryCoinAObject; } return { originalSplitedCoin, targetCoin, tragetCoinAmount, selectedCoinsResult, coinObjectIds }; } }; __name(_TransactionUtil, "TransactionUtil"); _TransactionUtil.FERRA_CLMM_MODULE_NAME = "clmm"; _TransactionUtil.FERRA_DLMM_MODULE_NAME = "dlmm"; /** * Create a zero-value coin object * @param txb - Transaction builder * @param coinType - Type of coin to create * @returns Zero-value coin object */ _TransactionUtil.callMintZeroValueCoin = /* @__PURE__ */ __name((txb, coinType) => { return txb.moveCall({ target: "0x2::coin::zero", typeArguments: [coinType] })[0]; }, "callMintZeroValueCoin"); var TransactionUtil = _TransactionUtil; // src/integrates/ferra/clmm.ts var _FerraClmmAgg = class _FerraClmmAgg { /** * Swap from CoinA to CoinB * @param params - Swap parameters * @returns Transaction object */ static swap(tx, sdk, params) { const { atob, poolId, coinTypeA, coinTypeB, amountIn } = params; const ferraClmmGlobalConfig = sdk.agg_pkg?.config?.Ferra?.clmm_global_config; if (!ferraClmmGlobalConfig) { throw new AggPairsError("Ferra CLMM Global config is not set", "InvalidConfig" /* InvalidConfig */); } return TransactionUtil.buildSwapClmmFerraTransaction(tx, { packageId: sdk.agg_pkg.package_id, globalConfig: ferraClmmGlobalConfig, poolId, coinTypeA, coinTypeB, amountIn, atob }); } }; __name(_FerraClmmAgg, "FerraClmmAgg"); var FerraClmmAgg = _FerraClmmAgg; // src/integrates/ferra/dlmm.ts var _FerraDlmmAgg = class _FerraDlmmAgg { /** * Swap from CoinA to CoinB * @param params - Swap parameters * @returns Transaction object */ static swap(tx, sdk, params) { const { minAmountOut, atob, pairId, coinTypeA, coinTypeB, amountIn } = params; const ferraDlmmGlobalConfig = sdk.agg_pkg?.config?.Ferra?.dlmm_global_config; if (!ferraDlmmGlobalConfig) { throw new AggPairsError("Ferra DLMM Global config is not set", "InvalidConfig" /* InvalidConfig */); } return TransactionUtil.buildSwapDlmmFerraTransaction(tx, { packageId: sdk.agg_pkg.package_id, globalConfig: ferraDlmmGlobalConfig, pairId, coinTypeA, coinTypeB, amountIn, atob, minAmountOut }); } }; __name(_FerraDlmmAgg, "FerraDlmmAgg"); var FerraDlmmAgg = _FerraDlmmAgg; // src/utils/cached-content.ts var cacheTime5min = 5 * 60 * 1e3; var cacheTime24h = 24 * 60 * 60 * 1e3; function getFutureTime(interval) { return Date.parse((/* @__PURE__ */ new Date()).toString()) + interval; } __name(getFutureTime, "getFutureTime"); var _CachedContent = class _CachedContent { constructor(value, overdueTime = 0) { this.overdueTime = overdueTime; this.value = value; } isValid() { if (this.value === null) { return false; } if (this.overdueTime === 0) { return true; } if (Date.parse((/* @__PURE__ */ new Date()).toString()) > this.overdueTime) { return false; } return true; } }; __name(_CachedContent, "CachedContent"); var CachedContent = _CachedContent; var _RpcModule = class _RpcModule extends SuiClient { /** * Get events for a given query criteria * @param query * @param paginationArgs * @returns */ async queryEventsByPage(query, paginationArgs = "all") { let result = []; let hasNextPage = true; const queryAll = paginationArgs === "all"; let nextCursor = queryAll ? null : paginationArgs.cursor; do { const res = await this.queryEvents({ query, cursor: nextCursor, limit: queryAll ? null : paginationArgs.limit }); if (res.data) { result = [...result, ...res.data]; hasNextPage = res.hasNextPage; nextCursor = res.nextCursor; } else { hasNextPage = false; } } while (queryAll && hasNextPage); return { data: result, nextCursor, hasNextPage }; } async queryTransactionBlocksByPage(filter, paginationArgs = "all", order = "ascending") { let result = []; let hasNextPage = true; const queryAll = paginationArgs === "all"; let nextCursor = queryAll ? null : paginationArgs.cursor; do { const res = await this.queryTransactionBlocks({ filter, cursor: nextCursor, order, limit: queryAll ? null : paginationArgs.limit, options: { showEvents: true } }); if (res.data) { result = [...result, ...res.data]; hasNextPage = res.hasNextPage; nextCursor = res.nextCursor; } else { hasNextPage = false; } } while (queryAll && hasNextPage); return { data: result, nextCursor, hasNextPage }; } /** * Get all objects owned by an address * @param owner * @param query * @param paginationArgs * @returns */ async getOwnedObjectsByPage(owner, query, paginationArgs = "all") { let result = []; let hasNextPage = true; const queryAll = paginationArgs === "all"; let nextCursor = queryAll ? null : paginationArgs.cursor; do { const res = await this.getOwnedObjects({ owner, ...query, cursor: nextCursor, limit: queryAll ? null : paginationArgs.limit }); if (res.data) { result = [...result, ...res.data]; hasNextPage = res.hasNextPage; nextCursor = res.nextCursor; } else { hasNextPage = false; } } while (queryAll && hasNextPage); return { data: result, nextCursor, hasNextPage }; } /** * Return the list of dynamic field objects owned by an object * @param parentId * @param paginationArgs * @returns */ async getDynamicFieldsByPage(parentId, paginationArgs = "all") { let result = []; let hasNextPage = true; const queryAll = paginationArgs === "all"; let nextCursor = queryAll ? null : paginationArgs.cursor; do { const res = await this.getDynamicFields({ parentId, cursor: nextCursor, limit: queryAll ? null : paginationArgs.limit }); if (res.data) { result = [...result, ...res.data]; hasNextPage = res.hasNextPage; nextCursor = res.nextCursor; } else { hasNextPage = false; } } while (queryAll && hasNextPage); return { data: result, nextCursor, hasNextPage }; } /** * Batch get details about a list of objects. If any of the object ids are duplicates the call will fail * @param ids * @param options * @param limit * @returns */ async batchGetObjects(ids, options, limit = 50) { let objectDataResponses = []; try { for (let i = 0; i < Math.ceil(ids.length / limit); i++) { const res = await this.multiGetObjects({ ids: ids.slice(i * limit, limit * (i + 1)), options }); objectDataResponses = [...objectDataResponses, ...res]; } } catch (error) { console.log(error); } return objectDataResponses; } /** * Calculates the gas cost of a transaction block. * @param {Transaction} tx - The transaction block to calculate gas for. * @returns {Promise<number>} - The estimated gas cost of the transaction block. * @throws {Error} - Throws an error if the sender is empty. */ async calculationTxGas(tx) { const { sender } = tx.blockData; if (sender === void 0) { throw Error("sdk sender is empty"); } const devResult = await this.devInspectTransactionBlock({ transactionBlock: tx, sender }); const { gasUsed } = devResult.effects; const estimateGas = Number(gasUsed.computationCost) + Number(gasUsed.storageCost) - Number(gasUsed.storageRebate); return estimateGas; } /** * Sends a transaction block after signing it with the provided keypair. * * @param {Ed25519Keypair | Secp256k1Keypair} keypair - The keypair used for signing the transaction. * @param {Transaction} tx - The transaction block to send. * @returns {Promise<SuiTransactionBlockResponse | undefined>} - The response of the sent transaction block. */ async sendTransaction(keypair, tx) { try { const resultTxn = await this.signAndExecuteTransaction({ transaction: tx, signer: keypair, options: { showEffects: true, showEvents: true } }); return resultTxn; } catch (error) { console.dir(error, { depth: null }); } return void 0; } /** * Send a simulation transaction. * @param tx - The transaction block. * @param simulationAccount - The simulation account. * @param useDevInspect - A flag indicating whether to use DevInspect. Defaults to true. * @returns A promise that resolves to DevInspectResults or undefined. */ async sendSimulationTransaction(tx, simulationAccount, useDevInspect = true) { try { if (useDevInspect) { const simulateRes = await this.devInspectTransactionBlock({ transactionBlock: tx, sender: simulationAccount }); return simulateRes; } } catch (error) { console.log("devInspectTransactionBlock error", error); } return void 0; } }; __name(_RpcModule, "RpcModule"); var RpcModule = _RpcModule; function checkInvalidSuiAddress(address) { if (!address.startsWith("0x") || address.length !== 66) { return false; } return true; } __name(checkInvalidSuiAddress, "checkInvalidSuiAddress"); var _TxBlock = class _TxBlock { constructor() { this.txBlock = new Transaction(); } /** * Transfer sui to many recipoents. * @param {string[]}recipients The recipient addresses. * @param {number[]}amounts The amounts of sui coins to be transferred. * @returns this */ transferSuiToMany(recipients, amounts) { if (recipients.length !== amounts.length) { throw new AggPairsError("The length of recipients and amounts must be the same", "InvalidRecipientAndAmountLength" /* InvalidRecipientAndAmountLength */); } for (const recipient of recipients) { if (!checkInvalidSuiAddress(recipient) === false) { throw new AggPairsError("Invalid recipient address", "InvalidRecipientAddress" /* InvalidRecipientAddress */); } } const tx = this.txBlock; const coins = tx.splitCoins( tx.gas, amounts.map((amount) => tx.pure.u64(amount)) ); recipients.forEach((recipient, index) => { tx.transferObjects([coins[index]], tx.pure.address(recipient)); }); return this; } /** * Transfer sui to one recipient. * @param {string}recipient recipient cannot be empty or invalid sui address. * @param {number}amount * @returns this */ transferSui(recipient, amount) { if (!checkInvalidSuiAddress(recipient) === false) { throw new AggPairsError("Invalid recipient address", "InvalidRecipientAddress" /* InvalidRecipientAddress */); } return this.transferSuiToMany([recipient], [amount]); } /** * Transfer coin to many recipients. * @param {string}recipient recipient cannot be empty or invalid sui address. * @param {number}amount amount cannot be empty or invalid sui address. * @param {string[]}coinObjectIds object ids of coins to be transferred. * @returns this * @deprecated use transferAndDestoryZeroCoin instead */ transferCoin(recipient, amount, coinObjectIds) { if (!checkInvalidSuiAddress(recipient) === false) { throw new AggPairsError("Invalid recipient address", "InvalidRecipientAddress" /* InvalidRecipientAddress */); } const tx = this.txBlock; const [primaryCoinA, ...mergeCoinAs] = coinObjectIds; const primaryCoinAInput = tx.object(primaryCoinA); if (mergeCoinAs.length > 0) { tx.mergeCoins( primaryCoinAInput, mergeCoinAs.map((coin) => tx.object(coin)) ); } const spitAmount = tx.splitCoins(primaryCoinAInput, [tx.pure.u64(amount)]); tx.transferObjects([spitAmount], tx.pure.address(recipient)); return this; } }; __name(_TxBlock, "TxBlock"); var TxBlock = _TxBlock; // src/modules/agg-swap.ts var _AggSwapModule = class _AggSwapModule { /** * Initialize the pair module with SDK instance * @param sdk - FerraAggregatorSDK instance */ constructor(sdk) { /** * Cache storage for pair data */ this._cache = {}; this._sdk = sdk; } /** * Get the SDK instance * @returns FerraAggregatorSDK instance */ get sdk() { return this._sdk; } async swapWithTradingRoutes(params) { let tx = new Transaction(); const sender = this.sdk.senderAddress; if (!checkInvalidSuiAddress(this.sdk.senderAddress)) { throw new AggPairsError( 'Invalid sender address: ferra agg sdk requires a valid sender address. Please set it using sdk.senderAddress = "0x..."', "InvalidSendAddress" /* InvalidSendAddress */ ); } tx.setSender(sender); tx.setSenderIfNotSet(sender); const coinTypeIn = params[0].swapStep[0].coinIn; const coinInAssets = CoinAssist.isSuiCoin(coinTypeIn) ? [] : await this.sdk.getOwnerCoinAssets(sender, coinTypeIn); let amountIns = []; let amountOuts = []; for (let routeIndex = 0; routeIndex < params.length; routeIndex++) { amountIns.push(TransactionUtil.buildCoinAmount(tx, coinInAssets, coinTypeIn, BigInt(params[routeIndex].swapStep[0].amountIn ?? 0n))); } for (let routeIndex = 0; routeIndex < params.length; routeIndex++) { const route = params[routeIndex]; let amountIn = amountIns[routeIndex]; for (let stepIndex = 0; stepIndex < route.swapStep.length; stepIndex++) { const step = route.swapStep[stepIndex]; const swapParams = { poolId: step.poolAddress, coinTypeA: step.direction ? step.coinIn : step.coinOut, coinTypeB: step.direction ? step.coinOut : step.coinIn, amountIn, atob: step.direction, dexOrigin: step.origin, dexType: step.type, minAmountOut: stepIndex === route.swapStep.length - 1 ? BigInt(route?.outputAmountMin ?? 0) : 0n }; const data = this.swap(tx, swapParams); tx = data[0]; amountIn = data[1]; if (stepIndex === route.swapStep.length - 1) { amountOuts.push(data[1]); } } } if (amountOuts.length > 1) { tx.mergeCoins(amountOuts[0], amountOuts.slice(1)); } tx.transferObjects([amountOuts[0]], tx.pure.address(sender)); return tx; } /** * Execute a swap with automatic routing * @param params - Swap parameters * @returns Transaction object */ swap(tx, params) { const { dexOrigin, dexType, atob } = params; let coinOut; try { switch (dexOrigin) { case "Ferra" /* Ferra */: [tx, coinOut] = this.switchFerraDex(tx, dexType, { poolId: params.poolId, coinTypeA: params.coinTypeA, coinTypeB: params.coinTypeB, amountIn: params.amountIn, atob }); break; case "Cetus" /* Cetus */: throw new AggPairsError("Not supported"); case "Navi" /* Navi */: throw new AggPairsError("Not supported"); case "SuiSwap" /* SuiSwap */: throw new AggPairsError("Not supported"); case "Turbos" /* Turbos */: throw new AggPairsError("Not supported"); default: throw new AggPairsError("Not supported"); } } catch (error) { throw error; } return [tx, coinOut]; } switchFerraDex(tx, dexType, swapParams) { try { let coinOut; switch (dexType) { case "CLMM" /* CLMM */: [tx, coinOut] = FerraClmmAgg.swap(tx, this.sdk.sdkOptions, swapParams); break; case "DLMM" /* DLMM */: [tx, coinOut] = FerraDlmmAgg.swap(tx, this.sdk.sdkOptions, { ...swapParams, pairId: swapParams.poolId }); break; default: throw new AggPairsError("Not supported"); } return [tx, coinOut]; } catch (error) { throw error; } } }; __name(_AggSwapModule, "AggSwapModule"); var AggSwapModule = _AggSwapModule; // src/sdk.ts var _FerraAggregatorSDK = class _FerraAggregatorSDK { constructor(options) { this._cache = {}; /** * After connecting the wallet, set the current wallet address to senderAddress. */ this._senderAddress = ""; this._sdkOptions = options; this._rpcModule = new RpcModule({ url: options.fullRpcUrl }); this._quoter = new QuoterModule(this); this._aggSwap = new AggSwapModule(this); patchFixSuiObjectId(this._sdkOptions); } /** * Getter for the sender address property. * @returns {SuiAddressType} The sender address. */ get senderAddress() { return this._senderAddress; } /** * Setter for the sender address property. * @param {string} value - The new sender address value. */ set senderAddress(value) { this._senderAddress = value; } /** * Getter for the fullClient property. * @returns {RpcModule} The fullClient property value. */ get fullClient() { return this._rpcModule; } /** * Getter for the sdkOptions property. * @returns {SdkOptions} The sdkOptions property value. */ get sdkOptions() { return this._sdkOptions; } get Quoter() { return this._quoter; } get AggSwap() { return this._aggSwap; } /** * Gets all coin assets for the given owner and coin type. * * @param suiAddress The address of the owner. * @param coinType The type of the coin. * @returns an array of coin assets. */ async getOwnerCoinAssets(suiAddress, coinType, forceRefresh = true) { const allCoinAsset = []; let nextCursor = null; const cacheKey = `${this.sdkOptions.fullRpcUrl}_${suiAddress}_${coinType}_getOwnerCoinAssets`; const cacheData = this.getCache(cacheKey, forceRefresh); if (cacheData) { return cacheData; } while (true) { const allCoinObject = await (coinType ? this.fullClient.getCoins({ owner: suiAddress, coinType, cursor: nextCursor }) : this.fullClient.getAllCoins({ owner: suiAddress, cursor: nextCursor })); allCoinObject.data.forEach((coin) => { if (BigInt(coin.balance) > 0) { allCoinAsset.push({ coinAddress: extractStructTagFromType(coin.coinType).source_address, coinObjectId: coin.coinObjectId, balance: BigInt(coin.balance) }); } }); nextCursor = allCoinObject.nextCursor; if (!allCoinObject.hasNextPage) { break; } } this.updateCache(cacheKey, allCoinAsset, 30 * 1e3); return allCoinAsset; } /** * Gets all coin balances for the given owner and coin type. * * @param suiAddress The address of the owner. * @param coinType The type of the coin. * @returns an array of coin balances. */ async getOwnerCoinBalances(suiAddress, coinType) { let allCoinBalance = []; if (coinType) { const res = await this.fullClient.getBalance({ owner: suiAddress, coinType }); allCoinBalance = [res]; } else { const res = await this.fullClient.getAllBalances({ owner: suiAddress }); allCoinBalance = [...res]; } return allCoinBalance; } /** * Updates the cache for the given key. * * @param key The key of the cache entry to update. * @param data The data to store in the cache. * @param time The time in minutes after which the cache entry should expire. */ updateCache(key, data, time = cacheTime24h) { let cacheData = this._cache[key]; if (cacheData) { cacheData.overdueTime = getFutureTime(time); cacheData.value = data; } else { cacheData = new CachedContent(data, getFutureTime(time)); } this._cache[key] = cacheData; } /** * Gets the cache entry for the given key. * * @param key The key of the cache entry to get. * @param forceRefresh Whether to force a refresh of the cache entry. * @returns The cache entry for the given key, or undefined if the cache entry does not exist or is expired. */ getCache(key, forceRefresh = false) { const cacheData = this._cache[key]; const isValid = cacheData?.isValid(); if (!forceRefresh && isValid) { return cacheData.value; } if (!isValid) { delete this._cache[key]; } return void 0; } }; __name(_FerraAggregatorSDK, "FerraAggregatorSDK"); var FerraAggregatorSDK = _FerraAggregatorSDK; // src/main.ts var main_default = FerraAggregatorSDK; // src/config/mainnet.ts var SDKConfig = { aggConfig: { Ferra: { clmm_global_config: "0x62f3f95bc1d68c4a92712d344bd7699d7babec3e17ab2a2fbc96dd5a5f968906", dlmm_global_config: "0xf28811a2b2fe8838129df1fc0b825057972436777bf0e45bda3d82c8d7c49850" } } }; var aggMainnet = { fullRpcUrl: "https://mainnet.suiet.app:443", simulationAccount: { address: "0x0000000000000000000000000000000000000000000000000000000000000000" }, agg_pkg: { package_id: "0xeeb25fb858c258ddf37488cfecb31ff3db2b2228aecdad5b5ebdeefb99f8a714", published_at: "0xeeb25fb858c258ddf37488cfecb31ff3db2b2228aecdad5b5ebdeefb99f8a714", config: SDKConfig.aggConfig }, quoterUrl: "https://agg-beta.ferra.xyz/agg/quote" }; function initMainnetSDK(fullNodeUrl, wallet) { if (fullNodeUrl) { aggMainnet.fullRpcUrl = fullNodeUrl; } const sdk = new main_default(aggMainnet); if (wallet && checkInvalidSuiAddress(wallet)) { sdk.senderAddress = wallet; } return sdk; } __name(initMainnetSDK, "initMainnetSDK"); // src/config/testnet.ts var SDKConfig2 = { aggConfig: { Ferra: { clmm_global_config: "0x0f21705a8a674ce564b8e320c7bfccae236b763370786d504f7631af6425ff62", dlmm_global_config: "0x3ae130485253c7cefc9e328275f03b5ee516bc5a6246b6ef4f9dcff126144fb1" } } }; var aggTestnet = { fullRpcUrl: "https://mainnet.suiet.app:443", simulationAccount: { address: "0x0000000000000000000000000000000000000000000000000000000000000000" }, agg_pkg: { package_id: "0xaa71601f6306104290d75002dc3da41e0daf972cc18f66557a8a5bba7e89a261", published_at: "0xaa71601f6306104290d75002dc3da41e0daf972cc18f66557a8a5bba7e89a261", config: SDKConfig2.aggConfig }, quoterUrl: "https://api-dev.ferra.xyz/agg/quote" }; function initTestnetSDK(fullNodeUrl, wallet) { if (fullNodeUrl) { aggTestnet.fullRpcUrl = fullNodeUrl; } const sdk = new main_default(aggTestnet); if (wallet && checkInvalidSuiAddress(wallet)) { sdk.senderAddress = wallet; } return sdk; } __name(initTestnetSDK, "initTestnetSDK"); // src/config/beta.ts var SDKConfig3 = { aggConfig: { Ferra: { clmm_global_config: "0xf95e1634845d71c56dcfcea3c96cef4c81ee2451b2e058a85ed763d81f06abf4", dlmm_global_config: "0x152b5df4367cdccd5fb266f5baebb39a25991244d8ba0b30175a324c5d7ef3f9" } } }; var aggBeta = { fullRpcUrl: "https://mainnet.suiet.app:443", simulationAccount: { address: "0x0000000000000000000000000000000000000000000000000000000000000000" }, agg_pkg: { package_id: "0x923fd3d2080f6fd2366b8dc615b2425bec1cda2ed0c0c523ed6de129926b19f6", published_at: "0x923fd3d2080f6fd2366b8dc615b2425bec1cda2ed0c0c523ed6de129926b19f6", config: SDKConfig3.aggConfig }, quoterUrl: "https://agg-beta.ferra.xyz/agg/quote" }; function initBetaSDK(fullNodeUrl, wallet) { if (fullNodeUrl) { aggBeta.fullRpcUrl = fullNodeUrl; } const sdk = new main_default(aggBeta); if (wallet && checkInvalidSuiAddress(wallet)) { sdk.senderAddress = wallet; } return sdk; } __name(initBetaSDK, "initBetaSDK"); // src/config/config.ts function initFerraSDK(options) { const { network, fullNodeUrl, wallet } = options; switch (network) { case "mainnet": return initMainnetSDK(fullNodeUrl, wallet); case "testnet": return initTestnetSDK(fullNodeUrl, wallet); case "beta": return initBetaSDK(fullNodeUrl, wallet); } return initTestnetSDK(fullNodeUrl, wallet); } __name(initFerraSDK, "initFerraSDK"); function initFerraAggregatorSDK(options) { return initFerraSDK(options); } __name(initFerraAggregatorSDK, "initFerraAggregatorSDK"); // src/index.ts var src_default = FerraAggregatorSDK; export { AggQuoterModule, AggSwapModule, CLOCK_ADDRESS, CachedContent, CoinInfoAddress, CoinStoreAddress, DexOrigins, DexTypes, FerraAggregatorSDK, QuoterModule, RpcModule, TransactionUtil, TxBlock, addHexPrefix, aggBeta, aggMainnet, aggTestnet, bufferToHex, cacheTime24h, cacheTime5min, checkAddress, checkInvalidSuiAddress, composeType, src_default as default, extractAddressFromType, extractStructTagFromType, fixCoinType, fixSuiObjectId, getDefaultSuiInputType, getFutureTime, hexToNumber, hexToString, initBetaSDK, initFerraAggregatorSDK, initFerraSDK, initMainnetSDK, initTestnetSDK, isSortedSymbols, normalizeCoinType, patchFixSuiObjectId, removeHexPrefix, shortAddress, shortString, toBuffer, utf8to16 }; //# sourceMappingURL=out.js.map //# sourceMappingURL=index.mjs.map