import React , { useContext , useState , useEffect } from "react" ; import * as assert from "assert" ; import { useAsync } from "react-async-hook" ; import { Provider , BN } from "@project-serum/anchor" ; import { PublicKey , Account } from "@solana/web3.js" ; import { MintInfo , AccountInfo as TokenAccount , Token , TOKEN_PROGRAM_ID , } from "@solana/spl-token" ; import { getOwnedAssociatedTokenAccounts , parseTokenAccountData , } from "../utils/tokens" ; import { SOL_MINT } from "../utils/pubkeys" ; export type TokenContext = { provider : Provider ; } ; const _TokenContext = React . createContext < TokenContext | null > ( null ) ; export function TokenContextProvider ( props ) { const provider = props . provider ; const [ , setRefresh ] = useState ( 0 ) ; useEffect ( ( ) => { if ( ! provider . wallet . publicKey ) { _OWNED_TOKEN_ACCOUNTS_CACHE . length = 0 ; setRefresh ( ( r ) => r + 1 ) ; return ; } getOwnedAssociatedTokenAccounts ( provider . connection , provider . wallet . publicKey ) . then ( ( accs ) => { if ( accs ) { _OWNED_TOKEN_ACCOUNTS_CACHE . push ( ... accs ) ; setRefresh ( ( r ) => r + 1 ) ; } } ) ; provider . connection . getAccountInfo ( provider . wallet . publicKey ) . then ( ( acc ) => { if ( acc ) { _OWNED_TOKEN_ACCOUNTS_CACHE . push ( { publicKey : provider . wallet . publicKey , account : { amount : new BN ( acc . lamports ) , mint : SOL_MINT , } , } ) ; setRefresh ( ( r ) => r + 1 ) ; } } ) ; } , [ provider . wallet . publicKey , provider . connection ] ) ; return ( < _TokenContext . Provider value = { { provider , } } > { props . children } < /_TokenContext.Provider>); } function useTokenContext ( ) { const ctx = useContext ( _TokenContext ) ; if ( ctx === null ) { throw new Error ( "Context not available" ) ; } return ctx ; } export function useOwnedTokenAccount ( mint ? ) { const { provider } = useTokenContext ( ) ; const [ , setRefresh ] = useState ( 0 ) ; const tokenAccounts = _OWNED_TOKEN_ACCOUNTS_CACHE . filter ( ( account ) => mint && account . account . mint . equals ( mint ) ) ; tokenAccounts . sort ( ( a , b ) => a . account . amount > b . account . amount ? - 1 : a . account . amount < b . account . amount ? 1 : 0 ) ; let tokenAccount = tokenAccounts [ 0 ] ; const isSol = mint ?. equals ( SOL_MINT ) ; useEffect ( ( ) => { let listener ; if ( tokenAccount && isSol ) { listener = provider . connection . onAccountChange ( provider . wallet . publicKey , ( info ) => { const token = { amount : new BN ( info . lamports ) , mint : SOL_MINT , } as TokenAccount ; if ( token . amount !== tokenAccount . account . amount ) { const index = _OWNED_TOKEN_ACCOUNTS_CACHE . indexOf ( tokenAccount ) ; assert . ok ( index >= 0 ) ; _OWNED_TOKEN_ACCOUNTS_CACHE [ index ] . account = token ; setRefresh ( ( r ) => r + 1 ) ; } } ) ; } else if ( tokenAccount ) { listener = provider . connection . onAccountChange ( tokenAccount . publicKey , ( info ) => { if ( info . data . length !== 0 ) { try { const token = parseTokenAccountData ( info . data ) ; if ( token . amount !== tokenAccount . account . amount ) { const index = _OWNED_TOKEN_ACCOUNTS_CACHE . indexOf ( tokenAccount ) ; assert . ok ( index >= 0 ) ; _OWNED_TOKEN_ACCOUNTS_CACHE [ index ] . account = token ; setRefresh ( ( r ) => r + 1 ) ; } } catch ( error ) { console . log ( "Failed to decode token AccountInfo" ) ; } } } ) ; } return ( ) => { if ( listener ) { provider . connection . removeAccountChangeListener ( listener ) ; } } ; } , [ provider . connection , tokenAccount ] ) ; if ( mint === undefined ) { return undefined ; } if ( ! isSol && tokenAccounts . length === 0 ) { return null ; } return tokenAccount ; } export function useMint ( mint ? ) { const { provider } = useTokenContext ( ) ; const asyncMintInfo = useAsync ( async ( ) => { if ( ! mint ) { return undefined ; } if ( _MINT_CACHE . get ( mint . toString ( ) ) ) { return _MINT_CACHE . get ( mint . toString ( ) ) ; } const mintClient = new Token ( provider . connection , mint , TOKEN_PROGRAM_ID , new Account ( ) ) ; const mintInfo = mintClient . getMintInfo ( ) ; _MINT_CACHE . set ( mint . toString ( ) , mintInfo ) ; return mintInfo ; } , [ provider . connection , mint ] ) ; if ( asyncMintInfo . result ) { return asyncMintInfo . result ; } return undefined ; } export function setMintCache ( pk , account ) { _MINT_CACHE . set ( pk . toString ( ) , new Promise ( ( resolve ) => resolve ( account ) ) ) ; } const _OWNED_TOKEN_ACCOUNTS_CACHE = [ ] ; const _MINT_CACHE = new Map < string , Promise < MintInfo > > ( [ [ SOL_MINT . toString ( ) , { decimals : 9 } ] , ] ) ;