import type { WalletAdapterNetwork, WalletName } from '@solana/wallet-adapter-base'; import { BaseMessageSignerWalletAdapter, WalletConfigError, WalletConnectionError, WalletDisconnectedError, WalletDisconnectionError, WalletError, WalletLoadError, WalletNotConnectedError, WalletNotReadyError, WalletPublicKeyError, WalletReadyState, WalletSendTransactionError, WalletSignMessageError, WalletSignTransactionError, isIosAndRedirectable, isVersionedTransaction, scopePollingDetectionStrategy, type SendTransactionOptions, } from '@solana/wallet-adapter-base'; import type { Transaction, TransactionVersion, VersionedTransaction } from '@solana/web3.js'; import { PublicKey, type Connection, type TransactionSignature } from '@solana/web3.js'; import type { default as Solflare } from '@solflare-wallet/sdk'; import { detectAndRegisterSolflareMetaMaskWallet } from './metamask/detect.js'; interface SolflareWindow extends Window { solflare?: { isSolflare?: boolean; }; SolflareApp?: unknown; } declare const window: SolflareWindow; export interface SolflareWalletAdapterConfig { network?: WalletAdapterNetwork; } export const SolflareWalletName = 'Solflare' as WalletName<'Solflare'>; export class SolflareWalletAdapter extends BaseMessageSignerWalletAdapter { name = SolflareWalletName; url = 'https://solflare.com'; icon = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIGlkPSJTIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA1MCA1MCI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOiMwMjA1MGE7c3Ryb2tlOiNmZmVmNDY7c3Ryb2tlLW1pdGVybGltaXQ6MTA7c3Ryb2tlLXdpZHRoOi41cHg7fS5jbHMtMntmaWxsOiNmZmVmNDY7fTwvc3R5bGU+PC9kZWZzPjxyZWN0IGNsYXNzPSJjbHMtMiIgeD0iMCIgd2lkdGg9IjUwIiBoZWlnaHQ9IjUwIiByeD0iMTIiIHJ5PSIxMiIvPjxwYXRoIGNsYXNzPSJjbHMtMSIgZD0iTTI0LjIzLDI2LjQybDIuNDYtMi4zOCw0LjU5LDEuNWMzLjAxLDEsNC41MSwyLjg0LDQuNTEsNS40MywwLDEuOTYtLjc1LDMuMjYtMi4yNSw0LjkzbC0uNDYuNS4xNy0xLjE3Yy42Ny00LjI2LS41OC02LjA5LTQuNzItNy40M2wtNC4zLTEuMzhoMFpNMTguMDUsMTEuODVsMTIuNTIsNC4xNy0yLjcxLDIuNTktNi41MS0yLjE3Yy0yLjI1LS43NS0zLjAxLTEuOTYtMy4zLTQuNTF2LS4wOGgwWk0xNy4zLDMzLjA2bDIuODQtMi43MSw1LjM0LDEuNzVjMi44LjkyLDMuNzYsMi4xMywzLjQ2LDUuMThsLTExLjY1LTQuMjJoMFpNMTMuNzEsMjAuOTVjMC0uNzkuNDItMS41NCwxLjEzLTIuMTcuNzUsMS4wOSwyLjA1LDIuMDUsNC4wOSwyLjcxbDQuNDIsMS40Ni0yLjQ2LDIuMzgtNC4zNC0xLjQyYy0yLS42Ny0yLjg0LTEuNjctMi44NC0yLjk2TTI2LjgyLDQyLjg3YzkuMTgtNi4wOSwxNC4xMS0xMC4yMywxNC4xMS0xNS4zMiwwLTMuMzgtMi01LjI2LTYuNDMtNi43MmwtMy4zNC0xLjEzLDkuMTQtOC43Ny0xLjg0LTEuOTYtMi43MSwyLjM4LTEyLjgxLTQuMjJjLTMuOTcsMS4yOS04Ljk3LDUuMDktOC45Nyw4Ljg5LDAsLjQyLjA0LjgzLjE3LDEuMjktMy4zLDEuODgtNC42MywzLjYzLTQuNjMsNS44LDAsMi4wNSwxLjA5LDQuMDksNC41NSw1LjIybDIuNzUuOTItOS41Miw5LjE0LDEuODQsMS45NiwyLjk2LTIuNzEsMTQuNzMsNS4yMmgwWiIvPjwvc3ZnPg=='; supportedTransactionVersions: ReadonlySet = new Set(['legacy', 0]); private _connecting: boolean; private _wallet: Solflare | null; private _publicKey: PublicKey | null; private _config: SolflareWalletAdapterConfig; private _readyState: WalletReadyState = typeof window === 'undefined' || typeof document === 'undefined' ? WalletReadyState.Unsupported : WalletReadyState.Loadable; constructor(config: SolflareWalletAdapterConfig = {}) { super(); this._connecting = false; this._publicKey = null; this._wallet = null; this._config = config; if (this._readyState !== WalletReadyState.Unsupported) { scopePollingDetectionStrategy(() => { if (window.solflare?.isSolflare || window.SolflareApp) { this._readyState = WalletReadyState.Installed; this.emit('readyStateChange', this._readyState); return true; } return false; }); detectAndRegisterSolflareMetaMaskWallet(); } } get publicKey() { return this._publicKey; } get connecting() { return this._connecting; } get connected() { return !!this._wallet?.connected; } get readyState() { return this._readyState; } async autoConnect(): Promise { // Skip autoconnect in the Loadable state on iOS // We can't redirect to a universal link without user input if (!(this.readyState === WalletReadyState.Loadable && isIosAndRedirectable())) { await this.connect(); } } async connect(): Promise { try { if (this.connected || this.connecting) return; if (this._readyState !== WalletReadyState.Loadable && this._readyState !== WalletReadyState.Installed) throw new WalletNotReadyError(); // redirect to the Solflare /browse universal link // this will open the current URL in the Solflare in-wallet browser if (this.readyState === WalletReadyState.Loadable && isIosAndRedirectable()) { const url = encodeURIComponent(window.location.href); const ref = encodeURIComponent(window.location.origin); window.location.href = `https://solflare.com/ul/v1/browse/${url}?ref=${ref}`; return; } let SolflareClass: typeof Solflare; try { SolflareClass = (await import('@solflare-wallet/sdk')).default; } catch (error: any) { throw new WalletLoadError(error?.message, error); } let wallet: Solflare; try { wallet = new SolflareClass({ network: this._config.network }); } catch (error: any) { throw new WalletConfigError(error?.message, error); } this._connecting = true; if (!wallet.connected) { try { await wallet.connect(); } catch (error: any) { throw new WalletConnectionError(error?.message, error); } } if (!wallet.publicKey) throw new WalletConnectionError(); let publicKey: PublicKey; try { publicKey = new PublicKey(wallet.publicKey.toBytes()); } catch (error: any) { throw new WalletPublicKeyError(error?.message, error); } wallet.on('disconnect', this._disconnected); wallet.on('accountChanged', this._accountChanged); this._wallet = wallet; this._publicKey = publicKey; this.emit('connect', publicKey); } catch (error: any) { this.emit('error', error); throw error; } finally { this._connecting = false; } } async disconnect(): Promise { const wallet = this._wallet; if (wallet) { wallet.off('disconnect', this._disconnected); wallet.off('accountChanged', this._accountChanged); this._wallet = null; this._publicKey = null; try { await wallet.disconnect(); } catch (error: any) { this.emit('error', new WalletDisconnectionError(error?.message, error)); } } this.emit('disconnect'); } async sendTransaction( transaction: T, connection: Connection, options: SendTransactionOptions = {} ): Promise { try { const wallet = this._wallet; if (!wallet) throw new WalletNotConnectedError(); try { const { signers, ...sendOptions } = options; if (isVersionedTransaction(transaction)) { signers?.length && transaction.sign(signers); } else { transaction = (await this.prepareTransaction(transaction, connection, sendOptions)) as T; signers?.length && (transaction as Transaction).partialSign(...signers); } sendOptions.preflightCommitment = sendOptions.preflightCommitment || connection.commitment; return await wallet.signAndSendTransaction(transaction, sendOptions); } catch (error: any) { if (error instanceof WalletError) throw error; throw new WalletSendTransactionError(error?.message, error); } } catch (error: any) { this.emit('error', error); throw error; } } async signTransaction(transaction: T): Promise { try { const wallet = this._wallet; if (!wallet) throw new WalletNotConnectedError(); try { return ((await wallet.signTransaction(transaction)) as T) || transaction; } catch (error: any) { throw new WalletSignTransactionError(error?.message, error); } } catch (error: any) { this.emit('error', error); throw error; } } async signAllTransactions(transactions: T[]): Promise { try { const wallet = this._wallet; if (!wallet) throw new WalletNotConnectedError(); try { return ((await wallet.signAllTransactions(transactions)) as T[]) || transactions; } catch (error: any) { throw new WalletSignTransactionError(error?.message, error); } } catch (error: any) { this.emit('error', error); throw error; } } async signMessage(message: Uint8Array): Promise { try { const wallet = this._wallet; if (!wallet) throw new WalletNotConnectedError(); try { return await wallet.signMessage(message, 'utf8'); } catch (error: any) { throw new WalletSignMessageError(error?.message, error); } } catch (error: any) { this.emit('error', error); throw error; } } private _disconnected = () => { const wallet = this._wallet; if (wallet) { wallet.off('disconnect', this._disconnected); this._wallet = null; this._publicKey = null; this.emit('error', new WalletDisconnectedError()); this.emit('disconnect'); } }; private _accountChanged = (newPublicKey?: PublicKey) => { if (!newPublicKey) return; const publicKey = this._publicKey; if (!publicKey) return; try { newPublicKey = new PublicKey(newPublicKey.toBytes()); } catch (error: any) { this.emit('error', new WalletPublicKeyError(error?.message, error)); return; } if (publicKey.equals(newPublicKey)) return; this._publicKey = newPublicKey; this.emit('connect', newPublicKey); }; }