@ferra-xyz/aggregator
Version:
1,416 lines (1,395 loc) • 46.2 kB
JavaScript
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