UNPKG

blub-sdk

Version:

A modular SDK for interacting with the BLUB ecosystem on the Sui blockchain.

257 lines (234 loc) 8.33 kB
/* eslint-disable @typescript-eslint/no-explicit-any */ import type { SuiMoveObject } from "@mysten/sui/client"; import { SuiAddress } from "./sui"; import { extractStructTagFromType, normalizeCoinType } from "./contract"; import { CoinAsset } from "../types"; const COIN_TYPE = "0x2::coin::Coin"; const COIN_TYPE_ARG_REGEX = /^0x2::coin::Coin<(.+)>$/; export const DEFAULT_GAS_BUDGET_FOR_SPLIT = 1000; export const DEFAULT_GAS_BUDGET_FOR_MERGE = 500; export const DEFAULT_GAS_BUDGET_FOR_TRANSFER = 100; export const DEFAULT_GAS_BUDGET_FOR_TRANSFER_SUI = 100; export const DEFAULT_GAS_BUDGET_FOR_STAKE = 1000; export const GAS_TYPE_ARG = "0x2::sui::SUI"; export const GAS_TYPE_ARG_LONG = "0x0000000000000000000000000000000000000000000000000000000000000002::sui::SUI"; export const GAS_SYMBOL = "SUI"; export const DEFAULT_NFT_TRANSFER_GAS_FEE = 450; export const SUI_SYSTEM_STATE_OBJECT_ID = "0x0000000000000000000000000000000000000005"; /** * This class provides helper methods for working with coins. */ export class CoinUtils { /**coin * 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. */ public static getCoinTypeArg(obj: SuiMoveObject) { 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. */ public static isSUI(obj: SuiMoveObject) { const arg = CoinUtils.getCoinTypeArg(obj); return arg ? CoinUtils.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. */ public static getCoinSymbol(coinTypeArg: string) { 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. */ public static getBalance(obj: SuiMoveObject): bigint { return BigInt((obj.fields as any).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. */ public static totalBalance( objs: CoinAsset[], coinAddress: SuiAddress ): bigint { 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. */ public static getID(obj: SuiMoveObject): string { return (obj.fields as any).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. */ public static getCoinTypeFromArg(coinTypeArg: string) { 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. */ public static getCoinAssets( coinType: string, allSuiObjects: CoinAsset[] ): CoinAsset[] { const coins: CoinAsset[] = []; 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. */ public static isSuiCoin(coinAddress: SuiAddress) { 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: CoinAsset[], amount: bigint, exclude: string[] = [] ): { objectArray: string[]; remainCoins: CoinAsset[]; amountArray: string[]; } { const selectedResult = CoinUtils.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: CoinAsset[], amount: bigint, exclude: string[] = [] ): { selectedCoins: CoinAsset[]; remainingCoins: CoinAsset[] } { const sortedCoins = CoinUtils.sortByBalance( coins.filter((c) => !exclude.includes(c.coinObjectId)) ); const total = CoinUtils.calculateTotalBalance(sortedCoins); if (total < amount) { return { selectedCoins: [], remainingCoins: sortedCoins }; } if (total === amount) { return { selectedCoins: sortedCoins, remainingCoins: [] }; } let sum = BigInt(0); const selectedCoins: CoinAsset[] = []; 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: CoinUtils.sortByBalance(selectedCoins), remainingCoins: CoinUtils.sortByBalance(remainingCoins), }; } /** * Sort the CoinAsset objects by their balance. * * @param coins The CoinAsset objects to sort. * @returns The sorted CoinAsset objects. */ static sortByBalance(coins: CoinAsset[]): CoinAsset[] { return coins.sort((a, b) => a.balance < b.balance ? -1 : a.balance > b.balance ? 1 : 0 ); } static sortByBalanceDes(coins: CoinAsset[]): CoinAsset[] { 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: CoinAsset[]): bigint { return coins.reduce((partialSum, c) => partialSum + c.balance, BigInt(0)); } }