/**
 * Adapted from explorer.solana.com code written by @jstarry.
 */ import type { TransactionError } from "@solana/web3.js" ; import invariant from "tiny-invariant" ; import { getTransactionInstructionError } from "./programErr" ; /**
 * A log entry.
 */ export type InstructionLogEntry = { /**
     * Stack depth.
     */ depth : number ; } & ( { type : "text" ; text : string ; } | { type : "system" ; text : string ; } | { type : "cpi" ; programAddress : string | null ; } | { type : "success" ; } | { type : "programError" ; text : string ; } | { type : "runtimeError" ; text : string ; } ) ; /**
 * Logs of an individual instruction.
 */ export interface InstructionLogs { /**
     * The program invoked, if it exists in the logs.
     */ programAddress ? : string ; /**
     * Logs of the instruction.
     */ logs : InstructionLogEntry [ ] ; /**
     * Whether the instruction failed.
     */ failed : boolean ; } param logs param error returns export const parseTransactionLogs = ( logs , error ) : InstructionLogs [ ] => { let depth = 0 ; const prettyLogs = [ ] ; let prettyError ; if ( ! logs ) { if ( error ) throw new Error ( JSON . stringify ( error ) ) ; throw new Error ( "No logs detected" ) ; } else if ( error ) { prettyError = getTransactionInstructionError ( error ) ; } logs . forEach ( ( log ) => { if ( log . startsWith ( "Program log:" ) ) { prettyLogs [ prettyLogs . length - 1 ] ?. logs . push ( { type : "text" , depth , text : log , } ) ; } else { const regex = /Program (\w*) invoke \[(\d)\]/g ; const matches = [ ... log . matchAll ( regex ) ] ; if ( matches . length > 0 ) { const programAddress = matches [ 0 ] ?. [ 1 ] ; invariant ( programAddress , "program address" ) ; if ( depth === 0 ) { prettyLogs . push ( { programAddress , logs : [ ] , failed : false , } ) ; } else { prettyLogs [ prettyLogs . length - 1 ] ?. logs . push ( { type : "cpi" , depth , programAddress : programAddress ?? null , } ) ; } depth ++ ; } else if ( log . includes ( "success" ) ) { prettyLogs [ prettyLogs . length - 1 ] ?. logs . push ( { type : "success" , depth , } ) ; depth -- ; } else if ( log . includes ( "failed" ) ) { const instructionLog = prettyLogs [ prettyLogs . length - 1 ] ; if ( instructionLog && ! instructionLog . failed ) { instructionLog . failed = true ; instructionLog . logs . push ( { type : "programError" , depth , text : log . slice ( log . indexOf ( ": " ) + 2 ) , } ) ; } depth -- ; } else { if ( depth === 0 ) { prettyLogs . push ( { logs : [ ] , failed : false , } ) ; depth ++ ; } prettyLogs [ prettyLogs . length - 1 ] ?. logs . push ( { type : "system" , depth , text : log , } ) ; } } } ) ; if ( prettyError && prettyLogs . length === 0 ) { prettyLogs . push ( { logs : [ ] , failed : true , } ) ; } if ( prettyError && prettyError . index === prettyLogs . length - 1 ) { const failedIx = prettyLogs [ prettyError . index ] ; if ( failedIx ) { failedIx . failed = true ; failedIx . logs . push ( { type : "runtimeError" , depth : 1 , text : prettyError . message , } ) ; } } return prettyLogs ; } ; const buildPrefix = ( depth ) => { const prefix = new Array ( depth - 1 ) . fill ( "\u00A0\u00A0" ) . join ( "" ) ; return prefix + "> " ; } ; const formatLogEntryString = ( entry ) => { switch ( entry . type ) { case "success" : return `Program returned success` ; case "programError" : return `Program returned error: ${ entry . text }` ; case "runtimeError" : return `Runtime error: ${ entry . text }` ; case "system" : return entry . text ; case "text" : return entry . text ; case "cpi" : return `Invoking Unknown ${ entry . programAddress ? `(${ entry . programAddress }) ` : "" }Program` ; } } ; param entry param prefix returns export const formatLogEntry = ( entry , prefix = false ) : string => { const prefixString = prefix ? buildPrefix ( entry . depth ) : "" ; return `${ prefixString }${ formatLogEntryString ( entry ) }` ; } ; param logs export const formatInstructionLogs = ( logs ) : string => logs . map ( ( log , i ) => { return [ `=> Instruction #${ i }: ${ log . programAddress ? `Program ${ log . programAddress }` : "System" }` , ... log . logs . map ( ( entry ) => formatLogEntry ( entry , true ) ) , ] . join ( "\n" ) ; } ) . join ( "\n" ) ;