UNPKG

@tronweb3/tronwallet-adapter-bitkeep

Version:

Wallet adapter for Bitget(BitKeep) Wallet extension and app.

350 lines (326 loc) 12.8 kB
import { AdapterState, isInBrowser, WalletReadyState, WalletSignMessageError, WalletNotFoundError, WalletDisconnectedError, WalletSignTransactionError, WalletConnectionError, WalletGetNetworkError, isInMobileBrowser, WalletError, AddonAdapter, } from '@tronweb3/tronwallet-abstract-adapter'; import { getNetworkInfoByTronWeb } from '@tronweb3/tronwallet-adapter-tronlink'; import type { Tron, TronLinkWallet } from '@tronweb3/tronwallet-adapter-tronlink'; import type { Transaction, SignedTransaction, AdapterName, BaseAdapterConfig, Network, TronWeb, } from '@tronweb3/tronwallet-abstract-adapter'; import { openBitgetWallet, supportBitgetWallet } from './utils.js'; declare global { interface Window { bitkeep: { tron: Tron; tronLink: TronLinkWallet; tronWeb: TronWeb; }; } } export type BitKeepAdapterConfig = BaseAdapterConfig; export const BitgetWalletAdapterName = 'Bitget Wallet' as AdapterName<'Bitget Wallet'>; export class BitKeepAdapter extends AddonAdapter { name = BitgetWalletAdapterName; url = 'https://web3.bitget.com'; icon = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTEyIiBoZWlnaHQ9IjUxMiIgdmlld0JveD0iMCAwIDUxMiA1MTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSI1MTIiIGhlaWdodD0iNTEyIiBmaWxsPSIjMDAxRjI5Ii8+CjxwYXRoIGQ9Ik0yMTkuOTQ4IDk1LjcwMjJDMjAxLjYyMyA5NS42OTI5IDE4My4zMyA5NS42ODM1IDE2NC45NDEgOTUuNzExNkMxNTMuODIyIDk1LjcxMTYgMTQ5LjY1MSAxMDkuNjcxIDE1Ny45MjEgMTE3LjkzOUwyODMuMDk4IDI0My4xMTdDMjg3LjAwNCAyNDYuNjkgMjg5LjQ0MSAyNTAuNTc0IDI4OS41MyAyNTUuNjkzQzI4OS40NDEgMjYwLjgxMiAyODcuMDA0IDI2NC42OTYgMjgzLjA5OCAyNjguMjY5TDE1Ny45MjEgMzkzLjQ0NkMxNDkuNjUxIDQwMS43MTUgMTUzLjgyMiA0MTUuNjc0IDE2NC45NDEgNDE1LjY3NEMxODMuMzMgNDE1LjcwMiAyMDEuNjIzIDQxNS42OTMgMjE5Ljk0OCA0MTUuNjgzQzIyOS4xMjIgNDE1LjY3OSAyMzguMzA1IDQxNS42NzQgMjQ3LjUxMSA0MTUuNjc0QzI1OS41NTUgNDE1LjY3NCAyNjYuNzIgNDA5LjI0IDI3My4xNTQgNDAyLjgwNUwzODYuMDQ3IDI4OS45MTJDMzk1LjA1NyAyODAuOTAyIDQwMy4xMTkgMjY4LjkzOSA0MDMuMDA5IDI1NS42OTNDNDAzLjExOSAyNDIuNDQ3IDM5NS4wNTcgMjMwLjQ4NCAzODYuMDQ3IDIyMS40NzRMMjczLjE1NCAxMDguNThDMjY2LjcyIDEwMi4xNDYgMjU5LjU1NSA5NS43MTE2IDI0Ny41MTEgOTUuNzExNkMyMzguMzA1IDk1LjcxMTYgMjI5LjEyMiA5NS43MDY5IDIxOS45NDggOTUuNzAyMloiIGZpbGw9IiMwMEYwRkYiLz4KPC9zdmc+Cg=='; config: Required<BitKeepAdapterConfig>; private _readyState: WalletReadyState = WalletReadyState.Loading; private _state: AdapterState = AdapterState.Loading; private _connecting: boolean; private _wallet: { tronWeb: TronWeb; tron: TronLinkWallet; } | null; private _address: string | null; constructor(config: BitKeepAdapterConfig = {}) { super(config); this.config = { ...this.commonConfig, }; this._connecting = false; this._wallet = null; this._address = null; if (!isInBrowser()) { this._readyState = WalletReadyState.NotFound; this.setState(AdapterState.NotFound); return; } if (supportBitgetWallet()) { this._readyState = WalletReadyState.Found; this._updateWallet().then(() => { if (this.connected) { this.emit('connect', this.address || ''); } }); } else { this._checkWallet().then(() => { if (this.connected) { this.emit('connect', this.address || ''); } }); } } get address() { return this._address; } get state() { return this._state; } get readyState() { return this._readyState; } get connecting() { return this._connecting; } /** * Get network information. * @returns {Network} Current network information. */ async network(): Promise<Network> { try { await this._checkWallet(); if (this.state !== AdapterState.Connected) throw new WalletDisconnectedError(); const wallet = this._wallet; if (!wallet || !wallet.tronWeb) throw new WalletDisconnectedError(); try { return await getNetworkInfoByTronWeb(wallet.tronWeb); } catch (e: any) { throw new WalletGetNetworkError(e?.message, e); } } catch (e: any) { this.emit('error', e); throw e; } } async connect(): Promise<void> { try { if (!(await this._beforeConnect())) return; const wallet = this._wallet; if (!wallet) return; if (!isInMobileBrowser()) { this._connecting = true; const res = await wallet.tron.request({ method: 'tron_requestAccounts' }); if (res?.code !== 200) { throw new WalletConnectionError( // @ts-ignore res?.code === 40001 ? 'The connection request is canceled by user.' : res?.message ); } } const address = wallet?.tronWeb.defaultAddress?.base58 || window.bitkeep?.tronWeb?.defaultAddress?.base58 || ''; this.setAddress(address); this.setState(AdapterState.Connected); this.emit('connect', this.address || ''); } catch (error: any) { const err = error instanceof WalletError ? error : new WalletConnectionError(error?.message, error); this.emit('error', err); throw err; } finally { this._connecting = false; } } async disconnect(): Promise<void> { if (this.state !== AdapterState.Connected) { return; } this.setAddress(null); this.setState(AdapterState.Disconnect); this.emit('disconnect'); } async signTransaction(transaction: Transaction): Promise<SignedTransaction> { try { const wallet = await this.checkAndGetWallet(); try { return await wallet.tronWeb.trx.sign(transaction); } catch (error: any) { if (error instanceof Error) { throw new WalletSignTransactionError(error.message, error); } else { throw new WalletSignTransactionError(error, new Error(error)); } } } catch (error: any) { this.emit('error', error); throw error; } } async multiSign(transaction: Transaction, options: { permissionId?: number } = {}): Promise<SignedTransaction> { try { const wallet = await this.checkAndGetWallet(); try { return await wallet.tronWeb.trx.multiSign(transaction, undefined, options.permissionId); } catch (error: any) { if (error instanceof Error) { throw new WalletSignTransactionError(error.message, error); } else { throw new WalletSignTransactionError(error, new Error(error)); } } } catch (error: any) { this.emit('error', error); throw error; } } async signMessage(message: string): Promise<string> { try { const wallet = await this.checkAndGetWallet(); try { return await wallet.tronWeb.trx.signMessageV2(message); } catch (error: any) { if (error instanceof Error) { throw new WalletSignMessageError(error.message, error); } else { throw new WalletSignMessageError(error, new Error(error)); } } } catch (error: any) { this.emit('error', error); throw error; } } private async checkAndGetWallet() { this.checkIfOpenApp(); await this._checkWallet(); if (!this.connected) throw new WalletDisconnectedError(); const wallet = this._wallet; if (!wallet || !wallet.tronWeb) throw new WalletDisconnectedError(); return wallet; } private checkReadyInterval: ReturnType<typeof setInterval> | null = null; private checkForWalletReady() { if (this.checkReadyInterval) { return; } let times = 0; const maxTimes = Math.floor(this.config.checkTimeout / 200); const check = async () => { if (this._wallet && this._wallet?.tron.ready) { this.checkReadyInterval && clearInterval(this.checkReadyInterval); this.checkReadyInterval = null; await this._updateWallet(); this.emit('connect', this.address || ''); } else if (times > maxTimes) { this.checkReadyInterval && clearInterval(this.checkReadyInterval); this.checkReadyInterval = null; } else { times++; } }; this.checkReadyInterval = setInterval(check, 200); } private _checkPromise: Promise<boolean> | null = null; /** * check if wallet exists by interval, the promise only resolve when wallet detected or timeout * @returns if wallet exists */ protected _checkWallet(): Promise<boolean> { if (this.readyState === WalletReadyState.Found) { return Promise.resolve(true); } if (this._checkPromise) { return this._checkPromise; } const interval = 100; const maxTimes = Math.floor(this.config.checkTimeout / interval); let times = 0, timer: ReturnType<typeof setInterval>; this._checkPromise = new Promise((resolve) => { const check = async () => { times++; const isSupport = supportBitgetWallet(); if (isSupport || times > maxTimes) { timer && clearInterval(timer); this._readyState = isSupport ? WalletReadyState.Found : WalletReadyState.NotFound; await this._updateWallet(); this.emit('readyStateChanged', this.readyState); resolve(isSupport); } }; timer = setInterval(check, interval); check(); }); return this._checkPromise; } private checkIfOpenApp() { if (this.config.openAppWithDeeplink === false) { return; } if (openBitgetWallet()) { throw new WalletNotFoundError(); } } private _updateWallet = async () => { let state = this.state; let address = this.address; if (supportBitgetWallet()) { if (isInMobileBrowser()) { const tron = window.bitkeep?.tronLink as unknown as TronLinkWallet; this._wallet = { tron, tronWeb: tron?.tronWeb, }; } else { const tronWeb = window.bitkeep?.tronWeb as unknown as TronWeb; this._wallet = { tron: window.bitkeep.tronLink as unknown as TronLinkWallet, tronWeb, }; } const ready = this._wallet.tron?.ready; address = ready ? this._wallet.tronWeb.defaultAddress?.base58 || null : ''; if (ready) { // Only run the security check once the wallet is actually connected. try { await this.checkSecurity(); } catch { this.setAddress(null); this.setState(AdapterState.Disconnect); return; } state = AdapterState.Connected; } else { state = AdapterState.Disconnect; this.checkForWalletReady(); } } else { this._wallet = null; address = null; state = AdapterState.NotFound; } this.setAddress(address); this.setState(state); }; private setAddress(address: string | null) { this._address = address; } private setState(state: AdapterState) { const preState = this.state; if (state !== preState) { this._state = state; this.emit('stateChanged', state); } } protected _openAppByDeepLinkIfNeed(): boolean { if (this.config.openAppWithDeeplink === false) { return false; } return openBitgetWallet(); } }