@bluefin-exchange/bluefin7k-aggregator-sdk
Version:
145 lines (144 loc) • 5.76 kB
JavaScript
import { Transaction, } from "@mysten/sui/transactions";
import { isValidSuiAddress, toBase64, toHex } from "@mysten/sui/utils";
import { Config } from "../../config";
import { _7K_CONFIG, _7K_PACKAGE_ID, _7K_VAULT } from "../../constants/_7k";
import { getSplitCoinForTx } from "../../libs/getSplitCoinForTx";
import { groupSwapRoutes } from "../../libs/groupSwapRoutes";
import { sponsorBluefinX } from "../../libs/protocols/bluefinx/client";
import { BluefinXTx } from "../../libs/protocols/bluefinx/types";
import { swapWithRoute } from "../../libs/swapWithRoute";
import { isBluefinXRouting, } from "../../types/aggregator";
import { SuiUtils } from "../../utils/sui";
import { denormalizeTokenType } from "../../utils/token";
import { getConfig } from "./config";
export const buildTx = async ({ quoteResponse, accountAddress, slippage, commission: __commission, devInspect, extendTx, isSponsored, }) => {
const isBluefinX = isBluefinXRouting(quoteResponse);
const _commission = {
...__commission,
// commission is ignored for bluefinx
commissionBps: isBluefinX ? 0 : __commission.commissionBps,
};
const { tx: _tx, coinIn } = extendTx || {};
let coinOut;
if (isBluefinX && devInspect) {
throw new Error("BluefinX tx is sponsored, skip devInspect");
}
if (!accountAddress) {
throw new Error("Sender address is required");
}
if (!quoteResponse.routes) {
throw new Error("Invalid quote response: 'routes' are required");
}
if (!isValidSuiAddress(_commission.partner)) {
throw new Error("Invalid commission partner address");
}
const tx = _tx || new Transaction();
const routes = groupSwapRoutes(quoteResponse);
const splits = routes.map((group) => group[0]?.amount ?? "0");
let coinData;
if (coinIn) {
coinData = tx.splitCoins(coinIn, splits);
SuiUtils.transferOrDestroyZeroCoin(tx, quoteResponse.tokenIn, coinIn, accountAddress);
}
else {
const { coinData: _data } = await getSplitCoinForTx(accountAddress, quoteResponse.swapAmountWithDecimal, splits, denormalizeTokenType(quoteResponse.tokenIn), tx, devInspect, isSponsored || isBluefinX);
coinData = _data;
}
const pythMap = await updatePythPriceFeedsIfAny(tx, quoteResponse);
const coinObjects = [];
const config = await getConfig();
await Promise.all(routes.map(async (route, index) => {
const inputCoinObject = coinData[index];
const coinRes = await swapWithRoute({
route,
inputCoinObject,
currentAccount: accountAddress,
tx,
config,
pythMap,
});
if (coinRes) {
coinObjects.push(coinRes);
}
}));
if (coinObjects.length > 0) {
const mergeCoin = coinObjects.length > 1
? SuiUtils.mergeCoins(coinObjects, tx)
: coinObjects[0];
const returnAmountAfterCommission = (BigInt(10000 - _commission.commissionBps) *
BigInt(quoteResponse.returnAmountWithDecimal)) /
BigInt(10000);
const minReceived = (BigInt(1e9 - +slippage * 1e9) * BigInt(returnAmountAfterCommission)) /
BigInt(1e9);
tx.moveCall({
target: `${_7K_PACKAGE_ID}::settle::settle`,
typeArguments: [quoteResponse.tokenIn, quoteResponse.tokenOut],
arguments: [
tx.object(_7K_CONFIG),
tx.object(_7K_VAULT),
tx.pure.u64(quoteResponse.swapAmountWithDecimal),
mergeCoin,
tx.pure.u64(minReceived), // minimum received
tx.pure.u64(returnAmountAfterCommission), // expected amount out
tx.pure.option("address", isValidSuiAddress(_commission.partner) ? _commission.partner : null),
tx.pure.u64(_commission.commissionBps),
tx.pure.u64(0),
],
});
if (!extendTx) {
tx.transferObjects([mergeCoin], tx.pure.address(accountAddress));
}
else {
coinOut = mergeCoin;
}
}
if (isBluefinX) {
const extra = quoteResponse.swaps[0].extra;
if (extra.quoteExpiresAtUtcMillis < Date.now()) {
throw new Error("Quote expired");
}
tx.setSenderIfNotSet(accountAddress);
const bytes = await tx.build({
client: Config.getSuiClient(),
onlyTransactionKind: true,
});
const res = await sponsorBluefinX({
quoteId: extra.quoteId,
txBytes: toBase64(bytes),
sender: accountAddress,
});
if (!res.success) {
throw new Error("Sponsor failed");
}
return {
tx: new BluefinXTx(res.quoteId, res.data.txBytes),
coinOut,
};
}
return { tx, coinOut };
};
const getPythPriceFeeds = (res) => {
const ids = new Set();
for (const s of res.swaps) {
for (const o of s.extra?.oracles || []) {
const bytes = o.Pyth?.price_identifier?.bytes;
if (bytes) {
ids.add("0x" + toHex(Uint8Array.from(bytes)));
}
}
}
return Array.from(ids);
};
const updatePythPriceFeedsIfAny = async (tx, quoteResponse) => {
// update oracles price if any
const pythMap = {};
const pythIds = getPythPriceFeeds(quoteResponse);
if (pythIds.length > 0) {
const prices = await Config.getPythConnection().getPriceFeedsUpdateData(pythIds);
const ids = await Config.getPythClient().updatePriceFeeds(tx, prices, pythIds);
pythIds.map((id, index) => {
pythMap[id] = ids[index];
});
}
return pythMap;
};