@layerzerolabs/lz-sui-sdk-v2
Version:
158 lines (141 loc) • 5.34 kB
text/typescript
import { SuiClient, SuiExecutionResult, SuiTransactionBlockResponse } from '@mysten/sui/client'
import { Keypair } from '@mysten/sui/cryptography'
import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519'
import { Transaction } from '@mysten/sui/transactions'
import { fromBase64 } from '@mysten/sui/utils'
import { MoveAbortError, SimulateResult, UnclassifiedError } from '../types'
export async function simulateTransaction(
suiClient: SuiClient,
tx: Transaction,
sender?: string
): Promise<SimulateResult[]> {
const resolvedSender = determineSenderAddress(tx, sender)
const { results, error } = await suiClient.devInspectTransactionBlock({
transactionBlock: tx,
sender: resolvedSender,
})
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (error != undefined && error != null) {
throw handleError(error)
}
if (results === undefined || results === null) {
throw new Error('No results found')
}
const lastCommandResults: SuiExecutionResult = results[results.length - 1]
if (lastCommandResults.returnValues === undefined) {
throw new Error('No return values found')
}
return lastCommandResults.returnValues.map((result) => {
return {
value: typeof result[0] === 'string' ? fromBase64(result[0]) : new Uint8Array(result[0]),
type: result[1],
}
})
}
export async function simulateTransactionMultiResult(
suiClient: SuiClient,
tx: Transaction,
resultIndex: number[],
sender?: string
): Promise<SimulateResult[][]> {
const resolvedSender = determineSenderAddress(tx, sender)
const { results, error } = await suiClient.devInspectTransactionBlock({
transactionBlock: tx,
sender: resolvedSender,
})
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (error != undefined && error != null) {
throw handleError(error)
}
if (results === undefined || results === null) {
throw new Error('No results found')
}
const selectedResults: SuiExecutionResult[] = resultIndex.map((idx) => {
return results[idx]
})
return selectedResults.map((result) => {
if (result.returnValues === undefined) {
throw new Error('No return values found')
}
return result.returnValues.map((result) => {
return {
value: typeof result[0] === 'string' ? fromBase64(result[0]) : new Uint8Array(result[0]),
type: result[1],
}
})
})
}
export async function validateTransaction(
client: SuiClient,
signer: Keypair,
tx: Transaction
): Promise<SuiTransactionBlockResponse> {
tx.setSenderIfNotSet(signer.getPublicKey().toSuiAddress())
const result = await client.signAndExecuteTransaction({
signer,
transaction: tx,
options: {
showEffects: true,
showObjectChanges: true,
showEvents: true,
},
})
if (result.effects?.status.status !== 'success') {
throw new Error(result.effects?.status.error)
}
await client.waitForTransaction({ digest: result.digest })
return result
}
/**
* Execute simulate using a moveCall function
* @param client - The Sui client
* @param moveCallFn - Function that adds moveCall to the transaction (supports both sync and async, any return type)
* @param parser - Function to parse the result
* @returns The parsed result
*/
export async function executeSimulate<T>(
client: SuiClient,
moveCallFn: (tx: Transaction) => unknown,
parser: (result: { value: Uint8Array }[]) => T
): Promise<T> {
const tx = new Transaction()
await moveCallFn(tx)
const result = await simulateTransaction(client, tx)
return parser(result)
}
/**
* Determines the appropriate sender address using fallback hierarchy:
* 1. Use provided sender if available
* 2. Use transaction's existing sender if valid
* 3. Generate a random sender as last resort
*
* @param tx - The transaction to get sender from
* @param preferredSender - Optional sender address to prioritize
* @returns The determined sender address
*/
function determineSenderAddress(tx: Transaction, preferredSender?: string): string {
//eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
if (preferredSender) return preferredSender
const existingSender = tx.getData().sender
if (typeof existingSender === 'string') return existingSender
return Ed25519Keypair.generate().toSuiAddress()
}
export function handleError(e: string): unknown {
if (e.includes('ExecutionError')) {
const majorStatusMatch = e.match(/major_status:\s([A-Z_]+)/)
if (majorStatusMatch !== null) {
const major_status = majorStatusMatch[1]
if (major_status === 'ABORTED') {
const subStatusMatch = e.match(/sub_status:\sSome\((\d+)\)/)
if (subStatusMatch === null) {
return new MoveAbortError(Number.NEGATIVE_INFINITY, e)
} else {
return new MoveAbortError(Number(subStatusMatch[1]), e)
}
}
} else {
return new UnclassifiedError(e)
}
}
return new Error(e)
}