@tronweb3/tronwallet-adapter-bitkeep
Version:
Wallet adapter for Bitget(BitKeep) Wallet extension and app.
350 lines (326 loc) • 12.8 kB
text/typescript
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();
}
}