Skip to content

Commit

Permalink
feat: add coinbase provider (#10202)
Browse files Browse the repository at this point in the history
* chore: add coinbase provider

* refactor: remove WalletLink

* refactor: remove MathWallet

* refactor: code style

* chore: coinbase switch chain

* chore: update chain list

* refactor: code style
  • Loading branch information
guanbinrui authored Jul 28, 2023
1 parent 8d8bb7e commit dafa792
Show file tree
Hide file tree
Showing 30 changed files with 150 additions and 188 deletions.
6 changes: 3 additions & 3 deletions packages/injected-script/main/GlobalVariableBridge/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,12 @@ export function __unsafe__onEvent(path: string, bridgeEvent: keyof InternalEvent
function __unsafe__untilInner(name: string) {
if ($.hasOwn(__unsafe__window, name)) return $.PromiseResolve(true)

let restCheckTimes = 150 // 30s
let restCheckTimes = 15 // 3s

return new $.Promise<true>((resolve) => {
return new $.Promise<true>((resolve, reject) => {
function check() {
restCheckTimes -= 1
if (restCheckTimes < 0) return
if (restCheckTimes < 0) return reject(new Error('timeout'))
if ($.hasOwn(__unsafe__window, name)) return resolve(true)
$.setTimeout(check, 200)
}
Expand Down
13 changes: 8 additions & 5 deletions packages/injected-script/sdk/Base.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { createPromise, sendEvent } from './utils.js'

export class InjectedProvider {
private events = new Map<string, Set<(data: unknown) => void>>()
private isReadyInternal = false
private isConnectedInternal = false
protected events = new Map<string, Set<(data: unknown) => void>>()
protected isReadyInternal = false
protected isConnectedInternal = false

constructor(public pathname: string) {
this.startup()
Expand All @@ -12,14 +12,17 @@ export class InjectedProvider {
private async startup() {
await this.untilAvailable()

// if a provider is not ready, it will not be able to connect
if (!this.isReady) return

this.on('connected', () => {
this.isConnectedInternal = true
})
this.on('disconnect', () => {
this.isConnectedInternal = false
})

this.isConnectedInternal = (await this.getProperty('isConnected')) as boolean
this.isConnectedInternal = (await this.getProperty<boolean | null>('isConnected')) ?? false
}

get isReady() {
Expand Down Expand Up @@ -95,7 +98,7 @@ export class InjectedProvider {
/**
* Access primitive property on the sdk object.
*/
getProperty(key: string): Promise<unknown> {
getProperty<T = unknown>(key: string): Promise<T | null> {
return createPromise((id) => sendEvent('web3BridgePrimitiveAccess', this.pathname, id, key))
}
}
5 changes: 4 additions & 1 deletion packages/injected-script/sdk/Clover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ export class CloverProvider extends InjectedProvider {
}

override async untilAvailable(): Promise<void> {
await super.untilAvailable(() => super.getProperty('isClover') as Promise<boolean>)
await super.untilAvailable(async () => {
const isClover = await super.getProperty<boolean>('isClover')
return !!isClover
})
}
}
4 changes: 1 addition & 3 deletions packages/injected-script/sdk/Coin98.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ export class Coin98Provider extends InjectedProvider {

override async request<T>(data: RequestArguments): Promise<T> {
// coin98 cannot handle it correctly (test with coin98 v6.0.3)
if (data.method === 'eth_chainId') {
return this.getProperty('chainId') as T
}
if (data.method === 'eth_chainId') return (await this.getProperty<T>('chainId'))!
return super.request<T>(data)
}
}
24 changes: 24 additions & 0 deletions packages/injected-script/sdk/Coinbase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { InjectedProvider } from './Base.js'
import { createPromise, sendEvent } from './utils.js'

export class CoinbaseProvider extends InjectedProvider {
constructor() {
super('coinbaseWalletExtension')
}

override async untilAvailable(): Promise<void> {
await super.untilAvailable(async () => !!(await super.getProperty<boolean>('isCoinbaseWallet')))
}

override connect(options: unknown): Promise<void> {
return createPromise((id) => sendEvent('web3BridgeExecute', [this.pathname, 'enable'].join('.'), id, options))
}

override emit(event: string, data: unknown[]) {
for (const f of this.events.get(event) || []) {
try {
Reflect.apply(f, null, event === 'chainChanged' ? [`0x${Number(data[0]).toString(16)}`] : data)
} catch {}
}
}
}
11 changes: 0 additions & 11 deletions packages/injected-script/sdk/MathWallet.ts

This file was deleted.

6 changes: 5 additions & 1 deletion packages/injected-script/sdk/MetaMask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ export class MetaMaskProvider extends InjectedProvider {
}

override async untilAvailable(): Promise<void> {
await super.untilAvailable(() => super.getProperty('isMetaMask') as Promise<boolean>)
await super.untilAvailable(async () => {
const isMetaMask = await super.getProperty<boolean>('isMetaMask')
const isCoinbaseWallet = await super.getProperty<boolean>('isCoinbaseWallet')
return !!isMetaMask && !isCoinbaseWallet
})
}
}
5 changes: 4 additions & 1 deletion packages/injected-script/sdk/Opera.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ export class OperaProvider extends InjectedProvider {
}

override async untilAvailable(): Promise<void> {
await super.untilAvailable(() => super.getProperty('isOpera') as Promise<boolean>)
await super.untilAvailable(async () => {
const isOpera = await super.getProperty<boolean>('isOpera')
return !!isOpera
})
}
}
4 changes: 2 additions & 2 deletions packages/injected-script/sdk/Phantom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ export class PhantomProvider extends InjectedProvider {
constructor() {
super('phantom.solana')
}

override async connect(options: unknown): Promise<unknown> {
await super.connect(options)
const publicKey = (await super.getProperty('publicKey')) as string
return {
publicKey,
publicKey: await super.getProperty<string>('publicKey'),
}
}
}
3 changes: 1 addition & 2 deletions packages/injected-script/sdk/Solflare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ export class SolflareProvider extends InjectedProvider {

override async connect(options: unknown): Promise<unknown> {
await super.connect(options)
const publicKey = (await super.getProperty('publicKey')) as string
return {
publicKey,
publicKey: await super.getProperty<string>('publicKey'),
}
}
}
12 changes: 0 additions & 12 deletions packages/injected-script/sdk/WalletLink.ts

This file was deleted.

30 changes: 14 additions & 16 deletions packages/injected-script/sdk/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { CustomEventId, decodeEvent } from '../shared/index.js'
import { Coin98Provider, Coin98ProviderType } from './Coin98.js'
import { CoinbaseProvider } from './Coinbase.js'
import { PhantomProvider } from './Phantom.js'
import { SolflareProvider } from './Solflare.js'
import { MetaMaskProvider } from './MetaMask.js'
import { sendEvent, rejectPromise, resolvePromise } from './utils.js'
import { MathWalletProvider } from './MathWallet.js'
import { WalletLinkProvider } from './WalletLink.js'
import { OperaProvider } from './Opera.js'
import { CloverProvider } from './Clover.js'

Expand All @@ -17,11 +16,21 @@ export const injectedCoin98SolanaProvider = new Coin98Provider(Coin98ProviderTyp
export const injectedPhantomProvider = new PhantomProvider()
export const injectedSolflareProvider = new SolflareProvider()
export const injectedMetaMaskProvider = new MetaMaskProvider()
export const injectedMathWalletProvider = new MathWalletProvider()
export const injectedWalletLinkProvider = new WalletLinkProvider()
export const injectedCoinbaseProvider = new CoinbaseProvider()
export const injectedOperaProvider = new OperaProvider()
export const injectedCloverProvider = new CloverProvider()

// Please keep this list update to date
const Providers = [
injectedCoinbaseProvider,
injectedOperaProvider,
injectedCloverProvider,
injectedMetaMaskProvider,
injectedCoin98EVMProvider,
injectedCoin98SolanaProvider,
injectedPhantomProvider,
]

export function pasteText(text: string) {
sendEvent('paste', text)
}
Expand Down Expand Up @@ -60,18 +69,7 @@ globalThis.document?.addEventListener?.(CustomEventId, (e) => {

case 'web3BridgeEmitEvent':
const [pathname, eventName, data] = r[1]
const provider = [
injectedCoin98EVMProvider,
injectedCoin98SolanaProvider,
injectedPhantomProvider,
injectedMetaMaskProvider,
injectedMathWalletProvider,
injectedWalletLinkProvider,
injectedOperaProvider,
injectedCloverProvider,
].find((x) => x.pathname === pathname)

provider?.emit(eventName, data)
Providers.filter((x) => x.pathname === pathname).forEach((x) => x?.emit(eventName, data))
return

case 'web3BridgeBindEvent':
Expand Down
3 changes: 1 addition & 2 deletions packages/injected-script/sdk/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@
"Phantom.ts",
"Solflare.ts",
"Coin98.ts",
"Coinbase.ts",
"MetaMask.ts",
"MathWallet.ts",
"Opera.ts",
"WalletLink.ts",
"Clover.ts",
"utils.ts"
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,14 @@ export function ConnectionContent(props: ConnectionContentProps) {
} catch (error: unknown) {
// eslint-disable-next-line no-alert
if (error instanceof Error) alert(error.message)
} finally {
if ((await Web3.getChainId()) === chainId) {
// eslint-disable-next-line no-alert
alert(`Switched to chain ${chainId}`)
} else {
// eslint-disable-next-line no-alert
alert(`Failed to switch to chain ${chainId}`)
}
}
}, [])

Expand Down
84 changes: 43 additions & 41 deletions packages/web3-providers/src/Web3/Base/state/Provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,7 @@ export class ProviderState<
}

async setup() {
await Promise.all([this.storage.account.initializedPromise, this.storage.providerType.initializedPromise])

await this.readyPromise
await this.setupSubscriptions()
await this.setupProviders()
}
Expand All @@ -100,52 +99,55 @@ export class ProviderState<
}

private async setupProviders() {
await Promise.all(
Object.entries(this.providers).map(async (entry) => {
const [providerType, provider] = entry as [
ProviderType,
WalletAPI.Provider<ChainId, ProviderType, Web3Provider, Web3>,
]

provider.emitter.on('chainId', async (chainId) => {
await this.setAccount(providerType, {
chainId: Number.parseInt(chainId, 16) as ChainId,
})
const providers = Object.entries(this.providers) as Array<
[ProviderType, WalletAPI.Provider<ChainId, ProviderType, Web3Provider, Web3>]
>

providers.map(async ([providerType, provider]) => {
try {
await provider.readyPromise
if (!provider.ready) return
} catch {
return
}

provider.emitter.on('chainId', async (chainId) => {
await this.setAccount(providerType, {
chainId: Number.parseInt(chainId, 16) as ChainId,
})
provider.emitter.on('connect', async ({ account }) => {
if (!this.options.isValidAddress(account)) return
// provider should update before account, otherwise account failed to update
await this.setProvider(providerType)
await this.setAccount(providerType, {
account,
})
})
provider.emitter.on('connect', async ({ account }) => {
if (!this.options.isValidAddress(account)) return
// provider should update before account, otherwise account failed to update
await this.setProvider(providerType)
await this.setAccount(providerType, {
account,
})
provider.emitter.on('accounts', async (accounts) => {
const account = first(accounts)
})
provider.emitter.on('accounts', async (accounts) => {
const account = first(accounts)

if (account && this.options.isValidAddress(account))
await this.setAccount(providerType, {
account,
})
})
provider.emitter.on('disconnect', async () => {
if (account && this.options.isValidAddress(account))
await this.setAccount(providerType, {
account: '',
chainId: this.options.getDefaultChainId(),
account,
})

if (!this.site) return

await this.storage.providerType.setValue(this.options.getDefaultProviderType())
})
provider.emitter.on('disconnect', async () => {
await this.setAccount(providerType, {
account: '',
chainId: this.options.getDefaultChainId(),
})

try {
await provider.setup(this.context)
} catch {
// ignore setup errors
}
}),
)
if (!this.site) return
await this.storage.providerType.setValue(this.options.getDefaultProviderType())
})

try {
await provider.setup(this.context)
} catch {
// ignore setup errors
}
})
}

private async setAccount(providerType: ProviderType, account: Partial<Account<ChainId>>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ export class Interceptor implements Middleware<ConnectionContext> {
[ProviderType.MetaMask]: Composer.from(new MetaMaskLike(ProviderType.MetaMask)),
[ProviderType.WalletConnect]: Composer.from(new WalletConnect()),
[ProviderType.Coin98]: Composer.from(new MetaMaskLike(ProviderType.Coin98)),
[ProviderType.WalletLink]: Composer.from(new MetaMaskLike(ProviderType.WalletLink)),
[ProviderType.MathWallet]: Composer.from(new MetaMaskLike(ProviderType.MathWallet)),
[ProviderType.Fortmatic]: Composer.from(new Fortmatic()),
[ProviderType.Opera]: Composer.from(new MetaMaskLike(ProviderType.Opera)),
[ProviderType.Clover]: Composer.from(new MetaMaskLike(ProviderType.Clover)),
Expand Down
12 changes: 10 additions & 2 deletions packages/web3-providers/src/Web3/EVM/providers/Base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,20 @@ export class BaseProvider implements WalletAPI.Provider<ChainId, ProviderType, W
return false
}

// No need to wait by default
/**
* This field indicates the provider is ready to be set up.
* Please make sure that the provider SDK or global environment is ready.
* No need to wait by default
*/
get ready() {
return true
}

// No need to wait by default
/**
* This field indicates the provider is ready to be set up.
* Please make sure that the provider SDK or global environment is ready.
* No need to wait by default
*/
get readyPromise() {
return Promise.resolve()
}
Expand Down
Loading

0 comments on commit dafa792

Please sign in to comment.