From ed705fdbe2c18598743ccf1ff48e89d9ca389c9e Mon Sep 17 00:00:00 2001 From: bryanForSafe <87746660+bryanForSafe@users.noreply.github.com> Date: Wed, 26 Oct 2022 21:39:45 +0800 Subject: [PATCH] add SafePalWallet add SafePalWallet --- src/WalletAdapters/SafePalWallet.ts | 324 ++++++++++++++++++++++++++++ 1 file changed, 324 insertions(+) create mode 100644 src/WalletAdapters/SafePalWallet.ts diff --git a/src/WalletAdapters/SafePalWallet.ts b/src/WalletAdapters/SafePalWallet.ts new file mode 100644 index 0000000..8c12ce9 --- /dev/null +++ b/src/WalletAdapters/SafePalWallet.ts @@ -0,0 +1,324 @@ +import { MaybeHexString, Types } from 'aptos'; +import { + WalletAccountChangeError, + WalletDisconnectionError, + WalletGetNetworkError, + WalletNetworkChangeError, + WalletNotConnectedError, + WalletNotReadyError, + WalletSignAndSubmitMessageError, + WalletSignMessageError, + WalletSignTransactionError +} from '../WalletProviders/errors'; +import { + AccountKeys, + BaseWalletAdapter, + NetworkInfo, + scopePollingDetectionStrategy, + SignMessagePayload, + SignMessageResponse, + WalletAdapterNetwork, + WalletName, + WalletReadyState +} from './BaseAdapter'; + +interface ConnectSafePalAccount { + address: MaybeHexString; + method: string; + publicKey: MaybeHexString; + status: number; +} + +interface SafePalAccount { + address: MaybeHexString; + publicKey: MaybeHexString; + authKey: MaybeHexString; + isConnected: boolean; +} +interface ISafePalWallet { + connect: () => Promise; + account(): Promise; + isConnected(): Promise; + generateTransaction(sender: MaybeHexString, payload: any, options?: any): Promise; + signAndSubmitTransaction(transaction: Types.TransactionPayload): Promise; + signTransaction(transaction: Types.TransactionPayload): Promise; + signMessage(message: SignMessagePayload): Promise; + disconnect(): Promise; + getChainId(): Promise<{ chainId: number }>; + network(): Promise; + onAccountChange: (listenr: (newAddress: string) => void) => void; + onNetworkChange: (listenr: (network: string) => void) => void; +} + +interface SafePalWindow extends Window { + safePal?: ISafePalWallet; +} + +declare const window: SafePalWindow; + +export const SafePalWalletName = 'SafePal' as WalletName<'SafePal'>; + +export interface SafePalWalletAdapterConfig { + provider?: ISafePalWallet; + // network?: WalletAdapterNetwork; + timeout?: number; +} + +export class SafePalWalletAdapter extends BaseWalletAdapter { + name = SafePalWalletName; + + url = 'https://chrome.google.com/webstore/detail/safepal-extension-wallet/lgmpcpglpngdoalbgeoldeajfclnhafa'; + + icon = 'https://raw.githubusercontent.com/hippospace/aptos-wallet-adapter/main/logos/safePal.png'; + + protected _provider: ISafePalWallet | undefined; + + protected _network: WalletAdapterNetwork; + + protected _chainId: string; + + protected _api: string; + + protected _timeout: number; + + protected _readyState: WalletReadyState = + typeof window === 'undefined' || typeof document === 'undefined' + ? WalletReadyState.Unsupported + : WalletReadyState.NotDetected; + + protected _connecting: boolean; + + protected _wallet: SafePalAccount | null; + + constructor({ + // provider, + // network = WalletAdapterNetwork.Testnet, + timeout = 10000 + }: SafePalWalletAdapterConfig = {}) { + super(); + + this._provider = typeof window !== 'undefined' ? window.safePal : undefined; + this._network = undefined; + this._timeout = timeout; + this._connecting = false; + this._wallet = null; + + if (typeof window !== 'undefined' && this._readyState !== WalletReadyState.Unsupported) { + scopePollingDetectionStrategy(() => { + this._provider = typeof window !== 'undefined' ? window.safePal : undefined; + if (this._provider) { + this._readyState = WalletReadyState.Installed; + this.emit('readyStateChange', this._readyState); + return true; + } + return false; + }); + } + } + + get publicAccount(): AccountKeys { + return { + publicKey: this._wallet?.publicKey || null, + address: this._wallet?.address || null, + authKey: this._wallet?.authKey || null + }; + } + + get network(): NetworkInfo { + return { + name: this._network, + api: this._api, + chainId: this._chainId + }; + } + + get connecting(): boolean { + return this._connecting; + } + + get connected(): boolean { + return !!this._wallet?.isConnected; + } + + get readyState(): WalletReadyState { + return this._readyState; + } + + async connect(): Promise { + try { + if (this.connected || this.connecting) return; + if ( + !( + this._readyState === WalletReadyState.Loadable || + this._readyState === WalletReadyState.Installed + ) + ) + throw new WalletNotReadyError(); + this._connecting = true; + + const provider = this._provider || window.safePal; + const isConnected = await provider?.isConnected(); + if (isConnected) { + await provider?.disconnect(); + } + const response = await provider?.connect(); + + if (!response) { + throw new WalletNotConnectedError('No connect response'); + } + + const walletAccount = await provider?.account(); + if (walletAccount) { + this._wallet = { + ...walletAccount, + isConnected: true + }; + + try { + const name = await provider?.network(); + const { chainId } = await provider?.getChainId(); + const api = null; + + this._network = name; + this._chainId = chainId.toString(); + this._api = api; + } catch (error: any) { + const errMsg = error.message; + this.emit('error', new WalletGetNetworkError(errMsg)); + throw error; + } + } + this.emit('connect', this._wallet?.address || ''); + } catch (error: any) { + this.emit('error', new Error(error)); + throw error; + } finally { + this._connecting = false; + } + } + + async disconnect(): Promise { + const wallet = this._wallet; + const provider = this._provider || window.safePal; + if (wallet) { + this._wallet = null; + + try { + await provider?.disconnect(); + } catch (error: any) { + this.emit('error', new WalletDisconnectionError(error?.message, error)); + } + } + + this.emit('disconnect'); + } + + async signTransaction( + transactionPyld: Types.TransactionPayload, + options?: any + ): Promise { + try { + const wallet = this._wallet; + const provider = this._provider || window.safePal; + if (!wallet || !provider) throw new WalletNotConnectedError(); + const tx = await provider.generateTransaction(wallet.address || '', transactionPyld, options); + if (!tx) throw new Error('Cannot generate transaction'); + const response = await provider?.signTransaction(tx); + + if (!response) { + throw new Error('No response'); + } + return response; + } catch (error: any) { + this.emit('error', new WalletSignTransactionError(error)); + throw error; + } + } + + async signAndSubmitTransaction( + transactionPyld: Types.TransactionPayload, + options?: any + ): Promise<{ hash: Types.HexEncodedBytes }> { + try { + const wallet = this._wallet; + const provider = this._provider || window.safePal; + if (!wallet || !provider) throw new WalletNotConnectedError(); + const tx = await provider.generateTransaction(wallet.address || '', transactionPyld, options); + if (!tx) throw new Error('Cannot generate transaction'); + const response = await provider?.signAndSubmitTransaction(tx); + + if (!response) { + throw new Error('No response'); + } + return { hash: response }; + } catch (error: any) { + this.emit('error', new WalletSignAndSubmitMessageError(error)); + throw error; + } + } + + async signMessage(msgPayload: SignMessagePayload): Promise { + try { + const wallet = this._wallet; + const provider = this._provider || window.safePal; + if (!wallet || !provider) throw new WalletNotConnectedError(); + if (typeof msgPayload !== 'object' || !msgPayload.nonce) { + throw new WalletSignMessageError('Invalid signMessage Payload'); + } + const response = await provider?.signMessage(msgPayload); + if (response) { + return response; + } else { + throw new Error('Sign Message failed'); + } + } catch (error: any) { + const errMsg = error.message; + this.emit('error', new WalletSignMessageError(errMsg)); + throw error; + } + } + + async onAccountChange(): Promise { + try { + const wallet = this._wallet; + const provider = this._provider || window.safePal; + if (!wallet || !provider) throw new WalletNotConnectedError(); + const handleChangeAccount = async (newAccount: string) => { + const { publicKey } = await provider?.account(); + this._wallet = { + ...this._wallet, + address: newAccount, + publicKey + }; + this.emit('accountChange', newAccount); + }; + await provider?.onAccountChange(handleChangeAccount); + } catch (error: any) { + const errMsg = error.message; + this.emit('error', new WalletAccountChangeError(errMsg)); + throw error; + } + } + + async onNetworkChange(): Promise { + try { + const wallet = this._wallet; + const provider = this._provider || window.safePal; + if (!wallet || !provider) throw new WalletNotConnectedError(); + const handleNetworkChange = async (newNetwork: WalletAdapterNetwork) => { + this._network = newNetwork; + const { chainId } = await provider?.getChainId(); + if (chainId) { + this._chainId = chainId.toString(); + + } + this.emit('networkChange', this._network); + }; + await provider?.onNetworkChange(handleNetworkChange); + } catch (error: any) { + const errMsg = error.message; + this.emit('error', new WalletNetworkChangeError(errMsg)); + throw error; + } + } +}