Skip to content

Commit

Permalink
feat: Injected Universal Provider (#3177)
Browse files Browse the repository at this point in the history
Co-authored-by: tomiir <[email protected]>
  • Loading branch information
ganchoradkov and tomiir authored Dec 17, 2024
1 parent 0f55885 commit 3cf3bc5
Show file tree
Hide file tree
Showing 20 changed files with 1,366 additions and 1,160 deletions.
22 changes: 22 additions & 0 deletions .changeset/lovely-dragons-clap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
'@reown/appkit-scaffold-ui': patch
'@reown/appkit': patch
'@reown/appkit-core': patch
'@reown/appkit-adapter-ethers': patch
'@reown/appkit-adapter-ethers5': patch
'@reown/appkit-adapter-solana': patch
'@reown/appkit-adapter-wagmi': patch
'@reown/appkit-utils': patch
'@reown/appkit-cdn': patch
'@reown/appkit-cli': patch
'@reown/appkit-common': patch
'@reown/appkit-experimental': patch
'@reown/appkit-polyfills': patch
'@reown/appkit-siwe': patch
'@reown/appkit-siwx': patch
'@reown/appkit-ui': patch
'@reown/appkit-wallet': patch
'@reown/appkit-wallet-button': patch
---

Adds walletConnectProvider flag to inject a UniversalProvider instance to be used by AppKit'
57 changes: 57 additions & 0 deletions apps/laboratory/src/pages/library/custom-universal-provider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { AppKit, createAppKit } from '@reown/appkit/react'
import { ThemeStore } from '../../utils/StoreUtil'
import { ConstantsUtil } from '../../utils/ConstantsUtil'
import { AppKitButtons } from '../../components/AppKitButtons'
import { mainnet } from '@reown/appkit/networks'
import { MultiChainInfo } from '../../components/MultiChainInfo'
import { UpaTests } from '../../components/UPA/UpaTests'
import Provider, { UniversalProvider } from '@walletconnect/universal-provider'
import { useEffect, useState } from 'react'

const networks = ConstantsUtil.EvmNetworks

export default function MultiChainWagmiAdapterOnly() {
const [uprovider, setUprovider] = useState<Provider | null>(null)
const [appkit, setAppKit] = useState<AppKit | null>(null)

async function initializeUniversalProvider() {
const provider = await UniversalProvider.init({
projectId: ConstantsUtil.ProjectId
})

const modal = createAppKit({
networks,
defaultNetwork: mainnet,
projectId: ConstantsUtil.ProjectId,
metadata: ConstantsUtil.Metadata,
universalProvider: provider
})

ThemeStore.setModal(modal)
setAppKit(modal)

provider.on('display_uri', (connection: string) => {
modal.open({ view: 'ConnectingWalletConnectBasic', uri: connection })
})

setUprovider(provider)
}

useEffect(() => {
initializeUniversalProvider()
}, [])

return (
<>
{uprovider && appkit ? (
<>
<AppKitButtons />
<MultiChainInfo />
<UpaTests />
</>
) : (
''
)}
</>
)
}
4 changes: 3 additions & 1 deletion packages/appkit-new/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1580,7 +1580,9 @@ export class AppKit {
}
}

this.universalProvider = await UniversalProvider.init(universalProviderOptions)
OptionsController.setUsingInjectedUniversalProvider(Boolean(this.options?.universalProvider))
this.universalProvider =
this.options.universalProvider ?? (await UniversalProvider.init(universalProviderOptions))
}

public async getUniversalProvider() {
Expand Down
12 changes: 7 additions & 5 deletions packages/appkit-new/src/utils/TypesUtil.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { AppKitNetwork, CaipNetwork, ThemeVariables } from '@reown/appkit-common'
import type { ChainAdapter, Metadata, OptionsControllerState, ThemeMode } from '@reown/appkit-core'
import type { AppKitSIWEClient } from '@reown/appkit-siwe'
import type { IUniversalProvider } from '@walletconnect/universal-provider'

export type AppKitOptions = {
/**
Expand Down Expand Up @@ -72,9 +73,10 @@ export type AppKitOptions = {
* @see https://cloud.walletconnect.com/
*/
metadata?: Metadata
/**
* UniversalProvider instance to be used by AppKit.
* AppKit will generate its own instance by default in none provided
* @default undefined
*/
universalProvider?: IUniversalProvider
} & OptionsControllerState

export type AppKitOptionsWithCaipNetworks = Omit<AppKitOptions, 'defaultNetwork' | 'networks'> & {
defaultNetwork?: CaipNetwork
networks: [CaipNetwork, ...CaipNetwork[]]
}
18 changes: 15 additions & 3 deletions packages/appkit/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,14 @@ export { AccountController }

// -- Types --------------------------------------------------------------------
export interface OpenOptions {
view: 'Account' | 'Connect' | 'Networks' | 'ApproveTransaction' | 'OnRampProviders'
view:
| 'Account'
| 'Connect'
| 'Networks'
| 'ApproveTransaction'
| 'OnRampProviders'
| 'ConnectingWalletConnectBasic'
uri?: string
}

type Adapters = Record<ChainNamespace, AdapterBlueprint>
Expand Down Expand Up @@ -241,6 +248,9 @@ export class AppKit {
// -- Public -------------------------------------------------------------------
public async open(options?: OpenOptions) {
await this.initOrContinue()
if (options?.uri && this.universalAdapter) {
ConnectionController.setUri(options.uri)
}
ModalController.open(options)
}

Expand Down Expand Up @@ -745,7 +755,7 @@ export class AppKit {
private getDefaultMetaData() {
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
return {
name: document.getElementsByTagName('title')[0]?.textContent || '',
name: document.getElementsByTagName('title')?.[0]?.textContent || '',
description:
document.querySelector<HTMLMetaElement>('meta[property="og:description"]')?.content || '',
url: window.location.origin,
Expand Down Expand Up @@ -1802,7 +1812,9 @@ export class AppKit {
logger
}

this.universalProvider = await UniversalProvider.init(universalProviderOptions)
OptionsController.setUsingInjectedUniversalProvider(Boolean(this.options?.universalProvider))
this.universalProvider =
this.options.universalProvider ?? (await UniversalProvider.init(universalProviderOptions))
}

public async getUniversalProvider() {
Expand Down
1 change: 1 addition & 0 deletions packages/appkit/src/library/react/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type { ChainNamespace } from '@reown/appkit-common'

type OpenOptions = {
view: 'Account' | 'Connect' | 'Networks' | 'ApproveTransaction' | 'OnRampProviders'
uri?: string
}

type ThemeModeOptions = AppKitOptions['themeMode']
Expand Down
44 changes: 41 additions & 3 deletions packages/appkit/src/tests/appkit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,22 @@ import { UniversalAdapter } from '../universal-adapter/client'
import type { AdapterBlueprint } from '../adapters/ChainAdapterBlueprint'
import { ProviderUtil } from '../store'
import { ErrorUtil } from '@reown/appkit-utils'
import { UniversalProvider } from '@walletconnect/universal-provider'

// Mock all controllers and UniversalAdapterClient
vi.mock('@reown/appkit-core')
vi.mock('../universal-adapter/client')

vi.mocked(global).window = { location: { origin: '' } } as any
vi.mocked(global).document = {
body: {
injectAdjacentElement: vi.fn()
} as any,
createElement: vi.fn().mockReturnValue({ appendChild: vi.fn() }),
getElementsByTagName: vi.fn().mockReturnValue([{ textContent: '' }]),
querySelector: vi.fn()
} as any

describe('Base', () => {
let appKit: AppKit

Expand Down Expand Up @@ -262,7 +273,8 @@ describe('Base', () => {
it('should get CAIP address', () => {
vi.mocked(ChainController).state = {
activeChain: 'eip155',
activeCaipAddress: 'eip155:1:0x123'
activeCaipAddress: 'eip155:1:0x123',
chains: new Map([['eip155', { namespace: 'eip155' }]])
} as any
expect(appKit.getCaipAddress()).toBe('eip155:1:0x123')
})
Expand All @@ -288,7 +300,8 @@ describe('Base', () => {
vi.mocked(AccountController.setCaipAddress).mockImplementation(() => {
vi.mocked(ChainController).state = {
...vi.mocked(ChainController).state,
activeCaipAddress: 'eip155:1:0x123'
activeCaipAddress: 'eip155:1:0x123',
chains: new Map([['eip155', { namespace: 'eip155' }]])
} as any
})

Expand Down Expand Up @@ -336,7 +349,8 @@ describe('Base', () => {

it('should get CAIP network', () => {
vi.mocked(ChainController).state = {
activeCaipNetwork: { id: 'eip155:1', name: 'Ethereum' }
activeCaipNetwork: { id: 'eip155:1', name: 'Ethereum' },
chains: new Map([['eip155', { namespace: 'eip155' }]])
} as any
expect(appKit.getCaipNetwork()).toEqual({ id: 'eip155:1', name: 'Ethereum' })
})
Expand Down Expand Up @@ -846,6 +860,30 @@ describe('Base', () => {
expect(mockUniversalAdapter.setUniversalProvider).toHaveBeenCalled()
})

it('should initialize UniversalProvider when not provided in options', () => {
const upSpy = vi.spyOn(UniversalProvider, 'init')
new AppKit({
...mockOptions,
adapters: [mockAdapter]
})

expect(OptionsController.setUsingInjectedUniversalProvider).toHaveBeenCalled()
expect(upSpy).toHaveBeenCalled()
})

it('should not initialize UniversalProvider when provided in options', async () => {
const up = await UniversalProvider.init({})
const upSpy = vi.spyOn(UniversalProvider, 'init')
new AppKit({
...mockOptions,
universalProvider: up,
adapters: [mockAdapter]
})

expect(upSpy).not.toHaveBeenCalled()
expect(OptionsController.setUsingInjectedUniversalProvider).toHaveBeenCalled()
})

it('should initialize multiple adapters for different namespaces', async () => {
const createAdapters = (appKit as any).createAdapters.bind(appKit)

Expand Down
16 changes: 15 additions & 1 deletion packages/appkit/src/universal-adapter/client.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import type UniversalProvider from '@walletconnect/universal-provider'
import { AdapterBlueprint } from '../adapters/ChainAdapterBlueprint.js'
import { WcHelpersUtil } from '../utils/index.js'
import { ChainController, CoreHelperUtil } from '@reown/appkit-core'
import {
ChainController,
CoreHelperUtil,
ConnectionController,
OptionsController
} from '@reown/appkit-core'
import bs58 from 'bs58'
import { ConstantsUtil, type ChainNamespace } from '@reown/appkit-common'

export class UniversalAdapter extends AdapterBlueprint {
public constructor(options?: AdapterBlueprint.Params) {
super(options)
}
public async connectWalletConnect(onUri: (uri: string) => void) {
const connector = this.connectors.find(c => c.type === 'WALLET_CONNECT')

Expand All @@ -17,6 +25,12 @@ export class UniversalAdapter extends AdapterBlueprint {
)
}

if (OptionsController.state.useInjectedUniversalProvider && ConnectionController.state.wcUri) {
onUri(ConnectionController.state.wcUri)

return
}

provider.on('display_uri', (uri: string) => {
onUri(uri)
})
Expand Down
14 changes: 8 additions & 6 deletions packages/appkit/src/utils/TypesUtil.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { AppKitNetwork, CaipNetwork, ThemeVariables } from '@reown/appkit-common'
import type { AppKitNetwork, ThemeVariables } from '@reown/appkit-common'
import type { ChainAdapter, Metadata, OptionsControllerState, ThemeMode } from '@reown/appkit-core'
import type { AppKitSIWEClient } from '@reown/appkit-siwe'
import type UniversalProvider from '@walletconnect/universal-provider'

export type AppKitOptions = {
/**
Expand Down Expand Up @@ -72,9 +73,10 @@ export type AppKitOptions = {
* @see https://cloud.walletconnect.com/
*/
metadata?: Metadata
/**
* UniversalProvider instance to be used by AppKit.
* AppKit will generate its own instance by default in none provided
* @default undefined
*/
universalProvider?: UniversalProvider
} & OptionsControllerState

export type AppKitOptionsWithCaipNetworks = Omit<AppKitOptions, 'defaultNetwork' | 'networks'> & {
defaultNetwork?: CaipNetwork
networks: [CaipNetwork, ...CaipNetwork[]]
}
10 changes: 6 additions & 4 deletions packages/core/src/controllers/ConnectionController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,7 @@ export const ConnectionController = {
state.wcPairingExpiry = undefined
this.state.status = 'connected'
} else {
await this._getClient()?.connectWalletConnect?.(uri => {
state.wcUri = uri
state.wcPairingExpiry = CoreHelperUtil.getPairingExpiry()
})
await this._getClient()?.connectWalletConnect?.(uri => this.setUri(uri))
}
},

Expand Down Expand Up @@ -226,6 +223,11 @@ export const ConnectionController = {
StorageUtil.deleteWalletConnectDeepLink()
},

setUri(uri: string) {
state.wcUri = uri
state.wcPairingExpiry = CoreHelperUtil.getPairingExpiry()
},

setWcLinking(wcLinking: ConnectionControllerState['wcLinking']) {
state.wcLinking = wcLinking
},
Expand Down
7 changes: 7 additions & 0 deletions packages/core/src/controllers/OptionsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ export interface OptionsControllerStateInternal {
isSiweEnabled?: boolean
isUniversalProvider?: boolean
hasMultipleAddresses?: boolean
useInjectedUniversalProvider?: boolean
}

type StateKey = keyof OptionsControllerStatePublic | keyof OptionsControllerStateInternal
Expand Down Expand Up @@ -301,5 +302,11 @@ export const OptionsController = {

setAllowUnsupportedChain(allowUnsupportedChain: OptionsControllerState['allowUnsupportedChain']) {
state.allowUnsupportedChain = allowUnsupportedChain
},

setUsingInjectedUniversalProvider(
useInjectedUniversalProvider: OptionsControllerState['useInjectedUniversalProvider']
) {
state.useInjectedUniversalProvider = useInjectedUniversalProvider
}
}
1 change: 1 addition & 0 deletions packages/core/src/controllers/RouterController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export interface RouterControllerState {
| 'ConnectingExternal'
| 'ConnectingFarcaster'
| 'ConnectingWalletConnect'
| 'ConnectingWalletConnectBasic'
| 'ConnectingSiwe'
| 'ConnectingSocial'
| 'ConnectSocials'
Expand Down
1 change: 1 addition & 0 deletions packages/scaffold-ui/exports/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export * from '../src/views/w3m-connect-view/index.js'
export * from '../src/views/w3m-connecting-external-view/index.js'
export * from '../src/views/w3m-connecting-multi-chain-view/index.js'
export * from '../src/views/w3m-connecting-wc-view/index.js'
export * from '../src/views/w3m-connecting-wc-basic-view/index.js'
export * from '../src/views/w3m-choose-account-name-view/index.js'
export * from '../src/views/w3m-downloads-view/index.js'
export * from '../src/views/w3m-get-wallet-view/index.js'
Expand Down
2 changes: 2 additions & 0 deletions packages/scaffold-ui/src/modal/w3m-router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ export class W3mRouter extends LitElement {
return html`<w3m-connect-view walletGuide="explore"></w3m-connect-view>`
case 'ConnectingWalletConnect':
return html`<w3m-connecting-wc-view></w3m-connecting-wc-view>`
case 'ConnectingWalletConnectBasic':
return html`<w3m-connecting-wc-basic-view></w3m-connecting-wc-basic-view>`
case 'ConnectingExternal':
return html`<w3m-connecting-external-view></w3m-connecting-external-view>`
case 'ConnectingSiwe':
Expand Down
Loading

0 comments on commit 3cf3bc5

Please sign in to comment.