import React , { useContext , useEffect , useMemo , useState } from 'react' ; import { AccountInfo , Connection , KeyedAccountInfo , PublicKey , } from '@solana/web3.js' ; import { useConnection , useConnectionConfig } from '@oyster/common' ; import { GovernanceAccountParser } from '@solana/spl-governance' ; import { GovernanceAccountType , Realm } from '@solana/spl-governance' ; import { getRealms } from '@solana/spl-governance' ; import { EventEmitter } from 'eventemitter3' ; import { useLocation } from 'react-router-dom' ; import { PROGRAM_VERSION } from '@solana/spl-governance' ; import { getGovernanceProgramVersion , ProgramAccount } from '@solana/spl-governance' ; import { arrayToRecord } from '../tools/script' ; export interface GovernanceContextState { realms : Record < string , ProgramAccount < Realm > > ; changeTracker : AccountChangeTracker ; programId : string ; programVersion : number ; } class AccountRemovedEventArgs { pubkey : string ; accountType : GovernanceAccountType ; constructor ( pubkey , accountType ) { this . pubkey = pubkey ; this . accountType = accountType ; } } class AccountUpdatedEventArgs { pubkey : string ; accountType : GovernanceAccountType ; accountInfo : AccountInfo < Buffer > ; constructor ( pubkey , accountType , accountInfo ) { this . pubkey = pubkey ; this . accountType = accountType ; this . accountInfo = accountInfo ; } } class AccountChangeTracker { private removeEmitter = new EventEmitter ( ) ; private updateEmitter = new EventEmitter ( ) ; onAccountRemoved ( callback ) { this . removeEmitter . on ( AccountRemovedEventArgs . name , callback ) ; return ( ) => this . removeEmitter . removeListener ( AccountRemovedEventArgs . name , callback ) ; } notifyAccountRemoved ( pubkey , accountType ) { this . removeEmitter . emit ( AccountRemovedEventArgs . name , new AccountRemovedEventArgs ( pubkey , accountType ) ) ; } onAccountUpdated ( callback ) { this . updateEmitter . on ( AccountUpdatedEventArgs . name , callback ) ; return ( ) => this . updateEmitter . removeListener ( AccountUpdatedEventArgs . name , callback ) ; } notifyAccountUpdated ( pubkey , accountType , accountInfo ) { this . updateEmitter . emit ( AccountUpdatedEventArgs . name , new AccountUpdatedEventArgs ( pubkey , accountType , accountInfo ) ) ; } async fetchAndNotifyAccountUpdated ( connection , pubkey ) { const info = await connection . getAccountInfo ( pubkey , 'recent' ) ; if ( info ) { this . notifyAccountUpdated ( pubkey . toBase58 ( ) , info . data [ 0 ] , info ) ; } else { console . error ( `Can't fetch account for ${ pubkey }` ) ; } } } export const GovernanceContext = React . createContext < GovernanceContextState | null > ( null ) ; export default function GovernanceProvider ( { children = null as any } ) { const connection = useConnection ( ) ; const { endpoint , env } = useConnectionConfig ( ) ; const location = useLocation ( ) ; const programId = useMemo ( ( ) => { const params = new URLSearchParams ( location . search ) ; return ( params . get ( 'programId' ) ?? 'GovER5Lthms3bLBqWub97yVrMmEogzX7xNjdXpPPCVZw' ) ; } , [ location ] ) ; const [ realms , setRealms ] = useState ( { } ) ; const [ changeTracker ] = useState ( new AccountChangeTracker ( ) ) ; const [ programVersion , setProgramVersion ] = useState ( PROGRAM_VERSION ) ; useEffect ( ( ) => { const sub = ( async ( ) => { const programPk = new PublicKey ( programId ) ; try { const loadedRealms = await getRealms ( connection , programPk ) ; setRealms ( arrayToRecord ( loadedRealms , r => r . pubkey . toBase58 ( ) ) ) ; } catch ( ex ) { console . error ( "Can't load Realms" , ex ) ; setRealms ( { } ) ; } return connection . onProgramAccountChange ( programPk , async ( info ) => { if ( info . accountInfo . data [ 0 ] === GovernanceAccountType . Realm ) { const realm = GovernanceAccountParser ( Realm ) ( info . accountId , info . accountInfo ) ; setRealms ( ( objs ) => ( { ... objs , [ info . accountId . toBase58 ( ) ] : realm , } ) ) ; } changeTracker . notifyAccountUpdated ( info . accountId . toBase58 ( ) , info . accountInfo . data [ 0 ] , info . accountInfo ) ; } ) ; } ) ( ) ; return ( ) => { sub . then ( id => connection . removeProgramAccountChangeListener ( id ) ) ; } ; } , [ connection , programId , endpoint ] ) ; useEffect ( ( ) => { getGovernanceProgramVersion ( connection , new PublicKey ( programId ) , env ) . then ( pVersion => { console . log ( 'PROGRAM VERSION' , { pVersion , env } ) ; setProgramVersion ( pVersion ) ; } ) ; } , [ env , connection , programId ] ) ; return ( < GovernanceContext . Provider value = { { realms , changeTracker , programVersion , programId , } } > { children } < /GovernanceContext.Provider>); } export function useGovernanceContext ( ) { const context = useContext ( GovernanceContext ) ; return context as GovernanceContextState ; } export function useProgramInfo ( ) { const context = useGovernanceContext ( ) ; return { programVersion : context . programVersion , programId : context . programId , } ; } export function useAccountChangeTracker ( ) { const context = useGovernanceContext ( ) ; return context . changeTracker ; } export function useRealms ( ) { const ctx = useGovernanceContext ( ) ; return Object . values ( ctx . realms ) ; } export function useRealm ( realm ) { const ctx = useGovernanceContext ( ) ; return realm && ctx . realms [ realm . toBase58 ( ) ] ; }