import type { AccountInfo , Connection } from "@solana/web3.js" ; import { PublicKey } from "@solana/web3.js" ; /**
 * A useful tool for debugging account structs. It gives a quick glance at
 * addresses and owners. It also converts bignums into JS numbers.
 *
 * Types converted:
 * - **big numbers**: converted to native numbers
 * - **addresses**: format in base58, and prints the owner in parentheses if the account exists
 * - **plain objects**: recursively converts
 *
 * HINT: This function is mainly useful for the browser. If you are writing
 * Rust integration tests, use debugAccountOwners from chai-solana instead, so
 * that you don't have to pass in connection.
 *
 * Usage:
 * ```
 * await printAccountOwners(connection, depositAccounts);
 * // using void is recommend in dapps to avoid slowing down the user experience
 * void printAccountOwners(connection, depositAccounts);
 * ```
 *
 * Example output:
 * ```
 * tests/awesomeTest.spec.ts:583:29 {
 *   payer: 'CEGhKVeyXUrihUnNU9EchSuu6pMHEsB8MiKgvhJqYgd1 (11111111111111111111111111111111)',
 *   foo: '61tMNVhG66QZQ4UEAoHytqaUN4G1xpk1zsS5YU7Y2Qui (135QzSyjKTKaZ7ebhLpvNA2KUahEjykMjbqz3JV1V4k9)',
 *   bar: '9oPMxXVSm5msAecxi4zJpKDwbHS9c6Yos1ru739rVExc (TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA)',
 *   tokenProgram: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA (BPFLoader2111111111111111111111111111111111)'
 * }
 * ```
 *
 * WARNING: This may break silently if web3 changes its api. This is only
 * intended for debugging purposes only. But it should be safe to use in production.
 */ export async function printAccountOwners ( connection , plainObj ) { try { if ( typeof plainObj !== "object" ) { throw new Error ( "printAccountOwners only works on objects" ) ; } else { let relativePath ; const callStack = new Error ( ) . stack ?. split ( "\n" ) ; if ( callStack ) { let expectIndex = callStack . findIndex ( ( l ) => l . includes ( `at ${ printAccountOwners . name }` ) ) ; const debugAccountOwnersIndex = callStack . findIndex ( ( l ) => l . includes ( `at debugAccountOwners` ) ) ; if ( debugAccountOwnersIndex > expectIndex ) { expectIndex = debugAccountOwnersIndex ; } if ( expectIndex > 0 && typeof process !== "undefined" && typeof window === "undefined" ) { const maybeProcess = process ; const targetLine = callStack [ expectIndex + 1 ] ; if ( targetLine ) { const cwd = maybeProcess . cwd ?. ( ) || "/" ; const targetLineAfterCwd = targetLine . substring ( targetLine . indexOf ( cwd ) + cwd . length ) ; if ( targetLineAfterCwd . length > 0 ) { relativePath = targetLineAfterCwd . substring ( 1 ) . replace ( /\)$/ , "" ) ; } } } } if ( ! connection ) { return ; } if ( relativePath ) { console . log ( relativePath , await _transformAccountOwners ( plainObj , connection ) ) ; } else { console . log ( await _transformAccountOwners ( plainObj , connection ) ) ; } } } catch ( e ) { console . error ( "Error in printAccountOwners:" , e ) ; } } /**
 * Just in case some browser compilation doesn't polyfill a fake process
 */ type MaybeProcess = { cwd ? : ( ) => string ; } ; /**
 * This is a patched version of web3's getMultipleAccountsInfo.
 *
 * When it reaches an account that doesn't exist, it will just continue instead
 * of giving up.
 *
 * WARNING: This may break silently if web3 changes its api. This is only
 * intended for debugging purposes only.
 */ async function gracefulGetMultipleAccountsInfo ( connection , publicKeys ) { try { const unknownConection = connection as ConnectionWithGetMultipleAccounts ; const rpcRequest = unknownConection . _rpcRequest ; if ( typeof rpcRequest !== "function" ) { console . error ( "_rpcRequest is not a function. Maybe web3 changed?" ) ; return [ ] ; } const unsafeRes = await rpcRequest ( "getMultipleAccounts" , [ publicKeys . map ( ( key ) => key . toBase58 ( ) ) , { encoding : "base64" , commitment : "processed" } , ] ) ; const value = unsafeRes ?. result ?. value ; if ( value ) { return value ; } return [ ] ; } catch ( e ) { console . error ( "Error in gracefulGetMultipleAccountsInfo:" , e ) ; return [ ] ; } } type PublicKeyBase58 = string ; type GetMultipleAccountsArgs = [ PublicKeyBase58 [ ] , { encoding : "base64" ; commitment : "processed" ; } ] ; interface ConnectionWithGetMultipleAccounts { _rpcRequest ? : ( method : "getMultipleAccounts" , args : GetMultipleAccountsArgs ) => Promise < undefined | { result ? : { value : ( AccountInfo < Buffer > | null ) [ ] ; } ; } > ; } /**
 * Lots of complexity in this function. But using getMultipleAccountsInfo is
 * very important. Even locally, it reduces the time from 300ms to 40ms. On a
 * remote RPC server, it would probably save much more time.
 */ const _transformAccountOwners = async ( plainObjWithAddressesAndBignums , connection ) : Promise < unknown > => { const result = { } ; if ( ! plainObjWithAddressesAndBignums || typeof plainObjWithAddressesAndBignums !== "object" ) { return plainObjWithAddressesAndBignums ; } const base58ToResultKey = { } ; const base58ToLookup = [ ] ; const asyncKeys = [ ] ; const asyncPromises = [ ] ; for ( const [ key , value ] of Object . entries ( plainObjWithAddressesAndBignums ) ) { if ( value && value . toBase58 && typeof value . toBase58 === "function" ) { const base58 = value . toBase58 ( ) ; base58ToResultKey [ base58 ] = key ; const pubKey = new PublicKey ( base58 ) ; if ( ! base58ToLookup . includes ( pubKey ) ) { base58ToLookup . push ( pubKey ) ; } } else if ( value && value . toNumber && typeof value . toNumber === "function" ) { result [ key ] = value . toNumber ( ) ; } else if ( isPlainObject ( value ) && value ) { asyncKeys . push ( key ) ; asyncPromises . push ( _transformAccountOwners ( value , connection ) ) ; } else { result [ key ] = value ; } } if ( Object . keys ( base58ToResultKey ) . length > 0 ) { const accountInfos = await gracefulGetMultipleAccountsInfo ( connection , base58ToLookup ) ; for ( const [ base58 , resultKey ] of Object . entries ( base58ToResultKey ) ) { const lookupIndex = base58ToLookup . findIndex ( ( p ) => p . toBase58 ( ) === base58 ) ; if ( lookupIndex >= 0 ) { const accountInfo = accountInfos [ lookupIndex ] ; const owner = accountInfo ?. owner ; if ( owner ) { result [ resultKey ] = `${ base58 } (${ owner })` ; } else { result [ resultKey ] = `${ base58 }` ; } } else { result [ resultKey ] = `${ base58 }` ; } } } const asyncValues = await Promise . all ( asyncPromises ) ; for ( let i = 0 ; i < asyncKeys . length ; i ++ ) { result [ asyncKeys [ i ] ! ! ] = asyncValues [ i ] ; } return result ; } ; function isPlainObject ( obj ) { return ( typeof obj === "object" && obj !== null && obj . constructor === Object && Object . prototype . toString . call ( obj ) === "[object Object]" ) ; }