UNPKG

@firefly-exchange/library-sui

Version:

Sui library housing helper methods, classes to interact with Bluefin protocol(s) deployed on Sui

263 lines (262 loc) 11.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CoinUtils = void 0; const library_1 = require("../library"); const types_1 = require("../types"); const utils_1 = require("@mysten/sui/utils"); const SuiBlocks_1 = require("./SuiBlocks"); class CoinUtils { /** * Returns all coins owned by the provided address of provided type * @param suiClient Sui Client * @param owner the owner of the coins * @param coinType The type (address::coin::Coin) of the coin * @param options optional {limit, cursor} * @returns CoinStruct */ static async getCoins(suiClient, owner, coinType) { const coins = []; let result; do { result = await suiClient.getCoins({ owner, coinType, cursor: result?.nextCursor }); coins.push(...result.data); } while (result.hasNextPage); return coins; } /** * Returns the coin balance of the provided coin type for provided address * @param suiClient Sui Client * @param owner the owner of the coins * @param currencyType The type (address::coin::Coin) of the coin * @param options optional {limit, cursor} * @returns BigNumberable sum of all coin values */ static async getCoinBalance(suiClient, owner, currencyType) { const coins = await CoinUtils.getCoins(suiClient, owner, currencyType); if (coins.length == 0) { return 0; } else { return CoinUtils.sumCoins(coins); } } /** * Returns the coins of provided type having the provided balance * @param suiClient Sui Client * @param amount the amount of coins we are looking for * @param owner The account address for which to find the coins * @param currencyType The type (address::coin::Coin) of the coin * @param options optional {limit, cursor} * @returns The coin struct of the coin and a boolean indicating if the coin has exact balance or more */ static async getCoinHavingBalance(suiClient, amount, owner, currencyType) { // get all coins of provided type const coins = await this.getCoins(suiClient, owner, currencyType); return CoinUtils.findCoinWithBalance(coins, amount); } /** * Creates a coin of provided quantity if possible * @param suiClient Sui Client * @param txb Transaction block * @param amount the amount of coins we are looking for * @param owner The account address for which to find the coins * @param coinType The type (address::coin::Coin) of the coin * @returns Txb and The new coin of provided amount */ static async createCoinWithBalance(suiClient, txb, amount, coinType, owner, options = { useGasObject: true }) { let targetCoin; let hasExactBalance = false; const amountBN = new types_1.BigNumber(amount); // if amount is zero, return zero coin if (amountBN.isEqualTo((0, library_1.bigNumber)(0))) { return [CoinUtils.zeroCoin(txb, coinType), undefined]; } // get all available coins the user has of provided type const availableCoins = CoinUtils.sortAscending(await CoinUtils.getCoins(suiClient, owner, coinType)); // sum up the balance of all coins const availableCoinsBalanceBN = new types_1.BigNumber(CoinUtils.sumCoins(availableCoins)); // if the total balance is < asked amount, throw if (amountBN.isGreaterThan(availableCoinsBalanceBN)) { throw `User: ${owner} does not have enough coins: ${coinType}`; } // if sui coin use the gas coin object if (CoinUtils.isSUI(coinType) && options.useGasObject) { return [ txb.splitCoins(txb.gas, [txb.pure.u64(amountBN.toFixed())]), undefined ]; } else { // find a coin with balance >= amount [targetCoin, hasExactBalance] = CoinUtils.findCoinWithBalance(availableCoins, amount); // if there is was no target coin found satisifying balance >= amount // then merge all coins in the first coin if (targetCoin == undefined) { // set first coin as base/target targetCoin = txb.object(availableCoins[0].coinObjectId); // merge all other coins in the first/target coin txb.mergeCoins(targetCoin, availableCoins.slice(1).map(coin => txb.object(coin.coinObjectId))); } else { targetCoin = txb.object(targetCoin.coinObjectId); } } /// If the coin has exact the balance needed, return it as the `splitCoin` and /// send the left-over coin as undefined as there is none /// If the coin has more balance than required, then split it and send the splited coin for usage /// and whatever is remaining left in targetCoin return hasExactBalance ? [targetCoin, undefined] : [ txb.splitCoins(targetCoin, [txb.pure.u64(amountBN.toFixed())]), targetCoin ]; } /** * Transfers the amount of coin type to the receiver * @param suiClient Sui Client * @param amount The amount of * @param coinType The type (address::coin::Coin) of the coin * @param receiver The address of the receiver * @param signer The sender's singer | keypair * @param isUIWallet True if being used by UI wallet * @returns SuiTransactionBlockResponse */ static async transferCoin(suiClient, amount, coinType, receiver, signer, isUIWallet = false) { const sender = signer.toSuiAddress(); let txb = new types_1.TransactionBlock(); txb = await CoinUtils.createTransferCoinTransaction(txb, suiClient, amount, coinType, receiver, sender); // sign the tx const txSignature = await SuiBlocks_1.SuiBlocks.buildAndSignTxBlock(txb, suiClient, signer, isUIWallet); return SuiBlocks_1.SuiBlocks.executeSignedTxBlock(txSignature, suiClient); } /** * Transfers all the coins of provided type * @param suiClient Sui Client * @param coinType The type (address::coin::Coin) of the coin * @param receiver The address of the receiver * @param signer The sender's singer | keypair * @param isUIWallet True if being used by UI wallet * @returns SuiTransactionBlockResponse */ static async transferAllCoins(suiClient, coinType, receiver, signer, isUIWallet = false) { const sender = signer.toSuiAddress(); let txb = new types_1.TransactionBlock(); txb = await CoinUtils.createTransferAllTransaction(txb, suiClient, coinType, receiver, sender); // sign the tx const txSignature = await SuiBlocks_1.SuiBlocks.buildAndSignTxBlock(txb, suiClient, signer, isUIWallet); return SuiBlocks_1.SuiBlocks.executeSignedTxBlock(txSignature, suiClient); } /** * Creates a transaction for transferring coin * @param txb Transaction Block * @param suiClient Sui Client * @param amount The amount of * @param coinType The type (address::coin::Coin) of the coin * @param receiver The address of the receiver * @param signer The sender's singer | keypair * @param isUIWallet True if being used by UI wallet * @returns SuiTransactionBlockResponse */ static async createTransferCoinTransaction(txb, suiClient, amount, coinType, receiver, sender) { // get the coins const [sendCoin, mergeCoin] = await CoinUtils.createCoinWithBalance(suiClient, txb, amount, coinType, sender); // transfer send coin to receiver txb.transferObjects([sendCoin], receiver); // transfer any lingering merge coin back to sender if (mergeCoin) { txb.transferObjects([mergeCoin], sender); } return txb; } /** * Creates a transaction for transferring all the coins of provided type * @param txb Transaction Block * @param suiClient Sui Client * @param coinType The type (address::coin::Coin) of the coin * @param receiver The address of the receiver * @param signer The sender's singer | keypair * @param isUIWallet True if being used by UI wallet * @returns SuiTransactionBlockResponse */ static async createTransferAllTransaction(txb, suiClient, coinType, receiver, sender) { const coins = await CoinUtils.getCoins(suiClient, sender, coinType); if (coins.length == 0) throw `No coin of type: ${coinType} available for transfer`; const mergeCoin = txb.object(coins[0].coinObjectId); // merge all other coins in the first coin txb.mergeCoins(mergeCoin, coins.slice(1).map(coin => txb.object(coin.coinObjectId))); // transfer merged coin to receiver txb.transferObjects([mergeCoin], receiver); return txb; } /** * Sums the balance of provided coins * @param coins Paginated coin array * @returns Sum of all coins */ static sumCoins(coins) { return coins.reduce( // eslint-disable-next-line @typescript-eslint/no-explicit-any (total, coin) => total + +coin.balance, 0); } /** * Finds the coin having the provided balance. If none then returns undefined * @param coins Paginated coin array * @param amount The amount of balance the coin must have * @returns CoinStruct of the coin and boolean indicating if the coin has exact balance or more */ static findCoinWithBalance(coins, amount) { for (const coin of coins) { const a = (0, library_1.bigNumber)(coin.balance); const b = (0, library_1.bigNumber)(amount); if (a.gte(b)) { return [coin, a.eq(b)]; } } return [undefined, false]; } /** * Makes a zero coin of provided type * @param txb Transaction block * @param coinType Coin Type * @returns TransactionObjectArgument */ static zeroCoin(txb, coinType) { return txb.moveCall({ target: "0x2::coin::zero", typeArguments: [coinType] }); } /** * Returns true if the provided is SUI * @param coinType * @returns true if the coin type is SUI */ static isSUI(coinType) { const normalizedAddress = (0, utils_1.normalizeSuiAddress)(coinType); return (normalizedAddress === "0x0000000000000000000000000000000000000000000000000000000000000002::sui::sui" || normalizedAddress === "0x000000000000000000000000000000000000000000000000000002::sui::sui"); } /** * Sorts the provided coin structs in ascending order * @param coins The CoinStruct to sort * @returns The sorted CoinStruct objects. */ static sortAscending(coins) { return coins.sort((a, b) => (0, library_1.bigNumber)(a.balance).lt((0, library_1.bigNumber)(b.balance)) ? -1 : (0, library_1.bigNumber)(a.balance).gt((0, library_1.bigNumber)(b.balance)) ? 1 : 0); } } exports.CoinUtils = CoinUtils;