Skip to content

Commit

Permalink
Convert Web3Utils class into tree-shakable individual exports
Browse files Browse the repository at this point in the history
  • Loading branch information
sisou committed Nov 4, 2019
1 parent fb3eb5d commit 84096d0
Show file tree
Hide file tree
Showing 2 changed files with 139 additions and 141 deletions.
2 changes: 1 addition & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ export * from './request-link-encoding/RequestLinkEncoding';
export { default as Tweenable } from './tweenable/Tweenable';
export * from './utf8-tools/Utf8Tools';
export * from './validation-utils/ValidationUtils';
export * from './web3-utils/Web3Utils'
import * as Web3Utils from './web3-utils/Web3Utils'; export { Web3Utils };
278 changes: 138 additions & 140 deletions src/web3-utils/Web3Utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// this imports only the type without bundling the library
type BigInteger = import('big-integer').BigInteger

type Web3Provider = {
export type Web3Provider = {
sendAsync<R>(options: object, callback: (error: Error, result: RpcResult<R>) => void): void,
send<R>(method: string, params: any[]): Promise<RpcResult<R>>,
send<R>(request: {method: string, params: any[]}): Promise<RpcResult<R>>,
Expand All @@ -13,7 +13,7 @@ type Web3Provider = {
on(event: string, handler: Function): void,
}

type EthRpcTransaction = {
export type EthRpcTransaction = {
from?: string,
to: string,
value: string | number | bigint | BigInteger,
Expand All @@ -23,7 +23,7 @@ type EthRpcTransaction = {
chainId?: number,
}

type RpcResult<R> = {
export type RpcResult<R> = {
id?: number,
jsonrpc?: string,
result?: R,
Expand All @@ -33,174 +33,172 @@ type RpcResult<R> = {
},
}

export class Web3Utils {
static getProvider() {
export function getProvider() {
// @ts-ignore ethereum and web3 do not exists on window
if (typeof window.ethereum !== 'undefined' || typeof window.web3 !== 'undefined') {
// @ts-ignore ethereum and web3 do not exists on window
if (typeof window.ethereum !== 'undefined' || typeof window.web3 !== 'undefined') {
// @ts-ignore ethereum and web3 do not exists on window
return (window.ethereum as Web3Provider) || (window.web3.currentProvider as Web3Provider)
}

throw new Error('No Web3 provider found')
}

static hasProvider(): boolean {
try {
this.getProvider()
return true
} catch (e) {
return false
}
return (window.ethereum as Web3Provider) || (window.web3.currentProvider as Web3Provider)
}

static getProviderName(): string {
const provider = this.getProvider()
throw new Error('No Web3 provider found')
}

if (provider.isMetaMask) return 'MetaMask'
return 'Browser Wallet'
export function hasProvider(): boolean {
try {
getProvider()
return true
} catch (e) {
return false
}
}

static async getAddress() {
const provider = this.getProvider()
await provider.enable()
export function getProviderName(): string {
const provider = getProvider()

if (provider.selectedAddress) return provider.selectedAddress
if (provider.isMetaMask) return 'MetaMask'
return 'Browser Wallet'
}

// While MetaMask supports both `enable()` and `send('eth_requestAccounts')` to get active addresses,
// Opera does not. The `eth_accounts` method works for both.
const address = (await this._rpc<string[]>('eth_accounts'))[0]
return address
}
export async function getAddress() {
const provider = getProvider()
await provider.enable()

static async getNetworkVersion() {
const provider = this.getProvider()
if (provider.networkVersion) return parseInt(provider.networkVersion)
if (provider.selectedAddress) return provider.selectedAddress

// While MetaMask supports the `networkVersion` prop,
// Opera does not. The `net_version` method works for both.
return parseInt(await this._rpc<string>('net_version'))
}
// While MetaMask supports both `enable()` and `send('eth_requestAccounts')` to get active addresses,
// Opera does not. The `eth_accounts` method works for both.
const address = (await _rpc<string[]>('eth_accounts'))[0]
return address
}

static async sendTransaction(
params: EthRpcTransaction,
network: number | string = 'main',
changeNetworkCallback?: (network: string) => void,
) {
const provider = this.getProvider()
await provider.enable()
export async function getNetworkVersion() {
const provider = getProvider()
if (provider.networkVersion) return parseInt(provider.networkVersion)

let chainId = this.stringToChainId(params.chainId || network)
// While MetaMask supports the `networkVersion` prop,
// Opera does not. The `net_version` method works for both.
return parseInt(await _rpc<string>('net_version'))
}

while (await this.getNetworkVersion() !== chainId) {
// Prevent website from reloading when user switches network
provider.autoRefreshOnNetworkChange = false
export async function sendTransaction(
params: EthRpcTransaction,
network: number | string = 'main',
changeNetworkCallback?: (network: string) => void,
) {
const provider = getProvider()
await provider.enable()

const networkName = this._capitalizeString(this.chainIdToString(chainId))
let chainId = stringToChainId(params.chainId || network)

if (typeof changeNetworkCallback === 'function') changeNetworkCallback(networkName)
else alert(`To send this transaction, you must change the network in your Ethereum provider to "${networkName}"`)
while (await getNetworkVersion() !== chainId) {
// Prevent website from reloading when user switches network
provider.autoRefreshOnNetworkChange = false

await new Promise<number>(resolve => {
provider.on('networkChanged', resolve)
})
}
const networkName = _capitalizeString(chainIdToString(chainId))

if (!params.from) {
params.from = await this.getAddress()
}
if (typeof changeNetworkCallback === 'function') changeNetworkCallback(networkName)
else alert(`To send this transaction, you must change the network in your Ethereum provider to "${networkName}"`)

// Ethereum JSON-RPC requires numbers be given in HEX representation
params.value = this._toHex(params.value)
params.gas = typeof params.gas !== 'undefined' ? this._toHex(params.gas) : undefined
params.gasPrice = typeof params.gasPrice !== 'undefined' ? this._toHex(params.gasPrice) : undefined
params.chainId = chainId
await new Promise<number>(resolve => {
provider.on('networkChanged', resolve)
})
}

return this._rpc<string>('eth_sendTransaction', params)
if (!params.from) {
params.from = await getAddress()
}

static stringToChainId(network: number | string) {
if (typeof network === 'number') return network

switch (network.toLowerCase()) {
case 'main':
case 'mainnet':
return 1
case 'test':
case 'testnet':
case 'ropsten':
return 3
case 'rinkeby':
return 4
case 'goerli':
return 5
case 'kovan':
return 42
case 'geth':
return 1337
default:
throw new Error('Invalid network name')
}
// Ethereum JSON-RPC requires numbers be given in HEX representation
params.value = _toHex(params.value)
params.gas = typeof params.gas !== 'undefined' ? _toHex(params.gas) : undefined
params.gasPrice = typeof params.gasPrice !== 'undefined' ? _toHex(params.gasPrice) : undefined
params.chainId = chainId

return _rpc<string>('eth_sendTransaction', params)
}

export function stringToChainId(network: number | string) {
if (typeof network === 'number') return network

switch (network.toLowerCase()) {
case 'main':
case 'mainnet':
return 1
case 'test':
case 'testnet':
case 'ropsten':
return 3
case 'rinkeby':
return 4
case 'goerli':
return 5
case 'kovan':
return 42
case 'geth':
return 1337
default:
throw new Error('Invalid network name')
}
}

static chainIdToString(id: number) {
switch (id) {
case 1: return 'mainnet'
case 3: return 'ropsten'
case 4: return 'rinkeby'
case 5: return 'goerli'
case 42: return 'kovan'
case 1337: return 'geth'
default: throw new Error('Invalid chain ID')
}
export function chainIdToString(id: number) {
switch (id) {
case 1: return 'mainnet'
case 3: return 'ropsten'
case 4: return 'rinkeby'
case 5: return 'goerli'
case 42: return 'kovan'
case 1337: return 'geth'
default: throw new Error('Invalid chain ID')
}
}

private static async _rpc<R>(method: string, params?: any) {
const provider = this.getProvider()

if (typeof params !== 'undefined' && !Array.isArray(params)) params = [params]

console.debug('WEB3 SEND:', method, params)

const response = await new Promise<RpcResult<R>>(async (resolve, reject) => {
if (typeof provider.send === 'function') {
try {
// Use send() as specified in EIP-1193
provider.send<R>(method, params).then(resolve, reject)
} catch (error) {
// Opera doesn't like EIP-1193 yet and implements a sync method with a single request object
try { resolve(provider.send<R>({ method, params })) } catch (error) { reject(error) }
}
} else if (typeof provider.sendAsync === 'function') {
// Use deprecated sendAsync() with callback
provider.sendAsync<R>({
method,
params,
}, (error, result) => {
if (error) reject(error)
resolve(result)
})
}
else reject(new Error('No known Web3 integration found'))
})
async function _rpc<R>(method: string, params?: any) {
const provider = getProvider()

if (typeof params !== 'undefined' && !Array.isArray(params)) params = [params]

console.debug('WEB3 RECEIVE:', response)
console.debug('WEB3 SEND:', method, params)

if (response.error) {
throw new Error(response.error.message)
const response = await new Promise<RpcResult<R>>(async (resolve, reject) => {
if (typeof provider.send === 'function') {
try {
// Use send() as specified in EIP-1193
provider.send<R>(method, params).then(resolve, reject)
} catch (error) {
// Opera doesn't like EIP-1193 yet and implements a sync method with a single request object
try { resolve(provider.send<R>({ method, params })) } catch (error) { reject(error) }
}
} else if (typeof provider.sendAsync === 'function') {
// Use deprecated sendAsync() with callback
provider.sendAsync<R>({
method,
params,
}, (error, result) => {
if (error) reject(error)
resolve(result)
})
}
else reject(new Error('No known Web3 integration found'))
})

return response.result!
}
console.debug('WEB3 RECEIVE:', response)

private static _toHex(value: string | number | bigint | BigInteger): string {
if (typeof value === 'string') {
if (value.substring(0, 2) !== '0x') throw new Error('Non-hex-encoded strings are not yet supported');
return value;
}
return '0x' + value.toString(16);
if (response.error) {
throw new Error(response.error.message)
}

private static _capitalizeString(str: string) {
return str[0].toUpperCase() + str.slice(1)
return response.result!
}

function _toHex(value: string | number | bigint | BigInteger): string {
if (typeof value === 'string') {
if (value.substring(0, 2) !== '0x') throw new Error('Non-hex-encoded strings are not yet supported');
return value;
}
return '0x' + value.toString(16);
}

function _capitalizeString(str: string) {
return str[0].toUpperCase() + str.slice(1)
}

0 comments on commit 84096d0

Please sign in to comment.