From 4ecae7481c420b93f33a1b7a36fe0e8af00ccd27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lcio=20Franco?= Date: Tue, 7 Jan 2025 17:00:04 +0800 Subject: [PATCH 01/19] fix: remove unused timer --- packages/walletconnect-connector/src/WalletConnectConnector.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/walletconnect-connector/src/WalletConnectConnector.ts b/packages/walletconnect-connector/src/WalletConnectConnector.ts index fdb09828..6f5c9570 100644 --- a/packages/walletconnect-connector/src/WalletConnectConnector.ts +++ b/packages/walletconnect-connector/src/WalletConnectConnector.ts @@ -344,7 +344,6 @@ export class WalletConnectConnector extends PredicateConnector { if (!ethProvider) return; - await new Promise((resolve) => setTimeout(resolve, 1000)); this.signAndValidate(ethProvider, address) .then(() => { clearTimeout(validationTimeout); From 275c012b3d382790bae0d4c1211053cf26ee062a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lcio=20Franco?= Date: Tue, 7 Jan 2025 18:43:53 +0800 Subject: [PATCH 02/19] feat: add presignature dialog --- .../react/src/providers/FuelUIProvider.tsx | 8 +-- .../PreSignature/PreSignatureDialog.tsx | 67 +++++++++++++++++++ packages/react/src/ui/Connect/index.tsx | 11 +-- 3 files changed, 76 insertions(+), 10 deletions(-) create mode 100644 packages/react/src/ui/Connect/components/PreSignature/PreSignatureDialog.tsx diff --git a/packages/react/src/providers/FuelUIProvider.tsx b/packages/react/src/providers/FuelUIProvider.tsx index f11a6b87..cedb2649 100644 --- a/packages/react/src/providers/FuelUIProvider.tsx +++ b/packages/react/src/providers/FuelUIProvider.tsx @@ -29,6 +29,7 @@ export enum Routes { INSTALL = 'install', CONNECTING = 'connecting', EXTERNAL_DISCLAIMER = 'disclaimer', + PRESIGNATURE = 'presignature', } export type FuelUIContextType = { @@ -164,10 +165,6 @@ export function FuelUIProvider({ [handleStartConnection], ); - const setRoute = useCallback((state: Routes) => { - setDialogRoute(state); - }, []); - const isLoading = useMemo(() => { const hasLoadedConnectors = (fuelConfig.connectors || []).length > connectors.length; @@ -224,7 +221,7 @@ export function FuelUIProvider({ // Dialog only dialog: { route: dialogRoute, - setRoute, + setRoute: setDialogRoute, connector, isOpen, connect: handleSelectConnector, @@ -248,7 +245,6 @@ export function FuelUIProvider({ isOpen, handleCancel, handleStartConnection, - setRoute, handleSelectConnector, handleConnect, handleRetryConnect, diff --git a/packages/react/src/ui/Connect/components/PreSignature/PreSignatureDialog.tsx b/packages/react/src/ui/Connect/components/PreSignature/PreSignatureDialog.tsx new file mode 100644 index 00000000..ba08f973 --- /dev/null +++ b/packages/react/src/ui/Connect/components/PreSignature/PreSignatureDialog.tsx @@ -0,0 +1,67 @@ +import { ConnectorIcon } from '../Core/ConnectorIcon'; + +import { Spinner } from '../../../../icons/Spinner'; +import { useConnectUI } from '../../../../providers/FuelUIProvider'; +import { + ConnectorButton, + ConnectorButtonPrimary, + ConnectorContent, + ConnectorDescription, + ConnectorDescriptionError, + ConnectorImage, + ConnectorTitle, +} from '../Connector/styles'; + +export function PreSignatureDialog() { + const { + error, + isConnecting, + theme, + cancel, + dialog: { connector }, + } = useConnectUI(); + + if (!connector) return null; + + return ( +
+ + + + + {connector.name} + {error ? ( + {error.message} + ) : isConnecting ? ( + + Requesting signature to
{connector.name}. +
+ ) : ( + + Sign this message to prove you own this wallet and proceed. +
+ Canceling will disconnect you. +
+ )} +
+ {/* @TODO: Add Cancel Styles */} + cancel()}> + Cancel + + {isConnecting ? ( + + + + ) : ( + alert(connector)}> + Sign + + )} +
+ ); +} diff --git a/packages/react/src/ui/Connect/index.tsx b/packages/react/src/ui/Connect/index.tsx index 943d0286..ba6cf366 100644 --- a/packages/react/src/ui/Connect/index.tsx +++ b/packages/react/src/ui/Connect/index.tsx @@ -16,15 +16,18 @@ import { Connecting } from './components/Connector/Connecting'; import { DialogContent } from './components/Core/DialogContent'; import { DialogFuel } from './components/Core/DialogFuel'; import { ExternalDisclaimer } from './components/ExternalDisclaimer/ExternalDisclaimer'; +import { PreSignatureDialog } from './components/PreSignature/PreSignatureDialog'; -const ConnectRoutes = ({ state }: { state: Routes }) => { - switch (state) { +const ConnectRoutes = ({ route }: { route: Routes }) => { + switch (route) { case Routes.LIST: return ; case Routes.INSTALL: return ; case Routes.EXTERNAL_DISCLAIMER: return ; + case Routes.PRESIGNATURE: + return ; case Routes.CONNECTING: return ; default: @@ -36,7 +39,7 @@ export function Connect() { const { theme, cancel, - dialog: { isOpen, route: state, connector, back }, + dialog: { isOpen, route, connector, back }, } = useConnectUI(); const handleOpenChange = (openState: boolean) => { @@ -55,7 +58,7 @@ export function Connect() { - + From 80f12b146c34a2b8aa1a2a49858330f642838a96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lcio=20Franco?= Date: Tue, 7 Jan 2025 19:30:02 +0800 Subject: [PATCH 03/19] chore: remove pre signature dialog from the main dialog --- packages/react/src/providers/FuelUIProvider.tsx | 1 - packages/react/src/ui/Connect/index.tsx | 3 --- 2 files changed, 4 deletions(-) diff --git a/packages/react/src/providers/FuelUIProvider.tsx b/packages/react/src/providers/FuelUIProvider.tsx index cedb2649..b82b9b34 100644 --- a/packages/react/src/providers/FuelUIProvider.tsx +++ b/packages/react/src/providers/FuelUIProvider.tsx @@ -29,7 +29,6 @@ export enum Routes { INSTALL = 'install', CONNECTING = 'connecting', EXTERNAL_DISCLAIMER = 'disclaimer', - PRESIGNATURE = 'presignature', } export type FuelUIContextType = { diff --git a/packages/react/src/ui/Connect/index.tsx b/packages/react/src/ui/Connect/index.tsx index ba6cf366..1d6240bc 100644 --- a/packages/react/src/ui/Connect/index.tsx +++ b/packages/react/src/ui/Connect/index.tsx @@ -16,7 +16,6 @@ import { Connecting } from './components/Connector/Connecting'; import { DialogContent } from './components/Core/DialogContent'; import { DialogFuel } from './components/Core/DialogFuel'; import { ExternalDisclaimer } from './components/ExternalDisclaimer/ExternalDisclaimer'; -import { PreSignatureDialog } from './components/PreSignature/PreSignatureDialog'; const ConnectRoutes = ({ route }: { route: Routes }) => { switch (route) { @@ -26,8 +25,6 @@ const ConnectRoutes = ({ route }: { route: Routes }) => { return ; case Routes.EXTERNAL_DISCLAIMER: return ; - case Routes.PRESIGNATURE: - return ; case Routes.CONNECTING: return ; default: From 10cf8b46a3bf220f77b2a0bfcdc42dc815c064c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lcio=20Franco?= Date: Tue, 7 Jan 2025 23:06:25 +0800 Subject: [PATCH 04/19] fix: prevent event propagation when wallet connect modal is opened --- .../react/src/ui/Connect/components/Core/DialogContent.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/react/src/ui/Connect/components/Core/DialogContent.tsx b/packages/react/src/ui/Connect/components/Core/DialogContent.tsx index b5cb419c..c24bb5e4 100644 --- a/packages/react/src/ui/Connect/components/Core/DialogContent.tsx +++ b/packages/react/src/ui/Connect/components/Core/DialogContent.tsx @@ -28,6 +28,13 @@ export const DialogContent = (props: Dialog.DialogContentProps) => { style={dialogContentStyle} {...props} className="fuel-connectors-dialog-content" + // Workaround to prevent closing dialog when interacting with WalletConnect Modal + onPointerDownOutside={(e) => { + const walletConnectDialog = document.querySelector('w3m-modal'); + if (walletConnectDialog?.classList.contains('open')) { + e.preventDefault(); + } + }} /> ); }; From c631c6e207331aa9158d89ef93ef4462b9783b95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lcio=20Franco?= Date: Tue, 7 Jan 2025 23:06:57 +0800 Subject: [PATCH 05/19] chore: typo --- packages/walletconnect-connector/src/constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/walletconnect-connector/src/constants.ts b/packages/walletconnect-connector/src/constants.ts index 0e2f0a37..f11750b4 100644 --- a/packages/walletconnect-connector/src/constants.ts +++ b/packages/walletconnect-connector/src/constants.ts @@ -3,7 +3,7 @@ export const ETHEREUM_ICON = 'data:image/svg+xml;utf8;base64,PHN2ZyB3aWR0aD0iNTEyIiBoZWlnaHQ9IjUxMiIgdmlld0JveD0iMCAwIDUxMiA1MTIiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxwYXRoIGQ9Ik0yNTMgMzM1LjEyMkwyNTUuODg2IDMzOEwzODggMjU5Ljk4N0wyNTUuODg2IDQxTDI1MyA1MC43OTgzVjMzNS4xMjJaIiBmaWxsPSIjMzQzNDM0Ii8+CjxwYXRoIGQ9Ik0yNTYgMzM4VjQxTDEyNCAyNTkuOTg2TDI1NiAzMzhaIiBmaWxsPSIjOEM4QzhDIi8+CjxwYXRoIGQ9Ik0yNTQgNDY1LjI4MUwyNTUuNjI4IDQ3MEwzODggMjg1TDI1NS42MjkgMzYyLjU2M0wyNTQuMDAxIDM2NC41MzJMMjU0IDQ2NS4yODFaIiBmaWxsPSIjM0MzQzNCIi8+CjxwYXRoIGQ9Ik0xMjQgMjg1TDI1NiA0NzBWMzYyLjU2MkwxMjQgMjg1WiIgZmlsbD0iIzhDOEM4QyIvPgo8cGF0aCBkPSJNMjU2IDIwMFYzMzhMMzg4IDI1OS45ODhMMjU2IDIwMFoiIGZpbGw9IiMxNDE0MTQiLz4KPHBhdGggZD0iTTI1NiAyMDBMMTI0IDI1OS45ODhMMjU2IDMzOFYyMDBaIiBmaWxsPSIjMzkzOTM5Ii8+Cjwvc3ZnPgo='; // 1 minute timeout for request signature -export const SINGATURE_VALIDATION_TIMEOUT = 1000 * 60; +export const SIGNATURE_VALIDATION_TIMEOUT = 1000 * 60; export const HAS_WINDOW = typeof window !== 'undefined'; export const WINDOW = HAS_WINDOW ? window : null; From 52cb891e6af96e91ab23e9b2bc440c50e9335c27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lcio=20Franco?= Date: Tue, 7 Jan 2025 23:07:06 +0800 Subject: [PATCH 06/19] chore: comment --- .../react/src/ui/Connect/components/Connector/Connecting.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react/src/ui/Connect/components/Connector/Connecting.tsx b/packages/react/src/ui/Connect/components/Connector/Connecting.tsx index fb6e6c60..cf79e308 100644 --- a/packages/react/src/ui/Connect/components/Connector/Connecting.tsx +++ b/packages/react/src/ui/Connect/components/Connector/Connecting.tsx @@ -27,6 +27,7 @@ export function Connecting({ className }: ConnectorProps) { isConnected, } = useConnectUI(); + // Auto-close connecting useEffect(() => { if (isConnected && route === Routes.CONNECTING && !isConnecting) { cancel(); From 14f64a2998994937fa7c76a4a0d22896b9c2219b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lcio=20Franco?= Date: Tue, 7 Jan 2025 23:07:38 +0800 Subject: [PATCH 07/19] feat: split connect flow into two steps before requesting signatures --- .../PreSignature/PreSignatureDialog.tsx | 67 ---------- .../src/WalletConnectConnector.ts | 115 ++++++++---------- 2 files changed, 53 insertions(+), 129 deletions(-) delete mode 100644 packages/react/src/ui/Connect/components/PreSignature/PreSignatureDialog.tsx diff --git a/packages/react/src/ui/Connect/components/PreSignature/PreSignatureDialog.tsx b/packages/react/src/ui/Connect/components/PreSignature/PreSignatureDialog.tsx deleted file mode 100644 index ba08f973..00000000 --- a/packages/react/src/ui/Connect/components/PreSignature/PreSignatureDialog.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { ConnectorIcon } from '../Core/ConnectorIcon'; - -import { Spinner } from '../../../../icons/Spinner'; -import { useConnectUI } from '../../../../providers/FuelUIProvider'; -import { - ConnectorButton, - ConnectorButtonPrimary, - ConnectorContent, - ConnectorDescription, - ConnectorDescriptionError, - ConnectorImage, - ConnectorTitle, -} from '../Connector/styles'; - -export function PreSignatureDialog() { - const { - error, - isConnecting, - theme, - cancel, - dialog: { connector }, - } = useConnectUI(); - - if (!connector) return null; - - return ( -
- - - - - {connector.name} - {error ? ( - {error.message} - ) : isConnecting ? ( - - Requesting signature to
{connector.name}. -
- ) : ( - - Sign this message to prove you own this wallet and proceed. -
- Canceling will disconnect you. -
- )} -
- {/* @TODO: Add Cancel Styles */} - cancel()}> - Cancel - - {isConnecting ? ( - - - - ) : ( - alert(connector)}> - Sign - - )} -
- ); -} diff --git a/packages/walletconnect-connector/src/WalletConnectConnector.ts b/packages/walletconnect-connector/src/WalletConnectConnector.ts index 6f5c9570..d54f123b 100644 --- a/packages/walletconnect-connector/src/WalletConnectConnector.ts +++ b/packages/walletconnect-connector/src/WalletConnectConnector.ts @@ -43,7 +43,7 @@ import { stringToHex } from 'viem'; import { ETHEREUM_ICON, HAS_WINDOW, - SINGATURE_VALIDATION_TIMEOUT, + SIGNATURE_VALIDATION_TIMEOUT, WINDOW, } from './constants'; import type { WalletConnectConfig } from './types'; @@ -193,7 +193,7 @@ export class WalletConnectConnector extends PredicateConnector { protected async getAccountAddresses(): Promise> { const wagmiConfig = this.getWagmiConfig(); if (!wagmiConfig) return null; - const addresses = getAccount(wagmiConfig).addresses || []; + const { addresses = [] } = getAccount(wagmiConfig); const accountsValidations = await this.getAccountValidations( addresses as `0x${string}`[], ); @@ -240,53 +240,40 @@ export class WalletConnectConnector extends PredicateConnector { } public async connect(): Promise { + const wagmiConfig = this.getWagmiConfig(); + if (!wagmiConfig) throw new Error('Wagmi config not found'); + + // User might have connected already, now let's ask for the signatures + const state = await this.requestSignatures(wagmiConfig); + if (state === 'validated') { + return true; + } + + // User not connected, let's show the WalletConnect modal this.createModal(); - const result = await new Promise((resolve, reject) => { - this.web3Modal.open(); - const wagmiConfig = this.getWagmiConfig(); - const unsub = this.web3Modal.subscribeEvents(async (event) => { - const requestValidations = () => { - this.requestValidations() - .then(() => resolve(true)) - .catch((err) => reject(err)) - .finally(() => unsub()); - }; - - switch (event.data.event) { - case 'MODAL_OPEN': - if (wagmiConfig) { - const account = getAccount(wagmiConfig); - if (account?.isConnected) { - unsub(); - this.web3Modal.close(); - requestValidations(); - break; - } - } - // Ensures that the WC Web3Modal config is applied over pre-existing states (e.g. Solan Connect Web3Modal) - this.createModal(); - break; - case 'CONNECT_SUCCESS': { - requestValidations(); - break; - } - case 'MODAL_CLOSE': - case 'CONNECT_ERROR': { - if (wagmiConfig) { - const account = getAccount(wagmiConfig); - if (account) { - requestValidations(); - break; - } - } - resolve(false); - unsub(); - break; + this.web3Modal.open(); + const unsub = this.web3Modal.subscribeEvents(async (event) => { + switch (event.data.event) { + case 'MODAL_OPEN': + // Ensures that the WC Web3Modal config is applied over pre-existing states (e.g. Solana Connect Web3Modal) + this.createModal(); + break; + case 'CONNECT_SUCCESS': { + const { addresses = [] } = getAccount(wagmiConfig); + for (const address of addresses) { + this.storage.setItem(`SIGNATURE_VALIDATION_${address}`, 'pending'); } + unsub(); + break; } - }); + case 'MODAL_CLOSE': + case 'CONNECT_ERROR': { + unsub(); + break; + } + } }); - return result; + return false; } private async getAccountValidations( @@ -310,26 +297,30 @@ export class WalletConnectConnector extends PredicateConnector { return hasValidate; } - async requestValidations() { - const wagmiConfig = this.getWagmiConfig(); - if (!wagmiConfig) { - throw new Error('Wagmi config not found'); - } + private async requestSignatures( + wagmiConfig: Config, + ): Promise<'validated' | 'pending'> { const account = getAccount(wagmiConfig); - const { addresses } = account; - for (const address of addresses || []) { - await this.requestValidation(address) - .then(() => { - this.handleConnect(account); - }) - .catch((err) => { - this.disconnect(); - throw err; - }); + + const { addresses = [], isConnected } = account; + for (const address of addresses) { + try { + await this.requestSignature(address); + } catch (err) { + this.disconnect(); + throw err; + } } + + if (isConnected) { + await this.handleConnect(account); + return 'validated'; + } + + return 'pending'; } - async requestValidation(address?: string) { + private async requestSignature(address?: string) { return new Promise(async (resolve, reject) => { const hasSignature = await this.accountHasValidation(address); if (hasSignature) return resolve(true); @@ -339,7 +330,7 @@ export class WalletConnectConnector extends PredicateConnector { reject( new Error("User didn't provide signature in less than 1 minute"), ); - }, SINGATURE_VALIDATION_TIMEOUT); + }, SIGNATURE_VALIDATION_TIMEOUT); const { ethProvider } = await this.getProviders(); if (!ethProvider) return; From ede16a7f4f7c5261bc3859b2348cd20a5fdfe492 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lcio=20Franco?= Date: Tue, 7 Jan 2025 23:07:53 +0800 Subject: [PATCH 08/19] chore: remove unused comment --- packages/react/src/ui/Connect/components/Core/DialogFuel.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react/src/ui/Connect/components/Core/DialogFuel.tsx b/packages/react/src/ui/Connect/components/Core/DialogFuel.tsx index 41635ee8..d130826f 100644 --- a/packages/react/src/ui/Connect/components/Core/DialogFuel.tsx +++ b/packages/react/src/ui/Connect/components/Core/DialogFuel.tsx @@ -9,7 +9,6 @@ export function DialogFuel({ open, onOpenChange, }: DialogRadix.DialogProps & { theme: 'dark' | 'light' }) { - // const currentConnector = fuel.currentConnector(); // Fix hydration problem between nextjs render and frontend render // UI was not getting updated and theme colors was set wrongly // see more here https://nextjs.org/docs/messages/react-hydration-error From 90f4c3b3d2f071b995858c6b107eb106820fef12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lcio=20Franco?= Date: Tue, 7 Jan 2025 23:17:16 +0800 Subject: [PATCH 09/19] fix: reset verification status when refusing --- packages/walletconnect-connector/src/WalletConnectConnector.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/walletconnect-connector/src/WalletConnectConnector.ts b/packages/walletconnect-connector/src/WalletConnectConnector.ts index d54f123b..af18d181 100644 --- a/packages/walletconnect-connector/src/WalletConnectConnector.ts +++ b/packages/walletconnect-connector/src/WalletConnectConnector.ts @@ -343,6 +343,7 @@ export class WalletConnectConnector extends PredicateConnector { }) .catch((err) => { clearTimeout(validationTimeout); + this.storage.removeItem(`SIGNATURE_VALIDATION_${address}`); reject(err); }); }); From 82b94badaa2073c7957d518eab27e76d8def53ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lcio=20Franco?= Date: Tue, 7 Jan 2025 23:52:59 +0800 Subject: [PATCH 10/19] feat: update copy when signing message --- .../components/Connector/Connecting.tsx | 58 +++++++++++++++++-- .../src/WalletConnectConnector.ts | 14 ++++- packages/walletconnect-connector/src/types.ts | 12 +++- 3 files changed, 77 insertions(+), 7 deletions(-) diff --git a/packages/react/src/ui/Connect/components/Connector/Connecting.tsx b/packages/react/src/ui/Connect/components/Connector/Connecting.tsx index cf79e308..4f5f53de 100644 --- a/packages/react/src/ui/Connect/components/Connector/Connecting.tsx +++ b/packages/react/src/ui/Connect/components/Connector/Connecting.tsx @@ -1,8 +1,10 @@ import { Routes, useConnectUI } from '../../../../providers/FuelUIProvider'; import { ConnectorIcon } from '../Core/ConnectorIcon'; -import { useEffect } from 'react'; +import type { ConnectorEvent } from 'fuels'; +import { useEffect, useMemo, useState } from 'react'; import { Spinner } from '../../../../icons/Spinner'; +import { useFuel } from '../../../../providers/FuelHooksProvider'; import { ConnectorButton, ConnectorButtonPrimary, @@ -17,7 +19,32 @@ type ConnectorProps = { className?: string; }; +export interface CustomCurrentConnectorEvent extends ConnectorEvent { + metadata?: { + pendingSignature: boolean; + }; +} + +enum ConnectStep { + CONNECT = 'connect', + SIGN = 'sign', +} + +const copy = { + [ConnectStep.CONNECT]: { + description: `Click on the button below to connect to ${location.origin}.`, + cta: 'Connect', + }, + [ConnectStep.SIGN]: { + title: 'Signing in progress', + description: + 'Sign this message to prove you own this wallet and proceed. Canceling will disconnect you.', + cta: 'Sign', + }, +} as const; + export function Connecting({ className }: ConnectorProps) { + const { fuel } = useFuel(); const { error, isConnecting, @@ -27,6 +54,14 @@ export function Connecting({ className }: ConnectorProps) { isConnected, } = useConnectUI(); + const [connectStep, setConnectStep] = useState( + ConnectStep.CONNECT, + ); + + const { description, cta } = useMemo(() => { + return copy[connectStep]; + }, [connectStep]); + // Auto-close connecting useEffect(() => { if (isConnected && route === Routes.CONNECTING && !isConnecting) { @@ -34,6 +69,21 @@ export function Connecting({ className }: ConnectorProps) { } }, [isConnected, route, isConnecting, cancel]); + // Switching to signing ownership mode + useEffect(() => { + const onCurrentConnectorChange = (e: CustomCurrentConnectorEvent) => { + if (e.metadata?.pendingSignature) { + setConnectStep(ConnectStep.SIGN); + } + }; + + fuel.on(fuel.events.currentConnector, onCurrentConnectorChange); + + return () => { + fuel.off(fuel.events.currentConnector, onCurrentConnectorChange); + }; + }, [fuel]); + if (!connector) return null; return ( @@ -55,9 +105,7 @@ export function Connecting({ className }: ConnectorProps) { Requesting connection to
{connector.name}. ) : ( - - Click on the button below to connect to {location.origin}. - + {description} )} {isConnecting ? ( @@ -66,7 +114,7 @@ export function Connecting({ className }: ConnectorProps) { ) : ( retryConnect(connector)}> - Connect + {cta} )} diff --git a/packages/walletconnect-connector/src/WalletConnectConnector.ts b/packages/walletconnect-connector/src/WalletConnectConnector.ts index af18d181..2fc7d33a 100644 --- a/packages/walletconnect-connector/src/WalletConnectConnector.ts +++ b/packages/walletconnect-connector/src/WalletConnectConnector.ts @@ -17,6 +17,7 @@ import { import type { Web3Modal } from '@web3modal/wagmi'; import { CHAIN_IDS, + type ConnectorEvent, type ConnectorMetadata, FuelConnectorEventTypes, Provider as FuelProvider, @@ -46,7 +47,7 @@ import { SIGNATURE_VALIDATION_TIMEOUT, WINDOW, } from './constants'; -import type { WalletConnectConfig } from './types'; +import type { CustomCurrentConnectorEvent, WalletConnectConfig } from './types'; import { subscribeAndEnforceChain } from './utils'; import { createWagmiConfig, createWeb3ModalInstance } from './web3Modal'; @@ -263,6 +264,17 @@ export class WalletConnectConnector extends PredicateConnector { for (const address of addresses) { this.storage.setItem(`SIGNATURE_VALIDATION_${address}`, 'pending'); } + + const currentConnectorEvent: CustomCurrentConnectorEvent = { + type: this.events.currentConnector, + data: this, + metadata: { + pendingSignature: true, + }, + }; + + // Workaround to tell Connecting dialog that now we'll request signature + this.emit(this.events.currentConnector, currentConnectorEvent); unsub(); break; } diff --git a/packages/walletconnect-connector/src/types.ts b/packages/walletconnect-connector/src/types.ts index 2277abfa..d58bc33c 100644 --- a/packages/walletconnect-connector/src/types.ts +++ b/packages/walletconnect-connector/src/types.ts @@ -1,6 +1,10 @@ import type { PredicateConfig } from '@fuel-connectors/common'; import type { Config as WagmiConfig } from '@wagmi/core'; -import type { Provider as FuelProvider, StorageAbstract } from 'fuels'; +import type { + ConnectorEvent, + Provider as FuelProvider, + StorageAbstract, +} from 'fuels'; export type WalletConnectConfig = { fuelProvider?: FuelProvider | Promise; @@ -12,3 +16,9 @@ export type WalletConnectConfig = { // if the dapp already has wagmi from eth connectors, it's better to skip auto reconnection as it can lead to session loss when refreshing the page skipAutoReconnect?: boolean; }; + +export interface CustomCurrentConnectorEvent extends ConnectorEvent { + metadata: { + pendingSignature: boolean; + }; +} From 85c6bc2cd170412f1ce07100e6f6b26a7e28d913 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lcio=20Franco?= Date: Tue, 7 Jan 2025 23:58:22 +0800 Subject: [PATCH 11/19] fix: prevent to reset previously validated accounts --- .../src/WalletConnectConnector.ts | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/packages/walletconnect-connector/src/WalletConnectConnector.ts b/packages/walletconnect-connector/src/WalletConnectConnector.ts index 2fc7d33a..20cc1663 100644 --- a/packages/walletconnect-connector/src/WalletConnectConnector.ts +++ b/packages/walletconnect-connector/src/WalletConnectConnector.ts @@ -261,20 +261,30 @@ export class WalletConnectConnector extends PredicateConnector { break; case 'CONNECT_SUCCESS': { const { addresses = [] } = getAccount(wagmiConfig); + + let hasAccountToSign = false; for (const address of addresses) { + if (await this.accountHasValidation(address)) { + continue; + } + + hasAccountToSign = true; this.storage.setItem(`SIGNATURE_VALIDATION_${address}`, 'pending'); } - const currentConnectorEvent: CustomCurrentConnectorEvent = { - type: this.events.currentConnector, - data: this, - metadata: { - pendingSignature: true, - }, - }; + if (hasAccountToSign) { + const currentConnectorEvent: CustomCurrentConnectorEvent = { + type: this.events.currentConnector, + data: this, + metadata: { + pendingSignature: true, + }, + }; + + // Workaround to tell Connecting dialog that now we'll request signature + this.emit(this.events.currentConnector, currentConnectorEvent); + } - // Workaround to tell Connecting dialog that now we'll request signature - this.emit(this.events.currentConnector, currentConnectorEvent); unsub(); break; } From d860a879e822dbf1d88e0fe0c8a1f8585d809b51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lcio=20Franco?= Date: Wed, 8 Jan 2025 00:00:43 +0800 Subject: [PATCH 12/19] docs: add changeset --- .changeset/honest-meals-sing.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/honest-meals-sing.md diff --git a/.changeset/honest-meals-sing.md b/.changeset/honest-meals-sing.md new file mode 100644 index 00000000..8bcc9898 --- /dev/null +++ b/.changeset/honest-meals-sing.md @@ -0,0 +1,7 @@ +--- +"@fuel-connectors/walletconnect-connector": minor +"@fuels/connectors": minor +"@fuels/react": minor +--- + +Added a pre-signature dialog to the `WalletConnectConnector` to inform users about the signature purpose. From 50f6e3d88d684a639fb1a91eeca5f203348947e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lcio=20Franco?= Date: Wed, 8 Jan 2025 00:05:12 +0800 Subject: [PATCH 13/19] test: add missing e2e sign step --- .../WalletConnectConnector/WalletConnectConnector.test.ts | 1 + .../react/src/ui/Connect/components/Connector/Connecting.tsx | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e-tests/runner/examples/connectors/WalletConnectConnector/WalletConnectConnector.test.ts b/e2e-tests/runner/examples/connectors/WalletConnectConnector/WalletConnectConnector.test.ts index 20ca085a..4bbdd8e7 100644 --- a/e2e-tests/runner/examples/connectors/WalletConnectConnector/WalletConnectConnector.test.ts +++ b/e2e-tests/runner/examples/connectors/WalletConnectConnector/WalletConnectConnector.test.ts @@ -47,6 +47,7 @@ test.describe('WalletConnectConnector', () => { // First-time connection requires a message signature (to prove ownership of the wallet) const connect: ConnectorFunctions['connect'] = async (page) => { await commonConnect(page); + await page.getByText('Sign').click(); await metamask.confirmSignature(); }; diff --git a/packages/react/src/ui/Connect/components/Connector/Connecting.tsx b/packages/react/src/ui/Connect/components/Connector/Connecting.tsx index 4f5f53de..bd283394 100644 --- a/packages/react/src/ui/Connect/components/Connector/Connecting.tsx +++ b/packages/react/src/ui/Connect/components/Connector/Connecting.tsx @@ -36,7 +36,6 @@ const copy = { cta: 'Connect', }, [ConnectStep.SIGN]: { - title: 'Signing in progress', description: 'Sign this message to prove you own this wallet and proceed. Canceling will disconnect you.', cta: 'Sign', From c867c8914e7808bdb5e5418194c928fb5042216a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lcio=20Franco?= Date: Wed, 8 Jan 2025 00:05:18 +0800 Subject: [PATCH 14/19] docs: add pr template --- .github/pull_request_template.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..b1b2814d --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,19 @@ + + +# Summary + + +# Checklist + +- [ ] I've added error handling for all actions/requests, and verified how this error will show on UI. +- [ ] I've reviewed all the copies changed/added in this PR (use AI if needs help) +- [ ] I've included the reference to the issues being closed (Github and/or Linear) +- [ ] I've changed the Docs to reflect my changes (project setup, run commands, etc…) +- [ ] I've put docs links where it may be helpful. From 58eb5150b4db3dcbf6603bf9e1ecd5aed9c0facd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lcio=20Franco?= Date: Wed, 8 Jan 2025 00:21:23 +0800 Subject: [PATCH 15/19] test: add missing selector --- .../WalletConnectConnector/WalletConnectConnector.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e-tests/runner/examples/connectors/WalletConnectConnector/WalletConnectConnector.test.ts b/e2e-tests/runner/examples/connectors/WalletConnectConnector/WalletConnectConnector.test.ts index 4bbdd8e7..e5c1ab52 100644 --- a/e2e-tests/runner/examples/connectors/WalletConnectConnector/WalletConnectConnector.test.ts +++ b/e2e-tests/runner/examples/connectors/WalletConnectConnector/WalletConnectConnector.test.ts @@ -47,7 +47,7 @@ test.describe('WalletConnectConnector', () => { // First-time connection requires a message signature (to prove ownership of the wallet) const connect: ConnectorFunctions['connect'] = async (page) => { await commonConnect(page); - await page.getByText('Sign').click(); + await page.getByText('Sign', { exact: true }).click(); await metamask.confirmSignature(); }; From 25bace8de76d561c49fd0728ce9d5bec42baed15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lcio=20Franco?= Date: Wed, 8 Jan 2025 01:11:41 +0800 Subject: [PATCH 16/19] fix: move location reference to component scope --- .../components/Connector/Connecting.tsx | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/packages/react/src/ui/Connect/components/Connector/Connecting.tsx b/packages/react/src/ui/Connect/components/Connector/Connecting.tsx index bd283394..2435208f 100644 --- a/packages/react/src/ui/Connect/components/Connector/Connecting.tsx +++ b/packages/react/src/ui/Connect/components/Connector/Connecting.tsx @@ -30,18 +30,6 @@ enum ConnectStep { SIGN = 'sign', } -const copy = { - [ConnectStep.CONNECT]: { - description: `Click on the button below to connect to ${location.origin}.`, - cta: 'Connect', - }, - [ConnectStep.SIGN]: { - description: - 'Sign this message to prove you own this wallet and proceed. Canceling will disconnect you.', - cta: 'Sign', - }, -} as const; - export function Connecting({ className }: ConnectorProps) { const { fuel } = useFuel(); const { @@ -58,7 +46,18 @@ export function Connecting({ className }: ConnectorProps) { ); const { description, cta } = useMemo(() => { - return copy[connectStep]; + if (connectStep === ConnectStep.CONNECT) { + return { + description: `Click on the button below to connect to ${location.origin}.`, + cta: 'Connect', + }; + } + + return { + description: + 'Sign this message to prove you own this wallet and proceed. Canceling will disconnect you.', + cta: 'Sign', + }; }, [connectStep]); // Auto-close connecting From db885c69a708d406c13e39b6c632e20f8d5de588 Mon Sep 17 00:00:00 2001 From: Luiz Gomes <8636507+LuizAsFight@users.noreply.github.com> Date: Fri, 10 Jan 2025 17:33:47 -0300 Subject: [PATCH 17/19] Update pull_request_template.md --- .github/pull_request_template.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index b1b2814d..5de5f515 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -17,3 +17,9 @@ Many times this section is not needed as the closed issues themselves explains t - [ ] I've included the reference to the issues being closed (Github and/or Linear) - [ ] I've changed the Docs to reflect my changes (project setup, run commands, etc…) - [ ] I've put docs links where it may be helpful. +- [ ] +- [ ] I've added error handling for all actions/requests, and verified how it will show on UI (or verifying error handling was needed) +- [ ] I've reviewed all the copies changed/added in this PR, using AI if needed (or no copy changes were made) +- [ ] I've included the reference to the Github and/or Linear issues being closed (or no issues to reference) +- [ ] I've changed the Docs to reflect my changes (or no doc updates were needed) +- [ ] I've put docs links where it may be helpful (or no doc links were needed) From 4a7dcee59bc0b108f54451d9f6d69079cb3701f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lcio=20Franco?= Date: Mon, 13 Jan 2025 23:33:55 +0700 Subject: [PATCH 18/19] fix: reset connection flow if user cancel signature --- .../components/Connector/Connecting.tsx | 19 ++++++++++++------- .../src/WalletConnectConnector.ts | 11 +++++++++++ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/packages/react/src/ui/Connect/components/Connector/Connecting.tsx b/packages/react/src/ui/Connect/components/Connector/Connecting.tsx index 2435208f..386307bc 100644 --- a/packages/react/src/ui/Connect/components/Connector/Connecting.tsx +++ b/packages/react/src/ui/Connect/components/Connector/Connecting.tsx @@ -45,10 +45,11 @@ export function Connecting({ className }: ConnectorProps) { ConnectStep.CONNECT, ); - const { description, cta } = useMemo(() => { + const { description, operation, cta } = useMemo(() => { if (connectStep === ConnectStep.CONNECT) { return { description: `Click on the button below to connect to ${location.origin}.`, + operation: 'connection', cta: 'Connect', }; } @@ -56,6 +57,7 @@ export function Connecting({ className }: ConnectorProps) { return { description: 'Sign this message to prove you own this wallet and proceed. Canceling will disconnect you.', + operation: 'signature', cta: 'Sign', }; }, [connectStep]); @@ -70,8 +72,10 @@ export function Connecting({ className }: ConnectorProps) { // Switching to signing ownership mode useEffect(() => { const onCurrentConnectorChange = (e: CustomCurrentConnectorEvent) => { - if (e.metadata?.pendingSignature) { - setConnectStep(ConnectStep.SIGN); + if (e.metadata && 'pendingSignature' in e.metadata) { + setConnectStep( + e.metadata.pendingSignature ? ConnectStep.SIGN : ConnectStep.CONNECT, + ); } }; @@ -96,15 +100,16 @@ export function Connecting({ className }: ConnectorProps) { {connector.name} - {error ? ( - {error.message} - ) : isConnecting ? ( + {isConnecting ? ( - Requesting connection to
{connector.name}. + Requesting {operation} to
{connector.name}.
) : ( {description} )} + {error && ( + {error.message} + )}
{isConnecting ? ( diff --git a/packages/walletconnect-connector/src/WalletConnectConnector.ts b/packages/walletconnect-connector/src/WalletConnectConnector.ts index 20cc1663..c2054fc7 100644 --- a/packages/walletconnect-connector/src/WalletConnectConnector.ts +++ b/packages/walletconnect-connector/src/WalletConnectConnector.ts @@ -366,6 +366,17 @@ export class WalletConnectConnector extends PredicateConnector { .catch((err) => { clearTimeout(validationTimeout); this.storage.removeItem(`SIGNATURE_VALIDATION_${address}`); + + const currentConnectorEvent: CustomCurrentConnectorEvent = { + type: this.events.currentConnector, + data: this, + metadata: { + pendingSignature: false, + }, + }; + + // Workaround to tell Connecting dialog that now we'll request connection again + this.emit(this.events.currentConnector, currentConnectorEvent); reject(err); }); }); From 9f9efda1517c6c26e0a4bbd3a26d70f4dcb3c7a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9lcio=20Franco?= Date: Mon, 13 Jan 2025 23:36:49 +0700 Subject: [PATCH 19/19] fix: prevent an unhandled promise when validating connection --- .../src/WalletConnectConnector.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/walletconnect-connector/src/WalletConnectConnector.ts b/packages/walletconnect-connector/src/WalletConnectConnector.ts index c2054fc7..8755831e 100644 --- a/packages/walletconnect-connector/src/WalletConnectConnector.ts +++ b/packages/walletconnect-connector/src/WalletConnectConnector.ts @@ -335,8 +335,13 @@ export class WalletConnectConnector extends PredicateConnector { } if (isConnected) { - await this.handleConnect(account); - return 'validated'; + try { + await this.handleConnect(account); + return 'validated'; + } catch (err) { + this.disconnect(); + throw err; + } } return 'pending';