UNPKG

@layerzerolabs/lz-sui-sdk-v2

Version:

900 lines (818 loc) 33.8 kB
import { CoinStruct, SuiClient } from '@mysten/sui/client' import { Transaction, TransactionArgument, TransactionResult } from '@mysten/sui/transactions' import { asAddress, asBool, asBytes, asBytes32, asObject, asU128, asU16, asU256, asU32, asU64, asU8 } from '../utils' const BYTES32_MODULE_NAME = 'bytes32' const BUFFER_READER_MODULE_NAME = 'buffer_reader' const BUFFER_WRITER_MODULE_NAME = 'buffer_writer' const PACKAGE_MODULE_NAME = 'package' export const UtilsErrorCode = { // Utils related errors (with Utils_ prefix) Utils_EInvalidLength: 1, } as const export class Utils { public packageId: string public readonly client: SuiClient constructor(packageId: string, client: SuiClient) { this.packageId = packageId this.client = client } // === bytes32 Functions === /** * Create a bytes32 from a byte array * @param tx - The transaction to add the move call to * @param peer - The byte array to convert to bytes32 or transaction argument * @returns Transaction result containing the bytes32 value */ fromBytesMoveCall(tx: Transaction, peer: Uint8Array | TransactionArgument): TransactionResult { return tx.moveCall({ target: this.#target('from_bytes'), arguments: [asBytes(tx, peer)], }) } /** * Create a bytes32 from a byte array with left padding * @param tx - The transaction to add the move call to * @param bytes - The byte array to convert with left padding or transaction argument * @returns Transaction result containing the left-padded bytes32 value */ fromBytesLeftPaddedMoveCall(tx: Transaction, bytes: Uint8Array | TransactionArgument): TransactionResult { return tx.moveCall({ target: this.#target('from_bytes_left_padded'), arguments: [asBytes(tx, bytes)], }) } /** * Create a bytes32 from a byte array with right padding * @param tx - The transaction to add the move call to * @param bytes - The byte array to convert with right padding or transaction argument * @returns Transaction result containing the right-padded bytes32 value */ fromBytesRightPaddedMoveCall(tx: Transaction, bytes: Uint8Array | TransactionArgument): TransactionResult { return tx.moveCall({ target: this.#target('from_bytes_right_padded'), arguments: [asBytes(tx, bytes)], }) } /** * Create a bytes32 from an address * @param tx - The transaction to add the move call to * @param address - The address to convert to bytes32 or transaction argument * @returns Transaction result containing the bytes32 representation of the address */ fromAddressMoveCall(tx: Transaction, address: string | TransactionArgument): TransactionResult { return tx.moveCall({ target: this.#target('from_address'), arguments: [asAddress(tx, address)], }) } /** * Create a bytes32 from an object ID * @param tx - The transaction to add the move call to * @param id - The object ID to convert to bytes32 or transaction argument * @returns Transaction result containing the bytes32 representation of the ID */ fromIdMoveCall(tx: Transaction, id: string | TransactionArgument): TransactionResult { return tx.moveCall({ target: this.#target('from_id'), arguments: [asObject(tx, id)], }) } /** * Create a zero bytes32 value (all zeros) * @param tx - The transaction to add the move call to * @returns Transaction result containing a zero bytes32 */ zeroBytes32MoveCall(tx: Transaction): TransactionResult { return tx.moveCall({ target: this.#target('zero_bytes32'), }) } /** * Create a bytes32 with all bits set to 1 (0xff...) * @param tx - The transaction to add the move call to * @returns Transaction result containing a bytes32 with all bits set */ ffBytes32MoveCall(tx: Transaction): TransactionResult { return tx.moveCall({ target: this.#target('ff_bytes32'), }) } /** * Check if a bytes32 value is zero (all zeros) * @param tx - The transaction to add the move call to * @param bytes32 - The bytes32 value to check or transaction argument * @returns Transaction result containing a boolean indicating if the value is zero */ isZeroMoveCall(tx: Transaction, bytes32: TransactionArgument): TransactionResult { return tx.moveCall({ target: this.#target('is_zero'), arguments: [bytes32], }) } /** * Check if a bytes32 value has all bits set to 1 * @param tx - The transaction to add the move call to * @param bytes32 - The bytes32 value to check or transaction argument * @returns Transaction result containing a boolean indicating if all bits are set */ isFfMoveCall(tx: Transaction, bytes32: TransactionArgument): TransactionResult { return tx.moveCall({ target: this.#target('is_ff'), arguments: [bytes32], }) } /** * Convert a bytes32 to a byte array * @param tx - The transaction to add the move call to * @param bytes32 - The bytes32 value to convert or transaction argument * @returns Transaction result containing the byte array representation */ toBytesMoveCall(tx: Transaction, bytes32: TransactionArgument): TransactionResult { return tx.moveCall({ target: this.#target('to_bytes'), arguments: [bytes32], }) } /** * Convert a bytes32 to an address * @param tx - The transaction to add the move call to * @param bytes32 - The bytes32 value to convert or transaction argument * @returns Transaction result containing the address representation */ toAddressMoveCall(tx: Transaction, bytes32: TransactionArgument): TransactionResult { return tx.moveCall({ target: this.#target('to_address'), arguments: [bytes32], }) } /** * Convert a bytes32 to an object ID * @param tx - The transaction to add the move call to * @param bytes32 - The bytes32 value to convert or transaction argument * @returns Transaction result containing the object ID representation */ toIdMoveCall(tx: Transaction, bytes32: TransactionArgument): TransactionResult { return tx.moveCall({ target: this.#target('to_id'), arguments: [bytes32], }) } // === Reader Buffer Functions === /** * Create a new buffer reader from a byte array * @param tx - The transaction to add the move call to * @param buffer - The byte array to create the reader from or transaction argument * @returns Transaction result containing the buffer reader */ newReaderMoveCall(tx: Transaction, buffer: Uint8Array | TransactionArgument): TransactionResult { return tx.moveCall({ target: this.#target('create', BUFFER_READER_MODULE_NAME), arguments: [asBytes(tx, buffer)], }) } /** * Get the current position of the buffer reader * @param tx - The transaction to add the move call to * @param reader - The buffer reader instance or transaction argument * @returns Transaction result containing the current position */ positionMoveCall(tx: Transaction, reader: TransactionArgument): TransactionResult { return tx.moveCall({ target: this.#target('position', BUFFER_READER_MODULE_NAME), arguments: [reader], }) } /** * Get the remaining length of data in the buffer reader * @param tx - The transaction to add the move call to * @param reader - The buffer reader instance or transaction argument * @returns Transaction result containing the remaining length */ remainingLengthMoveCall(tx: Transaction, reader: TransactionArgument): TransactionResult { return tx.moveCall({ target: this.#target('remaining_length', BUFFER_READER_MODULE_NAME), arguments: [reader], }) } /** * Skip a specified number of bytes in the buffer reader * @param tx - The transaction to add the move call to * @param reader - The buffer reader instance or transaction argument * @param len - The number of bytes to skip or transaction argument * @returns Transaction result containing the updated reader */ skipMoveCall(tx: Transaction, reader: TransactionArgument, len: number | TransactionArgument): TransactionResult { return tx.moveCall({ target: this.#target('skip', BUFFER_READER_MODULE_NAME), arguments: [reader, asU64(tx, len)], }) } /** * Set the position of the buffer reader * @param tx - The transaction to add the move call to * @param reader - The buffer reader instance or transaction argument * @param position - The position to set or transaction argument * @returns Transaction result containing the updated reader */ setPositionMoveCall( tx: Transaction, reader: TransactionArgument, position: number | TransactionArgument ): TransactionResult { return tx.moveCall({ target: this.#target('set_position', BUFFER_READER_MODULE_NAME), arguments: [reader, asU64(tx, position)], }) } /** * Rewind the buffer reader by a specified number of bytes * @param tx - The transaction to add the move call to * @param reader - The buffer reader instance or transaction argument * @param len - The number of bytes to rewind or transaction argument * @returns Transaction result containing the updated reader */ rewindMoveCall(tx: Transaction, reader: TransactionArgument, len: number | TransactionArgument): TransactionResult { return tx.moveCall({ target: this.#target('rewind', BUFFER_READER_MODULE_NAME), arguments: [reader, asU64(tx, len)], }) } /** * Read a boolean value from the buffer reader * @param tx - The transaction to add the move call to * @param reader - The buffer reader instance * @returns Transaction result containing the boolean value */ readBoolMoveCall(tx: Transaction, reader: TransactionArgument): TransactionResult { return tx.moveCall({ target: this.#target('read_bool', BUFFER_READER_MODULE_NAME), arguments: [reader], }) } /** * Read a u8 value from the buffer reader * @param tx - The transaction to add the move call to * @param reader - The buffer reader instance * @returns Transaction result containing the u8 value */ readU8MoveCall(tx: Transaction, reader: TransactionArgument): TransactionResult { return tx.moveCall({ target: this.#target('read_u8', BUFFER_READER_MODULE_NAME), arguments: [reader], }) } /** * Read a u16 value from the buffer reader * @param tx - The transaction to add the move call to * @param reader - The buffer reader instance * @returns Transaction result containing the u16 value */ readU16MoveCall(tx: Transaction, reader: TransactionArgument): TransactionResult { return tx.moveCall({ target: this.#target('read_u16', BUFFER_READER_MODULE_NAME), arguments: [reader], }) } /** * Read a u32 value from the buffer reader * @param tx - The transaction to add the move call to * @param reader - The buffer reader instance * @returns Transaction result containing the u32 value */ readU32MoveCall(tx: Transaction, reader: TransactionArgument): TransactionResult { return tx.moveCall({ target: this.#target('read_u32', BUFFER_READER_MODULE_NAME), arguments: [reader], }) } /** * Read a u64 value from the buffer reader * @param tx - The transaction to add the move call to * @param reader - The buffer reader instance * @returns Transaction result containing the u64 value */ readU64MoveCall(tx: Transaction, reader: TransactionArgument): TransactionResult { return tx.moveCall({ target: this.#target('read_u64', BUFFER_READER_MODULE_NAME), arguments: [reader], }) } /** * Read a u128 value from the buffer reader * @param tx - The transaction to add the move call to * @param reader - The buffer reader instance * @returns Transaction result containing the u128 value */ readU128MoveCall(tx: Transaction, reader: TransactionArgument): TransactionResult { return tx.moveCall({ target: this.#target('read_u128', BUFFER_READER_MODULE_NAME), arguments: [reader], }) } /** * Read a u256 value from the buffer reader * @param tx - The transaction to add the move call to * @param reader - The buffer reader instance * @returns Transaction result containing the u256 value */ readU256MoveCall(tx: Transaction, reader: TransactionArgument): TransactionResult { return tx.moveCall({ target: this.#target('read_u256', BUFFER_READER_MODULE_NAME), arguments: [reader], }) } /** * Read a bytes32 value from the buffer reader * @param tx - The transaction to add the move call to * @param reader - The buffer reader instance * @returns Transaction result containing the bytes32 value */ readBytes32MoveCall(tx: Transaction, reader: TransactionArgument): TransactionResult { return tx.moveCall({ target: this.#target('read_bytes32', BUFFER_READER_MODULE_NAME), arguments: [reader], }) } /** * Read an address value from the buffer reader * @param tx - The transaction to add the move call to * @param reader - The buffer reader instance * @returns Transaction result containing the address value */ readAddressMoveCall(tx: Transaction, reader: TransactionArgument): TransactionResult { return tx.moveCall({ target: this.#target('read_address', BUFFER_READER_MODULE_NAME), arguments: [reader], }) } /** * Read a fixed-length byte array from the buffer reader * @param tx - The transaction to add the move call to * @param reader - The buffer reader instance or transaction argument * @param len - The length of bytes to read or transaction argument * @returns Transaction result containing the byte array */ readFixedLenBytesMoveCall( tx: Transaction, reader: TransactionArgument, len: number | TransactionArgument ): TransactionResult { return tx.moveCall({ target: this.#target('read_fixed_len_bytes', BUFFER_READER_MODULE_NAME), arguments: [reader, asU64(tx, len)], }) } /** * Read all remaining bytes from the buffer reader until the end * @param tx - The transaction to add the move call to * @param reader - The buffer reader instance or transaction argument * @returns Transaction result containing the remaining bytes */ readBytesUntilEndMoveCall(tx: Transaction, reader: TransactionArgument): TransactionResult { return tx.moveCall({ target: this.#target('read_bytes_until_end', BUFFER_READER_MODULE_NAME), arguments: [reader], }) } /** * Get the total buffer length of the reader * @param tx - The transaction to add the move call to * @param reader - The buffer reader instance or transaction argument * @returns Transaction result containing the total buffer length */ readerBufferLengthMoveCall(tx: Transaction, reader: TransactionArgument): TransactionResult { return tx.moveCall({ target: this.#target('length', BUFFER_READER_MODULE_NAME), arguments: [reader], }) } /** * Convert the buffer reader to a byte array * @param tx - The transaction to add the move call to * @param reader - The buffer reader instance or transaction argument * @returns Transaction result containing the byte array representation */ readerToBytesMoveCall(tx: Transaction, reader: TransactionArgument): TransactionResult { return tx.moveCall({ target: this.#target('to_bytes', BUFFER_READER_MODULE_NAME), arguments: [reader], }) } // Writer functions /** * Create a new empty buffer writer * @param tx - The transaction to add the move call to * @returns Transaction result containing the buffer writer */ newWriterMoveCall(tx: Transaction): TransactionResult { return tx.moveCall({ target: this.#target('new', BUFFER_WRITER_MODULE_NAME), }) } /** * Create a buffer writer with initial data * @param tx - The transaction to add the move call to * @param buffer - The initial byte array data for the writer or transaction argument * @returns Transaction result containing the buffer writer */ createWriterMoveCall(tx: Transaction, buffer: Uint8Array | TransactionArgument): TransactionResult { return tx.moveCall({ target: this.#target('create', BUFFER_WRITER_MODULE_NAME), arguments: [asBytes(tx, buffer)], }) } /** * Get the total buffer length of the writer * @param tx - The transaction to add the move call to * @param writer - The buffer writer instance * @returns Transaction result containing the total buffer length */ writerBufferLengthMoveCall(tx: Transaction, writer: TransactionArgument): TransactionResult { return tx.moveCall({ target: this.#target('length', BUFFER_WRITER_MODULE_NAME), arguments: [writer], }) } /** * Convert the buffer writer to a byte array * @param tx - The transaction to add the move call to * @param writer - The buffer writer instance * @returns Transaction result containing the byte array representation */ writerToBytesMoveCall(tx: Transaction, writer: TransactionArgument): TransactionResult { return tx.moveCall({ target: this.#target('to_bytes', BUFFER_WRITER_MODULE_NAME), arguments: [writer], }) } /** * Write a boolean value to the buffer writer * @param tx - The transaction to add the move call to * @param writer - The buffer writer instance or transaction argument * @param value - The boolean value to write or transaction argument * @returns Transaction result containing the updated writer */ writeBoolMoveCall( tx: Transaction, writer: TransactionArgument, value: boolean | TransactionArgument ): TransactionResult { return tx.moveCall({ target: this.#target('write_bool', BUFFER_WRITER_MODULE_NAME), arguments: [writer, asBool(tx, value)], }) } /** * Write a u8 value to the buffer writer * @param tx - The transaction to add the move call to * @param writer - The buffer writer instance or transaction argument * @param value - The u8 value to write or transaction argument * @returns Transaction result containing the updated writer */ writeU8MoveCall( tx: Transaction, writer: TransactionArgument, value: number | TransactionArgument ): TransactionResult { return tx.moveCall({ target: this.#target('write_u8', BUFFER_WRITER_MODULE_NAME), arguments: [writer, asU8(tx, value)], }) } /** * Write a u16 value to the buffer writer * @param tx - The transaction to add the move call to * @param writer - The buffer writer instance or transaction argument * @param value - The u16 value to write or transaction argument * @returns Transaction result containing the updated writer */ writeU16MoveCall( tx: Transaction, writer: TransactionArgument, value: number | TransactionArgument ): TransactionResult { return tx.moveCall({ target: this.#target('write_u16', BUFFER_WRITER_MODULE_NAME), arguments: [writer, asU16(tx, value)], }) } /** * Write a u32 value to the buffer writer * @param tx - The transaction to add the move call to * @param writer - The buffer writer instance or transaction argument * @param value - The u32 value to write or transaction argument * @returns Transaction result containing the updated writer */ writeU32MoveCall( tx: Transaction, writer: TransactionArgument, value: number | TransactionArgument ): TransactionResult { return tx.moveCall({ target: this.#target('write_u32', BUFFER_WRITER_MODULE_NAME), arguments: [writer, asU32(tx, value)], }) } /** * Write a u64 value to the buffer writer * @param tx - The transaction to add the move call to * @param writer - The buffer writer instance or transaction argument * @param value - The u64 value to write or transaction argument * @returns Transaction result containing the updated writer */ writeU64MoveCall( tx: Transaction, writer: TransactionArgument, value: bigint | number | string | TransactionArgument ): TransactionResult { return tx.moveCall({ target: this.#target('write_u64', BUFFER_WRITER_MODULE_NAME), arguments: [writer, asU64(tx, value)], }) } /** * Write a u128 value to the buffer writer * @param tx - The transaction to add the move call to * @param writer - The buffer writer instance or transaction argument * @param value - The u128 value to write or transaction argument * @returns Transaction result containing the updated writer */ writeU128MoveCall( tx: Transaction, writer: TransactionArgument, value: bigint | number | string | TransactionArgument ): TransactionResult { return tx.moveCall({ target: this.#target('write_u128', BUFFER_WRITER_MODULE_NAME), arguments: [writer, asU128(tx, value)], }) } /** * Write a u256 value to the buffer writer * @param tx - The transaction to add the move call to * @param writer - The buffer writer instance or transaction argument * @param value - The u256 value to write or transaction argument * @returns Transaction result containing the updated writer */ writeU256MoveCall( tx: Transaction, writer: TransactionArgument, value: bigint | number | string | TransactionArgument ): TransactionResult { return tx.moveCall({ target: this.#target('write_u256', BUFFER_WRITER_MODULE_NAME), arguments: [writer, asU256(tx, value)], }) } /** * Write a byte array to the buffer writer * @param tx - The transaction to add the move call to * @param writer - The buffer writer instance or transaction argument * @param bytes - The byte array to write or transaction argument * @returns Transaction result containing the updated writer */ writeBytesMoveCall( tx: Transaction, writer: TransactionArgument, bytes: Uint8Array | TransactionArgument ): TransactionResult { return tx.moveCall({ target: this.#target('write_bytes', BUFFER_WRITER_MODULE_NAME), arguments: [writer, asBytes(tx, bytes)], }) } /** * Write an address to the buffer writer * @param tx - The transaction to add the move call to * @param writer - The buffer writer instance or transaction argument * @param address - The address to write or transaction argument * @returns Transaction result containing the updated writer */ writeAddressMoveCall( tx: Transaction, writer: TransactionArgument, address: string | TransactionArgument ): TransactionResult { return tx.moveCall({ target: this.#target('write_address', BUFFER_WRITER_MODULE_NAME), arguments: [writer, asAddress(tx, address)], }) } /** * Write a bytes32 value to the buffer writer * @param tx - The transaction to add the move call to * @param writer - The buffer writer instance or transaction argument * @param bytes32 - The bytes32 value to write (as Uint8Array) or transaction argument * @returns Transaction result containing the updated writer */ writeBytes32MoveCall( tx: Transaction, writer: TransactionArgument, bytes32: Uint8Array | TransactionArgument ): TransactionResult { return tx.moveCall({ target: this.#target('write_bytes32', BUFFER_WRITER_MODULE_NAME), arguments: [writer, asBytes32(tx, bytes32, this)], }) } // === Package Functions === /** * Get the original package address where a type was first defined * @param tx - The transaction to add the move call to * @param typeArgument - The type to get the original package address for * @returns Transaction result containing the original package address */ originalPackageOfTypeMoveCall(tx: Transaction, typeArgument: string): TransactionResult { return tx.moveCall({ target: this.#target('original_package_of_type', PACKAGE_MODULE_NAME), typeArguments: [typeArgument], }) } /** * Get the current package address where a type is defined * @param tx - The transaction to add the move call to * @param typeArgument - The type to get the current package address for * @returns Transaction result containing the current package address */ packageOfTypeMoveCall(tx: Transaction, typeArgument: string): TransactionResult { return tx.moveCall({ target: this.#target('package_of_type', PACKAGE_MODULE_NAME), typeArguments: [typeArgument], }) } // === Other Package Utils Functions === /** * Create an Option<Coin<SUI>> from a value * @param tx - The transaction to add the move call to * @param value - The amount of SUI to wrap in the option * @returns Transaction result containing option::none for zero values, option::some otherwise * @note TransactionArguments always create option::some (runtime values cannot be evaluated) */ createOptionSuiMoveCall(tx: Transaction, value: bigint | number | string | TransactionArgument): TransactionResult { if (this.isZeroValue(value)) { return this._createOptionNone(tx) } return this._createOptionSome(tx, value) } /** * Check if a static value (non-TransactionArgument) is zero */ private isZeroValue(value: bigint | number | string | TransactionArgument): boolean { if (typeof value === 'bigint' || typeof value === 'number' || typeof value === 'string') { return this._isZeroValue(value) } return false } /** * Check if a primitive value equals zero */ private _isZeroValue(value: bigint | number | string): boolean { switch (typeof value) { case 'bigint': return value === 0n case 'number': return value === 0 case 'string': return value === '0' || Number(value) === 0 default: return false } } /** * Create option::none for Coin<SUI> */ private _createOptionNone(tx: Transaction): TransactionResult { return tx.moveCall({ target: '0x1::option::none', typeArguments: ['0x2::coin::Coin<0x2::sui::SUI>'], arguments: [], }) } /** * Create option::some for Coin<SUI> */ private _createOptionSome( tx: Transaction, value: bigint | number | string | TransactionArgument ): TransactionResult { const coin = tx.splitCoins(tx.gas, [asU64(tx, value)]) return tx.moveCall({ target: '0x1::option::some', typeArguments: ['0x2::coin::Coin<0x2::sui::SUI>'], arguments: [coin], }) } /** * Splits specified amount of coins from user's wallet * @param tx - The transaction to add the move call to * @param coinType - The type of coin to split * @param owner - Address of the user whose coins to split * @param amount - Amount of coins to split (in smallest units) * @param limit - Maximum total number of coins to collect across all pages (default: 200) * @param pageSize - Maximum number of coins to fetch per page (default: 50) * @returns Promise resolving to split coin as TransactionResult * @throws Error if insufficient coins balance or no coins found */ async splitCoinMoveCall( tx: Transaction, coinType: string, owner: string, amount: bigint, limit = 200, pageSize = 50 ): Promise<TransactionResult> { const sufficientCoins = await this.#fetchSufficientCoins(owner, coinType, amount, limit, pageSize) const totalBalance = sufficientCoins.reduce((sum, coin) => sum + BigInt(coin.balance), 0n) // Use single coin if available, otherwise merge multiple coins const primaryCoin = sufficientCoins.find((coin) => BigInt(coin.balance) >= amount) ?? sufficientCoins[0] const primaryCoinObj = tx.object(primaryCoin.coinObjectId) // Merge additional coins if needed if (primaryCoin === sufficientCoins[0] && sufficientCoins.length > 1) { tx.mergeCoins( primaryCoinObj, sufficientCoins.slice(1).map((coin) => tx.object(coin.coinObjectId)) ) } // Split the required amount const splitCoin = tx.splitCoins(primaryCoinObj, [asU64(tx, amount)]) // Destroy zero-value remainder if total balance equals requested amount if (totalBalance === amount) { this.#destroyZeroCoin(tx, primaryCoinObj, coinType) } return splitCoin } // === Internal Functions === /** * Generate the full target path for move calls * @param name - The function name to call * @param module_name - The module name (defaults to BYTES32_MODULE_NAME) * @returns The full module path for the move call * @private */ #target(name: string, module_name = BYTES32_MODULE_NAME): string { return `${this.packageId}::${module_name}::${name}` } /** * Destroy a zero-value coin to clean up wallet state * @param tx - The transaction to add the move call to * @param coinObj - The coin object to destroy * @param coinType - The coin type * @private */ #destroyZeroCoin(tx: Transaction, coinObj: TransactionArgument, coinType: string): void { tx.moveCall({ target: '0x2::coin::destroy_zero', arguments: [coinObj], typeArguments: [coinType], }) } /** * Fetches coins incrementally until sufficient balance is reached * This method paginates through the user's ZRO coins to collect enough balance * @param owner - Address of the user whose ZRO coins to fetch * @param coinType - ZRO coin type string * @param amount - Required amount to reach * @param limit - Maximum total number of coins to collect across all pages * @param pageSize - Maximum coins to fetch per page * @returns Promise resolving to array of coin objects with sufficient total balance */ async #fetchSufficientCoins( owner: string, coinType: string, amount: bigint, limit: number, pageSize: number ): Promise<CoinStruct[]> { const coins: CoinStruct[] = [] let accumulatedBalance = 0n let cursor: string | null = null do { const coinsResponse = await this.client.getCoins({ owner, coinType, cursor, limit: pageSize }) if (coinsResponse.data.length === 0) { break } for (const coin of coinsResponse.data) { coins.push(coin) accumulatedBalance += BigInt(coin.balance) // Stop if we have enough balance if (accumulatedBalance >= amount) { return coins } // Throw error if we have reached the maximum coin limit if (coins.length >= limit) { throw new Error( `Insufficient ${coinType} balance: reached maximum coin limit (${limit}) but still need ${amount - accumulatedBalance} more` ) } } cursor = coinsResponse.hasNextPage ? coinsResponse.nextCursor ?? null : null } while (cursor != null) // If we've exhausted all coins but still don't have sufficient balance if (accumulatedBalance < amount) { throw new Error( `Insufficient ${coinType} balance: only found ${accumulatedBalance} but need ${amount} (shortfall: ${amount - accumulatedBalance})` ) } return coins } }