From 4b5d1c7ee941170483f0bd58268094ba91309fe5 Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Fri, 21 Feb 2025 10:32:35 +0200 Subject: [PATCH 1/5] chore: scraps auto reconnect in universal-provider and throws immediately when `approval` rejects --- .../sign-client/src/controllers/engine.ts | 18 +++++- .../src/UniversalProvider.ts | 55 ++++++------------- 2 files changed, 33 insertions(+), 40 deletions(-) diff --git a/packages/sign-client/src/controllers/engine.ts b/packages/sign-client/src/controllers/engine.ts index 911c8844f..cbd6e8efb 100644 --- a/packages/sign-client/src/controllers/engine.ts +++ b/packages/sign-client/src/controllers/engine.ts @@ -223,14 +223,27 @@ export class Engine extends IEngine { pairingTopic: topic, ...(sessionProperties && { sessionProperties }), }; + + const proposalId = payloadId(); + const { reject, resolve, done: approval, } = createDelayedPromise(expiry, PROPOSAL_EXPIRY_MESSAGE); + + const proposalExpireHandler = ({ id }: { id: number }) => { + if (id === proposalId) { + this.client.events.off("proposal_expire", proposalExpireHandler); + reject({ message: PROPOSAL_EXPIRY_MESSAGE, code: 0 }); + } + }; + + this.client.events.on("proposal_expire", proposalExpireHandler); this.events.once<"session_connect">( engineEvent("session_connect"), async ({ error, session }) => { + this.client.events.off("proposal_expire", proposalExpireHandler); if (error) reject(error); else if (session) { session.self.publicKey = publicKey; @@ -254,13 +267,14 @@ export class Engine extends IEngine { } }, ); - const id = await this.sendRequest({ + await this.sendRequest({ topic, method: "wc_sessionPropose", params: proposal, throwOnFailedPublish: true, + clientRpcId: proposalId, }); - await this.setProposal(id, { id, ...proposal }); + await this.setProposal(proposalId, { id: proposalId, ...proposal }); return { uri, approval }; }; diff --git a/providers/universal-provider/src/UniversalProvider.ts b/providers/universal-provider/src/UniversalProvider.ts index a6f89ac7e..8d1d7f73c 100644 --- a/providers/universal-provider/src/UniversalProvider.ts +++ b/providers/universal-provider/src/UniversalProvider.ts @@ -53,8 +53,6 @@ export class UniversalProvider implements IUniversalProvider { public logger: Logger; public uri: string | undefined; - private shouldAbortPairingAttempt = false; - private maxPairingAttempts = 10; private disableProviderPing = false; static async init(opts: UniversalProviderOpts) { @@ -187,44 +185,25 @@ export class UniversalProvider implements IUniversalProvider { } public async pair(pairingTopic: string | undefined): Promise { - this.shouldAbortPairingAttempt = false; - let pairingAttempts = 0; - do { - if (this.shouldAbortPairingAttempt) { - throw new Error("Pairing aborted"); - } - - if (pairingAttempts >= this.maxPairingAttempts) { - throw new Error("Max auto pairing attempts reached"); - } + const { uri, approval } = await this.client.connect({ + pairingTopic, + requiredNamespaces: this.namespaces, + optionalNamespaces: this.optionalNamespaces, + sessionProperties: this.sessionProperties, + }); - const { uri, approval } = await this.client.connect({ - pairingTopic, - requiredNamespaces: this.namespaces, - optionalNamespaces: this.optionalNamespaces, - sessionProperties: this.sessionProperties, - }); + if (uri) { + this.uri = uri; + this.events.emit("display_uri", uri); + } - if (uri) { - this.uri = uri; - this.events.emit("display_uri", uri); - } + const session = await approval(); + this.session = session; + // assign namespaces from session if not already defined + const approved = populateNamespacesChains(session.namespaces) as NamespaceConfig; + this.namespaces = mergeRequiredOptionalNamespaces(this.namespaces, approved); + this.persist("namespaces", this.namespaces); - await approval() - .then((session) => { - this.session = session; - // assign namespaces from session if not already defined - const approved = populateNamespacesChains(session.namespaces) as NamespaceConfig; - this.namespaces = mergeRequiredOptionalNamespaces(this.namespaces, approved); - this.persist("namespaces", this.namespaces); - }) - .catch((error) => { - if (error.message !== PROPOSAL_EXPIRY_MESSAGE) { - throw error; - } - pairingAttempts++; - }); - } while (!this.session); this.onConnect(); return this.session; } @@ -265,7 +244,7 @@ export class UniversalProvider implements IUniversalProvider { } public abortPairingAttempt() { - this.shouldAbortPairingAttempt = true; + this.logger.warn("abortPairingAttempt is deprecated."); } // ---------- Private ----------------------------------------------- // From 03300813d895827855dcd1e176cd78c0d53ee671 Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Fri, 21 Feb 2025 10:32:48 +0200 Subject: [PATCH 2/5] feat: tests --- .../universal-provider/test/index.spec.ts | 30 +++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/providers/universal-provider/test/index.spec.ts b/providers/universal-provider/test/index.spec.ts index a83c043bb..e427d1d2f 100644 --- a/providers/universal-provider/test/index.spec.ts +++ b/providers/universal-provider/test/index.spec.ts @@ -1,4 +1,4 @@ -import { expect, describe, it, beforeAll, afterAll } from "vitest"; +import { expect, describe, it, beforeAll, afterAll, vi } from "vitest"; import Web3 from "web3"; import { BigNumber, providers, utils } from "ethers"; import { TestNetwork } from "ethereum-test-network"; @@ -31,7 +31,6 @@ import { import { getChainId, getGlobal, getRpcUrl, setGlobal } from "../src/utils"; import { BUNDLER_URL, RPC_URL } from "../src/constants"; import { formatJsonRpcResult } from "@walletconnect/jsonrpc-utils"; -import { parseChainId } from "@walletconnect/utils"; const getDbName = (_prefix: string) => { return `./test/tmp/${_prefix}.db`; @@ -1261,6 +1260,30 @@ describe("UniversalProvider", function () { expectedChainId, }); }); + it("should reject connect on proposal expiry", async () => { + const dapp = await UniversalProvider.init({ + ...TEST_PROVIDER_OPTS, + name: "dapp", + }); + + await Promise.all([ + new Promise(async (resolve) => { + await dapp.connect({}).catch((error) => { + expect(error.message).to.eql("Proposal expired"); + expect(dapp.client.events.listenerCount("proposal_expire")).to.eql(0); + resolve(); + }); + }), + new Promise(async (resolve) => { + await throttle(2_000); + expect(dapp.client.events.listenerCount("proposal_expire")).to.eql(1); + const proposals = dapp.client.proposal.getAll(); + // force expiry of the proposal so the test doesn't wait for the default expiry time + dapp.client.core.expirer.set(proposals[0].id, 0); + resolve(); + }), + ]); + }); it("should cache `wallet_getCapabilities` request", async () => { const dapp = await UniversalProvider.init({ ...TEST_PROVIDER_OPTS, @@ -1506,3 +1529,6 @@ const validateProvider = async (params: ValidateProviderParams) => { } } }; +function toMiliseconds(arg0: any): number { + throw new Error("Function not implemented."); +} From 03d3687100b3c3681aa585c89c09fd71aeb1e046 Mon Sep 17 00:00:00 2001 From: Gancho Radkov Date: Fri, 21 Feb 2025 10:45:26 +0200 Subject: [PATCH 3/5] fix: rm unused var --- providers/universal-provider/src/UniversalProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/universal-provider/src/UniversalProvider.ts b/providers/universal-provider/src/UniversalProvider.ts index 8d1d7f73c..6dc4088be 100644 --- a/providers/universal-provider/src/UniversalProvider.ts +++ b/providers/universal-provider/src/UniversalProvider.ts @@ -1,4 +1,4 @@ -import SignClient, { PROPOSAL_EXPIRY_MESSAGE } from "@walletconnect/sign-client"; +import SignClient from "@walletconnect/sign-client"; import { SessionTypes } from "@walletconnect/types"; import { JsonRpcResult } from "@walletconnect/jsonrpc-types"; import { getSdkError, isValidArray, parseNamespaceKey } from "@walletconnect/utils"; From 9adddf0ddc2cca27b96bf4d4d1395d2ae50a706d Mon Sep 17 00:00:00 2001 From: Gancho Radkov <43912948+ganchoradkov@users.noreply.github.com> Date: Fri, 21 Feb 2025 10:47:33 +0200 Subject: [PATCH 4/5] chore: rm autocompleted fx --- providers/universal-provider/test/index.spec.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/providers/universal-provider/test/index.spec.ts b/providers/universal-provider/test/index.spec.ts index e427d1d2f..fe3470a3a 100644 --- a/providers/universal-provider/test/index.spec.ts +++ b/providers/universal-provider/test/index.spec.ts @@ -1529,6 +1529,3 @@ const validateProvider = async (params: ValidateProviderParams) => { } } }; -function toMiliseconds(arg0: any): number { - throw new Error("Function not implemented."); -} From 249ffb6dadd811e5c8d438b49edaa8f703991de6 Mon Sep 17 00:00:00 2001 From: Gancho Radkov <43912948+ganchoradkov@users.noreply.github.com> Date: Fri, 21 Feb 2025 14:50:03 +0200 Subject: [PATCH 5/5] chore: updates `abortPairingAttempt` warn message Co-authored-by: Ben Kremer --- providers/universal-provider/src/UniversalProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/providers/universal-provider/src/UniversalProvider.ts b/providers/universal-provider/src/UniversalProvider.ts index 6dc4088be..c0c3b2ba1 100644 --- a/providers/universal-provider/src/UniversalProvider.ts +++ b/providers/universal-provider/src/UniversalProvider.ts @@ -244,7 +244,7 @@ export class UniversalProvider implements IUniversalProvider { } public abortPairingAttempt() { - this.logger.warn("abortPairingAttempt is deprecated."); + this.logger.warn("abortPairingAttempt is deprecated. This is now a no-op."); } // ---------- Private ----------------------------------------------- //