@layerzerolabs/lz-sui-sdk-v2
Version:
900 lines (818 loc) • 33.8 kB
text/typescript
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
}
}