import { Adapter , SendTransactionOptions , WalletError , WalletName , WalletNotConnectedError , WalletNotReadyError , WalletReadyState , } from '@solana/wallet-adapter-base' ; import { Connection , PublicKey , Transaction } from '@solana/web3.js' ; import React , { FC , ReactNode , useCallback , useEffect , useMemo , useRef , useState } from 'react' ; import { WalletNotSelectedError } from './errors' ; import { useLocalStorage } from './useLocalStorage' ; import { Wallet , WalletContext } from './useWallet' ; export interface WalletProviderProps { children : ReactNode ; wallets : Adapter [ ] ; autoConnect ? : boolean ; onError ? : ( error : WalletError ) => void ; localStorageKey ? : string ; } const initialState = { wallet : null , adapter : null , publicKey : null , connected : false , } ; export const WalletProvider = ( { children , wallets : adapters , autoConnect = false , onError , localStorageKey = 'walletName' , } ) => { const [ name , setName ] = useLocalStorage < WalletName | null > ( localStorageKey , null ) ; const [ { wallet , adapter , publicKey , connected } , setState ] = useState ( initialState ) ; const readyState = adapter ?. readyState || WalletReadyState . Unsupported ; const [ connecting , setConnecting ] = useState ( false ) ; const [ disconnecting , setDisconnecting ] = useState ( false ) ; const isConnecting = useRef ( false ) ; const isDisconnecting = useRef ( false ) ; const isUnloading = useRef ( false ) ; const [ wallets , setWallets ] = useState ( ( ) => adapters . map ( ( adapter ) => ( { adapter , readyState : adapter . readyState , } ) ) ) ; useEffect ( ( ) => { function handleReadyStateChange ( this , readyState ) { setWallets ( ( prevWallets ) => { const walletIndex = prevWallets . findIndex ( ( { adapter } ) => adapter . name === this . name ) ; if ( walletIndex === - 1 ) return prevWallets ; return [ ... prevWallets . slice ( 0 , walletIndex ) , { ... prevWallets [ walletIndex ] , readyState } , ... prevWallets . slice ( walletIndex + 1 ) , ] ; } ) ; } for ( const adapter of adapters ) { adapter . on ( 'readyStateChange' , handleReadyStateChange , adapter ) ; } return ( ) => { for ( const adapter of adapters ) { adapter . off ( 'readyStateChange' , handleReadyStateChange , adapter ) ; } } ; } , [ adapters ] ) ; useEffect ( ( ) => { const wallet = wallets . find ( ( { adapter } ) => adapter . name === name ) ; if ( wallet ) { setState ( { wallet , adapter : wallet . adapter , connected : wallet . adapter . connected , publicKey : wallet . adapter . publicKey , } ) ; } else { setState ( initialState ) ; } } , [ name , wallets ] ) ; useEffect ( ( ) => { if ( isConnecting . current || connecting || connected || ! autoConnect || ! adapter || ! ( readyState === WalletReadyState . Installed || readyState === WalletReadyState . Loadable ) ) return ; ( async function ( ) { isConnecting . current = true ; setConnecting ( true ) ; try { await adapter . connect ( ) ; } catch ( error ) { setName ( null ) ; } finally { setConnecting ( false ) ; isConnecting . current = false ; } } ) ( ) ; } , [ isConnecting , connecting , connected , autoConnect , adapter , readyState ] ) ; useEffect ( ( ) => { function listener ( ) { isUnloading . current = true ; } window . addEventListener ( 'beforeunload' , listener ) ; return ( ) => window . removeEventListener ( 'beforeunload' , listener ) ; } , [ isUnloading ] ) ; const handleConnect = useCallback ( ( ) => { if ( ! adapter ) return ; setState ( ( state ) => ( { ... state , connected : adapter . connected , publicKey : adapter . publicKey } ) ) ; } , [ adapter ] ) ; const handleDisconnect = useCallback ( ( ) => { if ( ! isUnloading . current ) setName ( null ) ; } , [ isUnloading ] ) ; const handleError = useCallback ( ( error ) => { if ( ! isUnloading . current ) ( onError || console . error ) ( error ) ; return error ; } , [ isUnloading , onError ] ) ; useEffect ( ( ) => { if ( adapter ) { adapter . on ( 'connect' , handleConnect ) ; adapter . on ( 'disconnect' , handleDisconnect ) ; adapter . on ( 'error' , handleError ) ; return ( ) => { adapter . off ( 'connect' , handleConnect ) ; adapter . off ( 'disconnect' , handleDisconnect ) ; adapter . off ( 'error' , handleError ) ; } ; } } , [ adapter , handleConnect , handleDisconnect , handleError ] ) ; useEffect ( ( ) => { return ( ) => { adapter ?. disconnect ( ) ; } ; } , [ adapter ] ) ; const connect = useCallback ( async ( ) => { if ( isConnecting . current || connecting || disconnecting || connected ) return ; if ( ! adapter ) throw handleError ( new WalletNotSelectedError ( ) ) ; if ( ! ( readyState === WalletReadyState . Installed || readyState === WalletReadyState . Loadable ) ) { setName ( null ) ; if ( typeof window !== 'undefined' ) { window . open ( adapter . url , '_blank' ) ; } throw handleError ( new WalletNotReadyError ( ) ) ; } isConnecting . current = true ; setConnecting ( true ) ; try { await adapter . connect ( ) ; } catch ( error ) { setName ( null ) ; throw error ; } finally { setConnecting ( false ) ; isConnecting . current = false ; } } , [ isConnecting , connecting , disconnecting , connected , adapter , readyState , handleError ] ) ; const disconnect = useCallback ( async ( ) => { if ( isDisconnecting . current || disconnecting ) return ; if ( ! adapter ) return setName ( null ) ; isDisconnecting . current = true ; setDisconnecting ( true ) ; try { await adapter . disconnect ( ) ; } catch ( error ) { setName ( null ) ; throw error ; } finally { setDisconnecting ( false ) ; isDisconnecting . current = false ; } } , [ isDisconnecting , disconnecting , adapter ] ) ; const sendTransaction = useCallback ( async ( transaction , connection , options ? ) => { if ( ! adapter ) throw handleError ( new WalletNotSelectedError ( ) ) ; if ( ! connected ) throw handleError ( new WalletNotConnectedError ( ) ) ; return await adapter . sendTransaction ( transaction , connection , options ) ; } , [ adapter , handleError , connected ] ) ; const signTransaction = useMemo ( ( ) => adapter && 'signTransaction' in adapter ? async ( transaction ) : Promise < Transaction > => { if ( ! connected ) throw handleError ( new WalletNotConnectedError ( ) ) ; return await adapter . signTransaction ( transaction ) ; } : undefined , [ adapter , handleError , connected ] ) ; const signAllTransactions = useMemo ( ( ) => adapter && 'signAllTransactions' in adapter ? async ( transactions ) : Promise < Transaction [ ] > => { if ( ! connected ) throw handleError ( new WalletNotConnectedError ( ) ) ; return await adapter . signAllTransactions ( transactions ) ; } : undefined , [ adapter , handleError , connected ] ) ; const signMessage = useMemo ( ( ) => adapter && 'signMessage' in adapter ? async ( message ) : Promise < Uint8Array > => { if ( ! connected ) throw handleError ( new WalletNotConnectedError ( ) ) ; return await adapter . signMessage ( message ) ; } : undefined , [ adapter , handleError , connected ] ) ; return ( < WalletContext . Provider value = { { autoConnect , wallets , wallet , publicKey , connected , connecting , disconnecting , select : setName , connect , disconnect , sendTransaction , signTransaction , signAllTransactions , signMessage , } } > { children } < /WalletContext.Provider>); } ;