import { SuiClient, TransactionBlockData } from '@mysten/sui.js/client'; import { normalizeSuiAddress, normalizeSuiObjectId, normalizeStructTag } from '@mysten/sui.js/utils'; import { Coin } from '@/coin'; import { SUI_COIN, UNKNOWN_COIN } from '@/constants'; import { CoinTransferPayload, ObjectTransferPayload } from './types'; /** * @deprecated Use TxParser instead. */ export class TransactionResponseDataDecoder { constructor(private transactionData: TransactionBlockData) {} // coin transfer composed of [MergeCoins], SplitCoins and TransferObjects isCoinTransfer(): boolean { const transactions = this.transactionData.transaction.kind === 'ProgrammableTransaction' ? this.transactionData.transaction.transactions : null; if (!transactions || transactions.length < 2 || transactions.length > 3) { return false; } if (transactions.length === 2) { return 'SplitCoins' in transactions[0] && 'TransferObjects' in transactions[1]; } return 'SplitCoins' in transactions[1] && 'TransferObjects' in transactions[2]; } isObjectTransfer(): boolean { if (this.transactionData.transaction.kind !== 'ProgrammableTransaction') { return false; } const { transactions } = this.transactionData.transaction; if (transactions.length !== 1) { return false; } return 'TransferObjects' in transactions[0] && transactions[0].TransferObjects != null; } async coinTransferInfo(suiClient: SuiClient): Promise { if (!this.isCoinTransfer()) { throw new Error('Not a coin transfer'); } if (this.transactionData.transaction.kind !== 'ProgrammableTransaction') { throw new Error('Not a coin transfer'); } const { transactions } = this.transactionData.transaction; const { inputs } = this.transactionData.transaction; const splitPayload = transactions.length === 2 ? transactions[0] : transactions[1]; const transferPayload = transactions.length === 2 ? transactions[1] : transactions[2]; if (!('SplitCoins' in splitPayload && 'TransferObjects' in transferPayload)) { throw new Error('Not a coin transfer'); } const splitAmount = TransactionResponseDataDecoder.decodePureInput(inputs, { index: (splitPayload.SplitCoins[1][0] as { Input: number }).Input, }); const toAddress = TransactionResponseDataDecoder.decodePureInput(inputs, { index: (transferPayload.TransferObjects[1] as { Input: number }).Input, }); let coinType = normalizeStructTag(SUI_COIN); if (splitPayload.SplitCoins[0] !== 'GasCoin') { const objectDecoded = TransactionResponseDataDecoder.decodeObjectInput(inputs, { index: (splitPayload.SplitCoins[0] as { Input: number }).Input, }); const objectInfo = await suiClient.tryGetPastObject({ id: objectDecoded.objectId, version: objectDecoded.version, options: { showType: true }, }); if (objectInfo.status === 'VersionFound') { const foundCoinType = objectInfo.details.type; const suiCoinType = Coin.getCoinType(foundCoinType); coinType = suiCoinType ? normalizeStructTag(suiCoinType) : UNKNOWN_COIN; } } return { coinType, amount: BigInt(splitAmount), to: normalizeSuiAddress(toAddress), }; } async objectTransferInfo(suiClient: SuiClient): Promise { if (!this.isObjectTransfer()) { throw new Error('Not an object transfer'); } if (this.transactionData.transaction.kind !== 'ProgrammableTransaction') { throw new Error('Not an object transfer'); } const { transactions, inputs } = this.transactionData.transaction; const transactionPayload = transactions.pop(); const transferObjects = 'TransferObjects' in transactionPayload && transactionPayload.TransferObjects; const toAddress = TransactionResponseDataDecoder.decodePureInput(inputs, { index: (transferObjects[1] as { Input: number }).Input, }); const transferObjectsInput = (transferObjects[0][0] as any).Input as number; if (transferObjectsInput >= 0) { const objectDecoded = TransactionResponseDataDecoder.decodeObjectInput(inputs, { index: transferObjectsInput, }); const objectInfo = await suiClient.tryGetPastObject({ id: objectDecoded.objectId, version: objectDecoded.version, options: { showType: true }, }); return { objectId: normalizeSuiObjectId(objectDecoded.objectId), objectVersion: objectDecoded.version.toString(), objectType: objectInfo.status === 'VersionFound' ? objectInfo.details.type : '', to: normalizeSuiAddress(toAddress), }; } if (transferObjects[0][0] === 'GasCoin') { return { objectId: normalizeSuiObjectId('0x2'), objectVersion: '0', objectType: normalizeSuiObjectId('0x2'), to: normalizeSuiAddress(toAddress), }; } throw new Error('invliad object transfer tx'); } static decodeObjectInput(inputs: any, param: { index: number }) { const input = inputs[param.index]; return { objectId: normalizeSuiObjectId(input.objectId), version: parseInt(input.version ?? input.initialSharedVersion, 10), }; } static decodePureInput(inputs: any, param: { index: number }) { const input = inputs[param.index]; return input.value; } }