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

chore: Relay QR generation flow improvements #2652

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions packages/core/src/controllers/ConnectionController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export interface ConnectExternalOptions {
}

export interface ConnectionControllerClient {
connectWalletConnect: (onUri: (uri: string) => void) => Promise<void>
connectWalletConnect: () => Promise<void>
disconnect: () => Promise<void>
signMessage: (message: string) => Promise<string>
sendTransaction: (args: SendTransactionArgs) => Promise<`0x${string}` | null>
Expand Down Expand Up @@ -84,10 +84,12 @@ export const ConnectionController = {

async connectWalletConnect() {
StorageUtil.setConnectedConnector('WALLET_CONNECT')
await this._getClient().connectWalletConnect(uri => {
state.wcUri = uri
state.wcPairingExpiry = CoreHelperUtil.getPairingExpiry()
})
await this._getClient().connectWalletConnect()
},

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

async connectExternal(options: ConnectExternalOptions, chain?: Chain) {
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/utils/CoreHelperUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ export const CoreHelperUtil = {
},

isPairingExpired(expiry?: number) {
return expiry ? expiry - Date.now() <= ConstantsUtil.TEN_SEC_MS : true
// QR code lasts 15mins
return expiry ? expiry - Date.now() <= 15 * 60 * 1000 : true
},

isAllowedRetry(lastRetry: number) {
Expand Down
5 changes: 1 addition & 4 deletions packages/core/tests/controllers/ConnectionController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,7 @@ const type = 'AUTH' as ConnectorType
const storageSpy = vi.spyOn(StorageUtil, 'setConnectedConnector')

const client: ConnectionControllerClient = {
connectWalletConnect: async onUri => {
onUri(walletConnectUri)
await Promise.resolve(walletConnectUri)
},
connectWalletConnect: async () => Promise.resolve(),
disconnect: async () => Promise.resolve(),
signMessage: async (message: string) => Promise.resolve(message),
estimateGas: async () => Promise.resolve(BigInt(0)),
Expand Down
10 changes: 5 additions & 5 deletions packages/ethers/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,16 +194,12 @@ export class Web3Modal extends Web3ModalScaffold {
}

const connectionControllerClient: ConnectionControllerClient = {
connectWalletConnect: async onUri => {
connectWalletConnect: async () => {
const WalletConnectProvider = await this.getWalletConnectProvider()
if (!WalletConnectProvider) {
throw new Error('connectionControllerClient:getWalletConnectUri - provider is undefined')
}

WalletConnectProvider.on('display_uri', (uri: string) => {
onUri(uri)
})

// When connecting through walletconnect, we need to set the clientId in the store
const clientId = await WalletConnectProvider.signer?.client?.core?.crypto?.getClientId()
if (clientId) {
Expand Down Expand Up @@ -699,6 +695,10 @@ export class Web3Modal extends Web3ModalScaffold {

this.walletConnectProvider = await EthereumProvider.init(walletConnectProviderOptions)

this.walletConnectProvider.on('display_uri', (uri: string) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor but you could also

this.walletConnectProvider.on('display_uri', this.setQRCodeURI)

this.setQRCodeURI(uri)
})

await this.checkActiveWalletConnectProvider()
}

Expand Down
10 changes: 5 additions & 5 deletions packages/ethers5/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,16 +169,12 @@ export class Web3Modal extends Web3ModalScaffold {
}

const connectionControllerClient: ConnectionControllerClient = {
connectWalletConnect: async onUri => {
connectWalletConnect: async () => {
const WalletConnectProvider = await this.getWalletConnectProvider()
if (!WalletConnectProvider) {
throw new Error('connectionControllerClient:getWalletConnectUri - provider is undefined')
}

WalletConnectProvider.on('display_uri', (uri: string) => {
onUri(uri)
})

const params = await siweConfig?.getMessageParams?.()
// Must perform these checks to satify optional types
if (siweConfig?.options?.enabled && params && Object.keys(params || {}).length > 0) {
Expand Down Expand Up @@ -542,6 +538,10 @@ export class Web3Modal extends Web3ModalScaffold {

this.walletConnectProvider = await EthereumProvider.init(walletConnectProviderOptions)

this.walletConnectProvider.on('display_uri', (uri: string) => {
this.setQRCodeURI(uri)
})

await this.checkActiveWalletConnectProvider()
}

Expand Down
110 changes: 60 additions & 50 deletions packages/scaffold-ui/src/views/w3m-connecting-wc-view/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
AssetUtil,
ConnectionController,
ConnectorController,
ConstantsUtil,
CoreHelperUtil,
EventsController,
ModalController,
Expand All @@ -21,8 +20,6 @@ export class W3mConnectingWcView extends LitElement {
// -- Members ------------------------------------------- //
private interval?: ReturnType<typeof setInterval> = undefined

private lastRetry = Date.now()

private wallet = RouterController.state.data?.wallet

// -- State & Properties -------------------------------- //
Expand All @@ -33,7 +30,11 @@ export class W3mConnectingWcView extends LitElement {
public constructor() {
super()
this.initializeConnection()
this.interval = setInterval(this.initializeConnection.bind(this), ConstantsUtil.TEN_SEC_MS)
/*
* For 15minutes, the relay handles retries and reconnections internally.
* We re-initialize the connection after that
*/
this.interval = setInterval(this.initializeConnection.bind(this), 15 * 60 * 1000)
Copy link
Member

@chris13524 chris13524 Aug 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the Sign SDK that handles this, not the relay. And I would be concerned that depending on this behavior is dangerous. E.g. if the SDK is updated to a version w/o this

}

public override disconnectedCallback() {
Expand All @@ -55,58 +56,47 @@ export class W3mConnectingWcView extends LitElement {
}

// -- Private ------------------------------------------- //
private async initializeConnection(retry = false) {
try {
const { wcPairingExpiry } = ConnectionController.state
if (retry || CoreHelperUtil.isPairingExpired(wcPairingExpiry)) {
if (this.wallet) {
const url = AssetUtil.getWalletImage(this.wallet)
if (url) {
StorageUtil.setConnectedWalletImageUrl(url)
}
} else {
const connectors = ConnectorController.state.connectors
const connector = connectors.find(c => c.type === 'WALLET_CONNECT')
const url = AssetUtil.getConnectorImage(connector)
if (url) {
StorageUtil.setConnectedWalletImageUrl(url)
}
}

await ConnectionController.connectWalletConnect()
this.finalizeConnection()
if (
StorageUtil.getConnectedConnector() === 'AUTH' &&
OptionsController.state.hasMultipleAddresses
) {
RouterController.push('SelectAddresses')
} else if (OptionsController.state.isSiweEnabled) {
const { SIWEController } = await import('@web3modal/siwe')
if (SIWEController.state.status === 'success') {
ModalController.close()
} else {
RouterController.push('ConnectingSiwe')
}
} else {
ModalController.close()
}
private setConnectorImage() {
if (this.wallet) {
const url = AssetUtil.getWalletImage(this.wallet)
if (url) {
StorageUtil.setConnectedWalletImageUrl(url)
}
} else {
const connectors = ConnectorController.state.connectors
const connector = connectors.find(c => c.type === 'WALLET_CONNECT')
const url = AssetUtil.getConnectorImage(connector)
if (url) {
StorageUtil.setConnectedWalletImageUrl(url)
}
}
}

private async initializeConnection() {
try {
await ConnectionController.connectWalletConnect()
await this.finalizeConnection()
} catch (error) {
/**
* Potential errors:
* 1. User declined connection
* 2. QR Cannot be generated (network issue?)
* 3. QR Code expired
*/
EventsController.sendEvent({
type: 'track',
event: 'CONNECT_ERROR',
properties: { message: (error as BaseError)?.message ?? 'Unknown' }
properties: { message: (error as BaseError)?.message || 'Unknown' }
})
ConnectionController.setWcError(true)
if (CoreHelperUtil.isAllowedRetry(this.lastRetry)) {
SnackController.showError('Declined')
this.lastRetry = Date.now()
this.initializeConnection(true)
}
// ConnectionController.setWcError(true)
SnackController.showError((error as BaseError)?.message || 'Declined')

// We need to re-generate the session
this.initializeConnection()
}
}

private finalizeConnection() {
private async finalizeConnection() {
const { wcLinking, recentWallet } = ConnectionController.state

if (wcLinking) {
Expand All @@ -124,6 +114,26 @@ export class W3mConnectingWcView extends LitElement {
name: this.wallet?.name || 'Unknown'
}
})

// Setup connector images
this.setConnectorImage()

// Route after connect
if (
StorageUtil.getConnectedConnector() === 'AUTH' &&
OptionsController.state.hasMultipleAddresses
) {
RouterController.push('SelectAddresses')
} else if (OptionsController.state.isSiweEnabled) {
const { SIWEController } = await import('@web3modal/siwe')
if (SIWEController.state.status === 'success') {
ModalController.close()
} else {
RouterController.push('ConnectingSiwe')
}
} else {
ModalController.close()
}
}

private determinePlatforms() {
Expand Down Expand Up @@ -171,17 +181,17 @@ export class W3mConnectingWcView extends LitElement {
return html`<w3m-connecting-wc-browser></w3m-connecting-wc-browser>`
case 'desktop':
return html`
<w3m-connecting-wc-desktop .onRetry=${() => this.initializeConnection(true)}>
<w3m-connecting-wc-desktop .onRetry=${() => this.initializeConnection()}>
</w3m-connecting-wc-desktop>
`
case 'web':
return html`
<w3m-connecting-wc-web .onRetry=${() => this.initializeConnection(true)}>
<w3m-connecting-wc-web .onRetry=${() => this.initializeConnection()}>
</w3m-connecting-wc-web>
`
case 'mobile':
return html`
<w3m-connecting-wc-mobile isMobile .onRetry=${() => this.initializeConnection(true)}>
<w3m-connecting-wc-mobile isMobile .onRetry=${() => this.initializeConnection()}>
</w3m-connecting-wc-mobile>
`
case 'qrcode':
Expand Down
4 changes: 4 additions & 0 deletions packages/scaffold/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,10 @@ export class Web3ModalScaffold {
BlockchainApiController.setClientId(clientId)
}

protected setQRCodeURI: (typeof ConnectionController)['setQRCodeURI'] = uri => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the uri is not only for the QR code but for all connections using WalletConnect protocol. maybe setURI or setWalletConnectURI works better?

ConnectionController.setQRCodeURI(uri)
}

// -- Private ------------------------------------------------------------------
private async initControllers(options: ScaffoldOptions) {
ChainController.initialize([
Expand Down
13 changes: 10 additions & 3 deletions packages/solana/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,16 +121,14 @@ export class Web3Modal extends Web3ModalScaffold {
}

const connectionControllerClient: ConnectionControllerClient = {
connectWalletConnect: async onUri => {
connectWalletConnect: async () => {
const WalletConnectProvider = await this.WalletConnectConnector.getProvider()
if (!WalletConnectProvider) {
throw new Error('connectionControllerClient:getWalletConnectUri - provider is undefined')
}

WalletConnectProvider.on('display_uri', onUri)
const address = await this.WalletConnectConnector.connect()
this.setWalletConnectProvider(address)
WalletConnectProvider.removeListener('display_uri', onUri)
},

connectExternal: async ({ id }) => {
Expand Down Expand Up @@ -228,6 +226,15 @@ export class Web3Modal extends Web3ModalScaffold {
chains,
qrcode: true
})

this.WalletConnectConnector.getProvider().then(provider => {
if (provider) {
provider.on('display_uri', (uri: string) => {
this.setQRCodeURI(uri)
})
}
})

SolStoreUtil.setConnection(
new Connection(
SolHelpersUtil.detectRpcUrl(chain, OptionsController.state.projectId),
Expand Down
2 changes: 1 addition & 1 deletion packages/solana/src/connectors/walletConnectConnector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { UniversalProviderFactory } from './universalProvider.js'
import { BaseConnector } from './baseConnector.js'

import type { Signer } from '@solana/web3.js'
import type UniversalProvider from '@walletconnect/universal-provider'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This actually breaks in some bundlers (default import of UP), it should be a name import.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@glitch-txs can we make a lint or tsconfig role to disallow this?

import UniversalProvider from '@walletconnect/universal-provider'

import type { Connector } from './baseConnector.js'
import type { Chain } from '../utils/scaffold/SolanaTypesUtil.js'
Expand Down
Loading
Loading