import { PublicKey } from '@solana/web3.js' ; import { useEffect , useState } from 'react' ; import { getAccountTypes , GovernanceAccount , GovernanceAccountClass , } from '@solana/spl-governance' ; import { GovernanceAccountParser } from '@solana/spl-governance' ; import { GenericAccountParser , ParsedAccountBase } from '@oyster/common' ; import { MemcmpFilter } from '@solana/spl-governance' ; import { useAccountChangeTracker } from '../contexts/GovernanceContext' ; import { useRpcContext } from './useRpcContext' ; import { none , Option , some } from '../tools/option' ; import { getGovernanceAccounts } from '@solana/spl-governance' ; import { ProgramAccount } from '@solana/spl-governance' ; import { arrayToRecord } from '../tools/script' ; export function useGovernanceAccountByPubkey < TAccount extends GovernanceAccount > ( accountClass , pubkey ) { const [ account , setAccount ] = useState < Option < ProgramAccount < TAccount > > > ( ) ; const { connection , endpoint } = useRpcContext ( ) ; const accountChangeTracker = useAccountChangeTracker ( ) ; const getByPubkey = pubkey ?. toBase58 ( ) ; useEffect ( ( ) => { if ( ! pubkey ) { return ; } const sub = ( async ( ) => { try { const accountInfo = await connection . getAccountInfo ( pubkey ) ; if ( accountInfo ) { const loadedAccount = GovernanceAccountParser ( accountClass ) ( pubkey , accountInfo ! ) ; setAccount ( some ( loadedAccount ) ) ; } else { setAccount ( none ( ) ) ; } } catch ( ex ) { console . error ( `Can't load ${ pubkey . toBase58 ( ) } account` , ex ) ; setAccount ( none ( ) ) ; } return accountChangeTracker . onAccountUpdated ( update => { if ( update . pubkey === getByPubkey ) { const account = GovernanceAccountParser ( accountClass ) ( new PublicKey ( update . pubkey ) , update . accountInfo ) as ProgramAccount < TAccount > ; setAccount ( some ( account ) ) ; } } ) ; } ) ( ) ; return ( ) => { sub . then ( dispose => dispose ( ) ) ; } ; } , [ getByPubkey , connection , endpoint ] ) ; return account ; } export function useGovernanceAccountByPda < TAccount extends GovernanceAccount > ( accountClass , getPda , pdaArgs ) { const [ pda , setPda ] = useState < PublicKey | undefined > ( ) ; const pdaArgsKey = JSON . stringify ( pdaArgs ) ; useEffect ( ( ) => { ( async ( ) => { const resolvedPda = await getPda ( ) ; setPda ( resolvedPda ) ; } ) ( ) ; } , [ pdaArgsKey ] ) ; return useGovernanceAccountByPubkey < TAccount > ( accountClass , pda ) ; } export function useGovernanceAccountsByFilter < TAccount extends GovernanceAccount > ( accountClass , filters ) { const [ accounts , setAccounts ] = useState < Record < string , ProgramAccount < TAccount > > > ( { } ) ; const { connection , endpoint , programId } = useRpcContext ( ) ; const accountChangeTracker = useAccountChangeTracker ( ) ; const filterKey = JSON . stringify ( filters ) ; useEffect ( ( ) => { if ( filters . some ( f => ! f ) ) { return ; } const queryFilters = filters . map ( f => f ! ) ; const accountTypes = getAccountTypes ( accountClass ) ; const sub = ( async ( ) => { try { const loadedAccounts = await getGovernanceAccounts < TAccount > ( connection , programId , ( accountClass as any ) as new ( args ) => TAccount , queryFilters ) ; setAccounts ( arrayToRecord ( loadedAccounts , a => a . pubkey . toBase58 ( ) ) ) ; } catch ( ex ) { console . error ( `Can't load ${ accountClass . name }` , ex ) ; setAccounts ( { } ) ; } const disposeUpdateTracker = accountChangeTracker . onAccountUpdated ( update => { if ( accountTypes . some ( at => update . accountType === at ) ) { const isMatch = ! queryFilters . some ( f => ! f . isMatch ( update . accountInfo . data ) ) ; const account = GovernanceAccountParser ( accountClass ) ( new PublicKey ( update . pubkey ) , update . accountInfo ) as ProgramAccount < TAccount > ; setAccounts ( ( acts ) => { if ( isMatch ) { return { ... acts , [ update . pubkey ] : account , } ; } else if ( acts [ update . pubkey ] ) { return { ... Object . keys ( acts ) . filter ( k => k !== update . pubkey ) . reduce ( ( res , key ) => { res [ key ] = acts [ key ] ; return res ; } , { } as any ) , } ; } else { return acts ; } } ) ; } } ) ; const disposeRemoveTracker = accountChangeTracker . onAccountRemoved ( remove => { if ( accountTypes . some ( at => remove . accountType === at ) ) { setAccounts ( ( acts ) => { if ( acts [ remove . pubkey ] ) { return { ... Object . keys ( acts ) . filter ( k => k !== remove . pubkey ) . reduce ( ( res , key ) => { res [ key ] = acts [ key ] ; return res ; } , { } as any ) , } ; } else { return acts ; } } ) ; } } ) ; return { disposeRemoveTracker , disposeUpdateTracker } ; } ) ( ) ; return ( ) => { sub . then ( ( { disposeRemoveTracker , disposeUpdateTracker } ) => { disposeRemoveTracker ( ) ; disposeUpdateTracker ( ) ; } ) ; } ; } , [ filterKey , connection , endpoint ] ) ; return Object . values ( accounts ) ; } export function useGovernanceAccountByFilter < TAccount extends GovernanceAccount > ( accountClass , filters ) { const accounts = useGovernanceAccountsByFilter < TAccount > ( accountClass , filters ) ; if ( accounts . length === 0 ) { return undefined ; } if ( accounts . length === 1 ) { return accounts [ 0 ] ; } throw new Error ( `Filters ${ filters } returned multiple accounts ${ accounts } for ${ accountClass . name } while a single result was expected` ) ; } export function useAccountByPda ( getPda , pdaArgs ) { const { connection } = useRpcContext ( ) ; const [ account , setAccount ] = useState < Option < ParsedAccountBase > > ( ) ; const pdaArgsKey = JSON . stringify ( pdaArgs ) ; useEffect ( ( ) => { const sub = ( async ( ) => { const pdaPk = await getPda ( ) ; let subId = 0 ; if ( pdaPk ) { try { const accountInfo = await connection . getAccountInfo ( pdaPk ) ; if ( accountInfo ) { setAccount ( some ( GenericAccountParser ( pdaPk , accountInfo ) ) ) ; } else { setAccount ( none ( ) ) ; } } catch ( ex ) { console . error ( `Can't load ${ pdaPk . toBase58 ( ) } account` , ex ) ; setAccount ( none ( ) ) ; } subId = connection . onAccountChange ( pdaPk , a => setAccount ( some ( GenericAccountParser ( pdaPk , a ) ) ) ) ; } else { setAccount ( none ( ) ) ; } return ( ) => { subId > 0 && connection . removeAccountChangeListener ( subId ) ; } ; } ) ( ) ; return ( ) => { sub . then ( dispose => dispose ( ) ) ; } ; } , [ pdaArgsKey ] ) ; return account ; }