fugitut
Version:
A simple, maximally extensible, dependency minimized framework for building modern Ethereum dApps
223 lines (187 loc) • 6.93 kB
text/typescript
import { AbstractConnectorArguments, ConnectorUpdate } from '@web3-react-fork/types'
import { AbstractConnector } from '@web3-react-fork/abstract-connector'
import warning from 'tiny-warning'
import { SendReturnResult, SendReturn, SendOld, SendAsync } from './types'
function parseSendReturn(sendReturn: SendReturnResult | SendReturn): any {
return sendReturn.hasOwnProperty('result') ? sendReturn.result : sendReturn
}
export class NoEthereumProviderError extends Error {
public constructor() {
super()
this.name = this.constructor.name
this.message = 'No Ethereum provider was found on window.ethereum.'
}
}
export class UserRejectedRequestError extends Error {
public constructor() {
super()
this.name = this.constructor.name
this.message = 'The user rejected the request.'
}
}
export class InjectedConnector extends AbstractConnector {
constructor(kwargs: AbstractConnectorArguments) {
super(kwargs)
this.handleChainChanged = this.handleChainChanged.bind(this)
this.handleAccountsChanged = this.handleAccountsChanged.bind(this)
this.handleDisconnect = this.handleDisconnect.bind(this)
}
private handleChainChanged(chainId: string | number): void {
if (__DEV__) {
console.log("Handling 'chainChanged' event with payload", chainId)
}
this.emitUpdate({ chainId, provider: window.ethereum })
}
private handleAccountsChanged(accounts: string[]): void {
if (__DEV__) {
console.log("Handling 'accountsChanged' event with payload", accounts)
}
if (accounts.length === 0) {
this.emitDeactivate()
} else {
this.emitUpdate({ account: accounts[0] })
}
}
private handleDisconnect(code: number, reason: string): void {
if (__DEV__) {
console.log("Handling 'close' event with payload", code, reason)
}
this.emitDeactivate()
}
public async activate(): Promise<ConnectorUpdate> {
if (!window.ethereum) {
throw new NoEthereumProviderError()
}
if (window.ethereum.on) {
window.ethereum.on('chainChanged', this.handleChainChanged)
window.ethereum.on('accountsChanged', this.handleAccountsChanged)
window.ethereum.on('disconnect', this.handleDisconnect)
}
if ((window.ethereum as any).isMetaMask) {
;(window.ethereum as any).autoRefreshOnNetworkChange = false
}
// try to activate + get account via
const result: ConnectorUpdate = await new Promise(resolve => {
;(window.ethereum!.sendAsync as SendAsync)({ method: 'eth_requestAccounts' }, async (err, sendReturn) => {
if (err) {
if ((err as any).code === 4001) {
throw new UserRejectedRequestError()
}
warning(false, 'eth_requestAccounts was unsuccessful, falling back to enable')
const account = await window
.ethereum!.enable()
.then(sendReturn => sendReturn && parseSendReturn(sendReturn)[0])
resolve({ provider: window.ethereum, ...(account ? { account } : {}) })
}
const account = parseSendReturn(sendReturn)[0]
resolve({ provider: window.ethereum, ...(account ? { account } : {}) })
})
})
return result
}
public async getProvider(): Promise<any> {
return window.ethereum
}
public async getChainId(): Promise<number | string> {
if (!window.ethereum) {
throw new NoEthereumProviderError()
}
let chainId: string | number
chainId = await new Promise(resolve => {
;(window.ethereum!.sendAsync as SendAsync)({ method: 'eth_chainId' }, async (err, sendReturn) => {
if (err) {
warning(false, 'eth_chainId was unsuccessful, falling back to net_version')
resolve(0)
}
const result = parseSendReturn(sendReturn)
resolve(result)
})
})
if (!chainId) {
chainId = await new Promise(resolve => {
;(window.ethereum!.sendAsync as SendAsync)({ method: 'net_version' }, async (err, sendReturn) => {
if (err) {
warning(false, 'net_version was unsuccessful, falling back to net version v2')
resolve(0)
}
const result = parseSendReturn(sendReturn)
resolve(result)
})
})
}
if (!chainId) {
try {
chainId = parseSendReturn((window.ethereum.send as SendOld)({ method: 'net_version' }))
} catch {
warning(false, 'net_version v2 was unsuccessful, falling back to manual matches and static properties')
}
}
if (!chainId) {
if ((window.ethereum as any).isDapper) {
chainId = parseSendReturn((window.ethereum as any).cachedResults.net_version)
} else {
chainId =
(window.ethereum as any).chainId ||
(window.ethereum as any).netVersion ||
(window.ethereum as any).networkVersion ||
(window.ethereum as any)._chainId
}
}
return chainId
}
public async getAccount(): Promise<null | string> {
if (!window.ethereum) {
throw new NoEthereumProviderError()
}
let account: string
account = await new Promise(resolve => {
;(window.ethereum!.sendAsync as SendAsync)({ method: 'eth_accounts' }, async (err, sendReturn) => {
if (err) {
warning(false, 'eth_accounts was unsuccessful, falling back to enable')
resolve('')
}
const result = parseSendReturn(sendReturn)[0]
resolve(result)
})
})
if (!account) {
try {
account = await window.ethereum.enable().then(sendReturn => parseSendReturn(sendReturn)[0])
} catch {
warning(false, 'enable was unsuccessful, falling back to eth_accounts v2')
}
}
if (!account) {
account = parseSendReturn((window.ethereum.send as SendOld)({ method: 'eth_accounts' }))[0]
}
return account
}
public deactivate() {
if (window.ethereum && window.ethereum.removeListener) {
window.ethereum.removeListener('chainChanged', this.handleChainChanged)
window.ethereum.removeListener('accountsChanged', this.handleAccountsChanged)
window.ethereum.removeListener('disconnect', this.handleDisconnect)
}
}
public async isAuthorized(): Promise<boolean> {
if (!window.ethereum) {
return false
}
let toRet: boolean = false
toRet = await new Promise(resolve => {
;(window.ethereum!.sendAsync as SendAsync)({ method: 'eth_accounts' }, async (err, sendReturn) => {
if (err) {
warning(false, 'eth_accounts was unsuccessful, falling back to eth_accounts v2')
const account = parseSendReturn((window.ethereum!.send as SendOld)({ method: 'eth_accounts' }))[0]
resolve(!!account)
}
const result = parseSendReturn(sendReturn)
if (result.length > 0) {
resolve(true)
}
resolve(false)
})
})
return toRet
}
}