11import { ZeroBytes32 } from '@fuel-ts/address/configs' ;
22import { ErrorCode , FuelError } from '@fuel-ts/errors' ;
33import type { BN } from '@fuel-ts/math' ;
4- import { bn } from '@fuel-ts/math' ;
5- import { ReceiptType , TransactionType } from '@fuel-ts/transactions' ;
4+ import { bn , toBytes } from '@fuel-ts/math' ;
5+ import { ReceiptType , TransactionCoder , TransactionType } from '@fuel-ts/transactions' ;
66import type { InputContract , Output , OutputChange , Input } from '@fuel-ts/transactions' ;
7+ import { arrayify , concat } from '@fuel-ts/utils' ;
78
89import type {
910 TransactionResultReceipt ,
@@ -13,7 +14,7 @@ import type {
1314 TransactionResultTransferReceipt ,
1415} from '../transaction-response' ;
1516
16- import type { FunctionCall } from './call' ;
17+ import { getFunctionCall , type FunctionCall } from './call' ;
1718import {
1819 getInputFromAssetId ,
1920 getInputAccountAddress ,
@@ -269,24 +270,68 @@ export function getWithdrawFromFuelOperations({
269270 return withdrawFromFuelOperations ;
270271}
271272
273+ /** @hidden */
274+ function findBytesSegmentIndex ( whole : Uint8Array , segment : Uint8Array ) {
275+ for ( let i = 0 ; i <= whole . length - segment . length ; i ++ ) {
276+ let match = true ;
277+ for ( let j = 0 ; j < segment . length ; j ++ ) {
278+ if ( whole [ i + j ] !== segment [ j ] ) {
279+ match = false ;
280+ break ;
281+ }
282+ }
283+ if ( match ) {
284+ return i ;
285+ }
286+ }
287+ return - 1 ;
288+ }
289+
272290/** @hidden */
273291function getContractCalls (
274292 contractInput : InputContract ,
275293 abiMap : AbiMap | undefined ,
276- _receipt : TransactionResultCallReceipt ,
277- _rawPayload : string ,
278- _maxInputs : BN
294+ receipt : TransactionResultCallReceipt ,
295+ scriptData ?: Uint8Array
279296) : FunctionCall [ ] {
297+ const calls : FunctionCall [ ] = [ ] ;
298+
280299 const abi = abiMap ?. [ contractInput . contractID ] ;
281- if ( ! abi ) {
282- return [ ] ;
300+ if ( ! abi || ! scriptData ) {
301+ return calls ;
302+ }
303+
304+ const bytesSegment = concat ( [
305+ arrayify ( receipt . to ) , // Contract ID (32 bytes)
306+ toBytes ( receipt . param1 . toHex ( ) , 8 ) , // Function selector offset (8 bytes)
307+ toBytes ( receipt . param2 . toHex ( ) , 8 ) , // Function args offset (8 bytes)
308+ ] ) ;
309+
310+ const segmentIndex = findBytesSegmentIndex ( scriptData , bytesSegment ) ;
311+
312+ /**
313+ * If the byte segment is not found, it likely indicates a non-standard contract call, such as:
314+ *
315+ * 1. Manual External Call: A direct call from a Sway script or contract using
316+ * `abi(abi_interface, contract_id)` built-in Sway function.
317+ *
318+ * 2. Inline ASM Call: A call made using the ASM `call` instruction in Sway,
319+ * without setting `param1` and `param2` offsets just like the SDKs do.
320+ *
321+ * In these cases, the function call cannot be decoded.
322+ */
323+ const canDecodeFunctionCall = segmentIndex !== - 1 ;
324+
325+ if ( ! canDecodeFunctionCall ) {
326+ return calls ;
283327 }
284328
285- // Until we can successfully decode all operations, including multicall we
286- // will just return an empty. This should then be reintroduced in
287- // https://github.com/FuelLabs/fuels-ts/issues/3733
288- return [ ] ;
289- // return [ getFunctionCall({ abi, receipt, rawPayload, maxInputs }) ];
329+ const offset = segmentIndex + bytesSegment . length ;
330+
331+ const call = getFunctionCall ( { abi, receipt, offset, scriptData } ) ;
332+ calls . push ( call ) ;
333+
334+ return calls ;
290335}
291336
292337/** @hidden */
@@ -307,8 +352,7 @@ function processCallReceipt(
307352 contractInput : InputContract ,
308353 inputs : Input [ ] ,
309354 abiMap : AbiMap | undefined ,
310- rawPayload : string ,
311- maxInputs : BN ,
355+ scriptData : Uint8Array | undefined ,
312356 baseAssetId : string
313357) : Operation [ ] {
314358 const assetId = receipt . assetId === ZeroBytes32 ? baseAssetId : receipt . assetId ;
@@ -318,7 +362,7 @@ function processCallReceipt(
318362 }
319363
320364 const inputAddress = getInputAccountAddress ( input ) ;
321- const calls = getContractCalls ( contractInput , abiMap , receipt , rawPayload , maxInputs ) ;
365+ const calls = getContractCalls ( contractInput , abiMap , receipt , scriptData ) ;
322366
323367 return [
324368 {
@@ -345,7 +389,6 @@ export function getContractCallOperations({
345389 receipts,
346390 abiMap,
347391 rawPayload,
348- maxInputs,
349392 baseAssetId,
350393} : InputOutputParam &
351394 ReceiptParam &
@@ -360,18 +403,19 @@ export function getContractCallOperations({
360403 return [ ] ;
361404 }
362405
406+ let scriptData : Uint8Array | undefined ;
407+
408+ if ( rawPayload ) {
409+ const [ transaction ] = new TransactionCoder ( ) . decode ( arrayify ( rawPayload ) , 0 ) ;
410+ if ( transaction . type === TransactionType . Script ) {
411+ scriptData = arrayify ( transaction . scriptData as string ) ;
412+ }
413+ }
414+
363415 return contractCallReceipts
364416 . filter ( ( receipt ) => receipt . to === contractInput . contractID )
365417 . flatMap ( ( receipt ) =>
366- processCallReceipt (
367- receipt ,
368- contractInput ,
369- inputs ,
370- abiMap ,
371- rawPayload as string ,
372- maxInputs ,
373- baseAssetId
374- )
418+ processCallReceipt ( receipt , contractInput , inputs , abiMap , scriptData , baseAssetId )
375419 ) ;
376420 } ) ;
377421}
0 commit comments