Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v3] WalletConnect connector logic, hook up disconnect events to acc view #1202

Merged
merged 15 commits into from
Jul 17, 2023
4 changes: 2 additions & 2 deletions apps/laboratory/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
"@emotion/react": "11.11.1",
"@emotion/styled": "11.11.0",
"next": "13.4.9",
"framer-motion": "10.12.18",
"framer-motion": "10.12.20",
"wagmi": "1.3.8",
"viem": "1.2.14"
"viem": "1.2.15"
}
}
2 changes: 1 addition & 1 deletion apps/laboratory/src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const { chains, publicClient } = configureChains([mainnet], [publicProvider()])
const wagmiConfig = createConfig({
autoConnect: true,
connectors: [
new WalletConnectConnector({ chains, options: { projectId } }),
new WalletConnectConnector({ chains, options: { projectId, showQrModal: false } }),
new InjectedConnector({ chains, options: { shimDisconnect: true } }),
new CoinbaseWalletConnector({ chains, options: { appName: 'Web3Modal' } })
],
Expand Down
2 changes: 1 addition & 1 deletion dangerfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ async function checkScaffoldHtmlPackage() {
fail(`${f} is a scaffold element, but does not define w3m- prefix`)
}

if (diff?.added.includes('.subscribe(') && !diff.added.includes('this.unsubscribe.forEach')) {
if (diff?.added.includes('.subscribe') && !diff.added.includes('this.unsubscribe.forEach')) {
fail(`${f} is subscribing to controller states without unsubscribe logic`)
}
}
Expand Down
24 changes: 12 additions & 12 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"devDependencies": {
"@typescript-eslint/eslint-plugin": "6.0.0",
"@typescript-eslint/parser": "6.0.0",
"@types/react": "18.2.14",
"@types/react": "18.2.15",
"@types/react-dom": "18.2.7",
"eslint": "8.44.0",
"eslint-config-prettier": "8.8.0",
Expand Down
1 change: 1 addition & 0 deletions packages/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,6 @@ export type {
} from './src/controllers/ConnectorController'

// -- Utils -------------------------------------------------------------------
export { ConstantsUtil } from './src/utils/ConstantsUtil'
export { CoreHelperUtil } from './src/utils/CoreHelperUtil'
export type { CaipAddress, CaipChainId } from './src/utils/TypeUtils'
12 changes: 8 additions & 4 deletions packages/core/src/controllers/AccountController.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { subscribeKey } from 'valtio/utils'
import { proxy } from 'valtio/vanilla'
import { subscribeKey as subKey } from 'valtio/utils'
import { proxy, subscribe as sub } from 'valtio/vanilla'
import { CoreHelperUtil } from '../utils/CoreHelperUtil'
import type { CaipAddress } from '../utils/TypeUtils'

Expand All @@ -24,8 +24,12 @@ const state = proxy<AccountControllerState>({
export const AccountController = {
state,

subscribe<K extends StateKey>(key: K, callback: (value: AccountControllerState[K]) => void) {
return subscribeKey(state, key, callback)
subscribe(callback: (newState: AccountControllerState) => void) {
return sub(state, () => callback(state))
},

subscribeKey<K extends StateKey>(key: K, callback: (value: AccountControllerState[K]) => void) {
return subKey(state, key, callback)
},

setIsConnected(isConnected: AccountControllerState['isConnected']) {
Expand Down
29 changes: 24 additions & 5 deletions packages/core/src/controllers/ConnectionController.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { subscribeKey as subKey } from 'valtio/utils'
import { proxy, ref } from 'valtio/vanilla'
import { CoreHelperUtil } from '../utils/CoreHelperUtil'

// -- Types --------------------------------------------- //
export interface ConnectionControllerClient {
Expand All @@ -9,8 +11,11 @@ export interface ConnectionControllerClient {

export interface ConnectionControllerState {
_client?: ConnectionControllerClient
walletConnectUri?: string
wcUri?: string
wcPromise?: Promise<void>
wcPairingExpiry?: number
}
type StateKey = keyof ConnectionControllerState

// -- State --------------------------------------------- //
const state = proxy<ConnectionControllerState>({})
Expand All @@ -19,6 +24,13 @@ const state = proxy<ConnectionControllerState>({})
export const ConnectionController = {
state,

subscribeKey<K extends StateKey>(
key: K,
callback: (value: ConnectionControllerState[K]) => void
) {
return subKey(state, key, callback)
},

_getClient() {
if (!state._client) {
throw new Error('ConnectionController client not set')
Expand All @@ -31,18 +43,25 @@ export const ConnectionController = {
state._client = ref(client)
},

async connectWalletConnect() {
await this._getClient().connectWalletConnect(uri => {
state.walletConnectUri = uri
connectWalletConnect() {
state.wcPromise = this._getClient().connectWalletConnect(uri => {
state.wcUri = uri
state.wcPairingExpiry = CoreHelperUtil.getPairingExpiry()
})
},

async connectExternal(id: string) {
await this._getClient().connectExternal?.(id)
},

resetWcConnection() {
state.wcUri = undefined
state.wcPairingExpiry = undefined
state.wcPromise = undefined
},

async disconnect() {
await this._getClient().disconnect()
state.walletConnectUri = ''
this.resetWcConnection()
}
}
6 changes: 3 additions & 3 deletions packages/core/src/controllers/ConnectorController.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { subscribeKey } from 'valtio/utils'
import { subscribeKey as subKey } from 'valtio/utils'
import { proxy } from 'valtio/vanilla'

// -- Types --------------------------------------------- //
Expand Down Expand Up @@ -26,8 +26,8 @@ const state = proxy<ConnectorControllerState>({
export const ConnectorController = {
state,

subscribe<K extends StateKey>(key: K, callback: (value: ConnectorControllerState[K]) => void) {
return subscribeKey(state, key, callback)
subscribeKey<K extends StateKey>(key: K, callback: (value: ConnectorControllerState[K]) => void) {
return subKey(state, key, callback)
},

setConnectors(connectors: ConnectorControllerState['connectors']) {
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/controllers/ModalController.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { subscribeKey } from 'valtio/utils'
import { subscribeKey as subKey } from 'valtio/utils'
import { proxy } from 'valtio/vanilla'
import { AccountController } from './AccountController'
import type { RouterControllerState } from './RouterController'
Expand Down Expand Up @@ -26,8 +26,8 @@ const state = proxy<ModalControllerState>({
export const ModalController = {
state,

subscribe<K extends StateKey>(key: K, callback: (value: ModalControllerState[K]) => void) {
return subscribeKey(state, key, callback)
subscribeKey<K extends StateKey>(key: K, callback: (value: ModalControllerState[K]) => void) {
return subKey(state, key, callback)
},

open(options?: ModalControllerArguments['open']) {
Expand Down
8 changes: 4 additions & 4 deletions packages/core/src/controllers/RouterController.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { subscribeKey } from 'valtio/utils'
import { subscribeKey as subKey } from 'valtio/utils'
import { proxy } from 'valtio/vanilla'
import type { Connector } from './ConnectorController'

// -- Types --------------------------------------------- //
export interface RouterControllerState {
view: 'Account' | 'Connect' | 'ConnectingExternal' | 'Networks'
view: 'Account' | 'Connect' | 'ConnectingExternal' | 'ConnectingWalletConnect' | 'Networks'
history: RouterControllerState['view'][]
data?: {
connector: Connector
Expand All @@ -23,8 +23,8 @@ type StateKey = keyof RouterControllerState
export const RouterController = {
state,

subscribe<K extends StateKey>(key: K, callback: (value: RouterControllerState[K]) => void) {
return subscribeKey(state, key, callback)
subscribeKey<K extends StateKey>(key: K, callback: (value: RouterControllerState[K]) => void) {
return subKey(state, key, callback)
},

push(view: RouterControllerState['view'], data?: RouterControllerState['data']) {
Expand Down
7 changes: 7 additions & 0 deletions packages/core/src/utils/ConstantsUtil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const ConstantsUtil = {
FOUR_MINUTES_MS: 240_000,

TEN_SEC_MS: 10_000,

ONE_SEC_MS: 1_000
}
17 changes: 15 additions & 2 deletions packages/core/src/utils/CoreHelperUtil.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { ConstantsUtil } from './ConstantsUtil'
import { CaipAddress } from './TypeUtils'

export const CoreHelperUtil = {
isClient() {
return typeof window !== 'undefined'
},

getPlainAddress(caipAddress: CaipAddress) {
return caipAddress.split(':')[2]
isPairingExpired(expiry?: number) {
return expiry ? expiry - Date.now() <= ConstantsUtil.TEN_SEC_MS : true
},

isAllowedRetry(lastRetry: number) {
return Date.now() - lastRetry >= ConstantsUtil.ONE_SEC_MS
},

truncateAddress(address: string) {
Expand All @@ -15,5 +20,13 @@ export const CoreHelperUtil = {

copyToClopboard(text: string) {
navigator.clipboard.writeText(text)
},

getPairingExpiry() {
return Date.now() + ConstantsUtil.FOUR_MINUTES_MS
},

getPlainAddress(caipAddress: CaipAddress) {
return caipAddress.split(':')[2]
}
}
15 changes: 12 additions & 3 deletions packages/core/tests/controllers/ConnectionController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,13 @@ describe('ModalController', () => {

it('should update state correctly on disconnect()', async () => {
await ConnectionController.disconnect()
expect(ConnectionController.state.walletConnectUri).toEqual('')
expect(ConnectionController.state.wcUri).toEqual(undefined)
expect(ConnectionController.state.wcPairingExpiry).toEqual(undefined)
expect(ConnectionController.state.wcPromise).toEqual(undefined)
})

it('should not throw on connectWalletConnect()', async () => {
await ConnectionController.connectWalletConnect()
it('should not throw on connectWalletConnect()', () => {
ConnectionController.connectWalletConnect()
})

it('should not throw on connectExternal()', async () => {
Expand All @@ -52,4 +54,11 @@ describe('ModalController', () => {

await ConnectionController.connectExternal(externalId)
})

it('should update state correctly on resetWcConnection()', () => {
ConnectionController.resetWcConnection()
expect(ConnectionController.state.wcUri).toEqual(undefined)
expect(ConnectionController.state.wcPairingExpiry).toEqual(undefined)
expect(ConnectionController.state.wcPromise).toEqual(undefined)
})
})
2 changes: 2 additions & 0 deletions packages/scaffold-html/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ export * from './src/modal/w3m-router'
export * from './src/views/w3m-account-view'
export * from './src/views/w3m-connect-view'
export * from './src/views/w3m-connecting-external-view'
export * from './src/views/w3m-connecting-wc-view'

export * from './src/partials/w3m-header'
export * from './src/partials/w3m-wc-connecting-qrcode'

export { Web3ModalScaffoldHtml } from './src/client'

Expand Down
4 changes: 4 additions & 0 deletions packages/scaffold-html/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ export class Web3ModalScaffoldHtml {
ConnectorController.setConnectors(connectors)
}

protected resetWcConnection: (typeof ConnectionController)['resetWcConnection'] = () => {
ConnectionController.resetWcConnection()
}

// -- Private ------------------------------------------------------------------
private setControllerClients(options: Options) {
NetworkController.setClient(options.networkControllerClient)
Expand Down
2 changes: 1 addition & 1 deletion packages/scaffold-html/src/modal/w3m-modal/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export class W3mModal extends LitElement {
initializeTheming()
setColorTheme('dark')
this.unsubscribe.push(
ModalController.subscribe('open', open => (open ? this.onOpen() : this.onClose()))
ModalController.subscribeKey('open', open => (open ? this.onOpen() : this.onClose()))
)
}

Expand Down
4 changes: 3 additions & 1 deletion packages/scaffold-html/src/modal/w3m-router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export class W3mRouter extends LitElement {

public constructor() {
super()
this.unsubscribe.push(RouterController.subscribe('view', view => this.onRouteChange(view)))
this.unsubscribe.push(RouterController.subscribeKey('view', view => this.onRouteChange(view)))
}

public firstUpdated() {
Expand Down Expand Up @@ -50,6 +50,8 @@ export class W3mRouter extends LitElement {
switch (this.view) {
case 'Connect':
return html`<w3m-connect-view></w3m-connect-view>`
case 'ConnectingWalletConnect':
return html`<w3m-connecting-wc-view></w3m-connecting-wc-view>`
case 'ConnectingExternal':
return html`<w3m-connecting-external-view></w3m-connecting-external-view>`
case 'Account':
Expand Down
Loading