From d6f3010479d958bdd58cedb5ca0c6d2e7440b12e Mon Sep 17 00:00:00 2001 From: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> Date: Mon, 27 Jan 2025 09:25:27 -0800 Subject: [PATCH 01/56] Create FCL Ethereum Provider Boilerplate --- package-lock.json | 57 +++++++++++++------ packages/fcl-ethereum-provider/.babelrc | 3 + .../fcl-ethereum-provider/.browserslistrc | 2 + packages/fcl-ethereum-provider/.eslintrc.json | 20 +++++++ packages/fcl-ethereum-provider/.npmignore | 1 + packages/fcl-ethereum-provider/CHANGELOG.md | 1 + packages/fcl-ethereum-provider/README.md | 15 +++++ packages/fcl-ethereum-provider/package.json | 41 +++++++++++++ .../src/create-provider.ts | 36 ++++++++++++ .../src/events/event-manager.test.ts | 3 + .../src/events/event-manager.ts | 32 +++++++++++ packages/fcl-ethereum-provider/src/index.ts | 1 + .../src/provider.test.ts | 5 ++ .../fcl-ethereum-provider/src/provider.ts | 48 ++++++++++++++++ .../src/rpc/handlers/eth-accounts.ts | 3 + .../src/rpc/rpc-processor.test.ts | 5 ++ .../src/rpc/rpc-processor.ts | 19 +++++++ .../fcl-ethereum-provider/src/rpc/types.ts | 1 + .../src/types/provider.ts | 36 ++++++++++++ packages/fcl-ethereum-provider/tsconfig.json | 9 +++ 20 files changed, 321 insertions(+), 17 deletions(-) create mode 100644 packages/fcl-ethereum-provider/.babelrc create mode 100644 packages/fcl-ethereum-provider/.browserslistrc create mode 100644 packages/fcl-ethereum-provider/.eslintrc.json create mode 100644 packages/fcl-ethereum-provider/.npmignore create mode 100644 packages/fcl-ethereum-provider/CHANGELOG.md create mode 100644 packages/fcl-ethereum-provider/README.md create mode 100644 packages/fcl-ethereum-provider/package.json create mode 100644 packages/fcl-ethereum-provider/src/create-provider.ts create mode 100644 packages/fcl-ethereum-provider/src/events/event-manager.test.ts create mode 100644 packages/fcl-ethereum-provider/src/events/event-manager.ts create mode 100644 packages/fcl-ethereum-provider/src/index.ts create mode 100644 packages/fcl-ethereum-provider/src/provider.test.ts create mode 100644 packages/fcl-ethereum-provider/src/provider.ts create mode 100644 packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.ts create mode 100644 packages/fcl-ethereum-provider/src/rpc/rpc-processor.test.ts create mode 100644 packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts create mode 100644 packages/fcl-ethereum-provider/src/rpc/types.ts create mode 100644 packages/fcl-ethereum-provider/src/types/provider.ts create mode 100644 packages/fcl-ethereum-provider/tsconfig.json diff --git a/package-lock.json b/package-lock.json index 2005eb331..6ebc973f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6712,6 +6712,10 @@ "resolved": "packages/fcl-core", "link": true }, + "node_modules/@onflow/fcl-ethereum-provider": { + "resolved": "packages/fcl-ethereum-provider", + "link": true + }, "node_modules/@onflow/fcl-react-native": { "resolved": "packages/fcl-react-native", "link": true @@ -29050,16 +29054,16 @@ }, "packages/fcl": { "name": "@onflow/fcl", - "version": "1.13.2", + "version": "1.13.4", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.25.7", "@onflow/config": "1.5.1", - "@onflow/fcl-core": "1.13.2", - "@onflow/fcl-wc": "5.5.2", + "@onflow/fcl-core": "1.13.4", + "@onflow/fcl-wc": "5.5.4", "@onflow/interaction": "0.0.11", "@onflow/rlp": "1.2.3", - "@onflow/sdk": "1.5.5", + "@onflow/sdk": "1.5.6", "@onflow/types": "1.4.1", "@onflow/util-actor": "1.3.4", "@onflow/util-address": "1.2.3", @@ -29124,7 +29128,7 @@ }, "packages/fcl-core": { "name": "@onflow/fcl-core", - "version": "1.13.2", + "version": "1.13.4", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.25.7", @@ -29132,8 +29136,8 @@ "@onflow/config": "1.5.1", "@onflow/interaction": "0.0.11", "@onflow/rlp": "1.2.3", - "@onflow/sdk": "1.5.5", - "@onflow/transport-http": "1.10.4", + "@onflow/sdk": "1.5.6", + "@onflow/transport-http": "1.10.5", "@onflow/types": "1.4.1", "@onflow/util-actor": "1.3.4", "@onflow/util-address": "1.2.3", @@ -29178,17 +29182,36 @@ "node": ">=4.2.0" } }, + "packages/fcl-ethereum-provider": { + "name": "@onflow/fcl-ethereum-provider", + "version": "0.0.0", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.25.7", + "@onflow/fcl": "1.13.4" + }, + "devDependencies": { + "@babel/preset-typescript": "^7.25.7", + "@onflow/fcl-bundle": "1.6.0", + "@types/jest": "^29.5.13", + "@typescript-eslint/eslint-plugin": "^6.21.0", + "@typescript-eslint/parser": "^6.21.0", + "eslint": "^8.57.1", + "eslint-plugin-jsdoc": "^46.10.1", + "jest": "^29.7.0" + } + }, "packages/fcl-react-native": { "name": "@onflow/fcl-react-native", - "version": "1.9.9", + "version": "1.9.11", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.25.7", "@onflow/config": "1.5.1", - "@onflow/fcl-core": "1.13.2", + "@onflow/fcl-core": "1.13.4", "@onflow/interaction": "0.0.11", "@onflow/rlp": "1.2.3", - "@onflow/sdk": "1.5.5", + "@onflow/sdk": "1.5.6", "@onflow/types": "1.4.1", "@onflow/util-actor": "1.3.4", "@onflow/util-address": "1.2.3", @@ -29240,7 +29263,7 @@ }, "packages/fcl-wc": { "name": "@onflow/fcl-wc", - "version": "5.5.2", + "version": "5.5.4", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.25.7", @@ -29268,7 +29291,7 @@ "jest-preset-preact": "^4.1.1" }, "peerDependencies": { - "@onflow/fcl-core": "1.13.2" + "@onflow/fcl-core": "1.13.4" } }, "packages/fcl/node_modules/cross-fetch": { @@ -29359,13 +29382,13 @@ }, "packages/sdk": { "name": "@onflow/sdk", - "version": "1.5.5", + "version": "1.5.6", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.25.7", "@onflow/config": "1.5.1", "@onflow/rlp": "1.2.3", - "@onflow/transport-http": "1.10.4", + "@onflow/transport-http": "1.10.5", "@onflow/typedefs": "1.4.0", "@onflow/util-actor": "1.3.4", "@onflow/util-address": "1.2.3", @@ -29414,13 +29437,13 @@ }, "devDependencies": { "@onflow/fcl-bundle": "1.6.0", - "@onflow/sdk": "1.5.5", + "@onflow/sdk": "1.5.6", "jest": "^29.7.0" } }, "packages/transport-http": { "name": "@onflow/transport-http", - "version": "1.10.4", + "version": "1.10.5", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.25.7", @@ -29437,7 +29460,7 @@ "devDependencies": { "@onflow/fcl-bundle": "1.6.0", "@onflow/rlp": "1.2.3", - "@onflow/sdk": "1.5.5", + "@onflow/sdk": "1.5.6", "@onflow/types": "1.4.1", "jest": "^29.7.0" } diff --git a/packages/fcl-ethereum-provider/.babelrc b/packages/fcl-ethereum-provider/.babelrc new file mode 100644 index 000000000..d766c90b2 --- /dev/null +++ b/packages/fcl-ethereum-provider/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@babel/preset-env"], "@babel/preset-typescript"] +} diff --git a/packages/fcl-ethereum-provider/.browserslistrc b/packages/fcl-ethereum-provider/.browserslistrc new file mode 100644 index 000000000..6114ba78d --- /dev/null +++ b/packages/fcl-ethereum-provider/.browserslistrc @@ -0,0 +1,2 @@ +defaults and supports es6-module +maintained node versions diff --git a/packages/fcl-ethereum-provider/.eslintrc.json b/packages/fcl-ethereum-provider/.eslintrc.json new file mode 100644 index 000000000..e80c057f5 --- /dev/null +++ b/packages/fcl-ethereum-provider/.eslintrc.json @@ -0,0 +1,20 @@ +{ + "env": { + "browser": true, + "es2021": true, + "jest": true, + "node": true + }, + "extends": [ + "plugin:jsdoc/recommended-typescript", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" + ], + "plugins": ["jsdoc", "@typescript-eslint"], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "ignorePatterns": ["**/dist/"] +} diff --git a/packages/fcl-ethereum-provider/.npmignore b/packages/fcl-ethereum-provider/.npmignore new file mode 100644 index 000000000..8eba6c8dd --- /dev/null +++ b/packages/fcl-ethereum-provider/.npmignore @@ -0,0 +1 @@ +src/ diff --git a/packages/fcl-ethereum-provider/CHANGELOG.md b/packages/fcl-ethereum-provider/CHANGELOG.md new file mode 100644 index 000000000..fdb6897bf --- /dev/null +++ b/packages/fcl-ethereum-provider/CHANGELOG.md @@ -0,0 +1 @@ +# @onflow/fcl-ethereum-provider diff --git a/packages/fcl-ethereum-provider/README.md b/packages/fcl-ethereum-provider/README.md new file mode 100644 index 000000000..28b95b8f6 --- /dev/null +++ b/packages/fcl-ethereum-provider/README.md @@ -0,0 +1,15 @@ +# @onflow/fcl-ethereum-provider + +Lightweight utility for transport-agnostic, bidirectional RPC communication. + +# Status + +- **Last Updated:** Aug 20th, 2025 +- **Stable:** No +- **Risk of Breaking Change:** Yes + +# Install + +```bash +npm install --save @onflow/fcl-ethereum-provider +``` \ No newline at end of file diff --git a/packages/fcl-ethereum-provider/package.json b/packages/fcl-ethereum-provider/package.json new file mode 100644 index 000000000..693eba13d --- /dev/null +++ b/packages/fcl-ethereum-provider/package.json @@ -0,0 +1,41 @@ +{ + "name": "@onflow/fcl-ethereum-provider", + "version": "0.0.0", + "description": "Ethereum provider for FCL-compatible wallets", + "license": "Apache-2.0", + "author": "Dapper Labs ", + "homepage": "https://onflow.org", + "repository": { + "type": "git", + "url": "git+ssh://git@github.com/onflow/flow-js-sdk.git" + }, + "bugs": { + "url": "https://github.com/onflow/flow-js-sdk/issues" + }, + "devDependencies": { + "@babel/preset-typescript": "^7.25.7", + "@onflow/fcl-bundle": "1.6.0", + "@types/jest": "^29.5.13", + "@typescript-eslint/eslint-plugin": "^6.21.0", + "@typescript-eslint/parser": "^6.21.0", + "eslint": "^8.57.1", + "eslint-plugin-jsdoc": "^46.10.1", + "jest": "^29.7.0" + }, + "source": "src/util-uid.ts", + "main": "dist/util-uid.js", + "module": "dist/util-uid.module.js", + "unpkg": "dist/util-uid.umd.js", + "types": "types/util-uid.d.ts", + "scripts": { + "prepublishOnly": "npm test && npm run build", + "test": "jest", + "build": "fcl-bundle", + "test:watch": "jest --watch", + "start": "fcl-bundle --watch" + }, + "dependencies": { + "@babel/runtime": "^7.25.7", + "@onflow/fcl": "1.13.4" + } +} diff --git a/packages/fcl-ethereum-provider/src/create-provider.ts b/packages/fcl-ethereum-provider/src/create-provider.ts new file mode 100644 index 000000000..821b57871 --- /dev/null +++ b/packages/fcl-ethereum-provider/src/create-provider.ts @@ -0,0 +1,36 @@ +import * as fcl from "@onflow/fcl" +import {Eip1193Provider} from "./types/provider" +import {FclEthereumProvider} from "./provider" +import {RpcProcessor} from "./rpc/rpc-processor" +import {Service} from "@onflow/typedefs" +import {EventManager} from "./events/event-manager" + +/** + * Create a new FCL Ethereum provider + * @param config - Configuration object + * @param config.user - The current user + * @param config.service - The service + * @param config.gateway - The gateway + * @returns The provider + * @public + * @example + * ```javascript + * import {createProvider} from "@onflow/fcl-ethereum-provider" + * + * const provider = createProvider({ + * user: fcl.currentUser, + * service: fcl.service, + * gateway: "http://localhost:8080", + * }) + * ``` + */ +export function createProvider(config: { + user: typeof fcl.currentUser + service?: Service + gateway?: string +}): Eip1193Provider { + const rpcService = new RpcProcessor() + const eventService = new EventManager() + const provider = new FclEthereumProvider(rpcService, eventService) + return provider +} diff --git a/packages/fcl-ethereum-provider/src/events/event-manager.test.ts b/packages/fcl-ethereum-provider/src/events/event-manager.test.ts new file mode 100644 index 000000000..c52bf6fdb --- /dev/null +++ b/packages/fcl-ethereum-provider/src/events/event-manager.test.ts @@ -0,0 +1,3 @@ +test("notifications service", () => { + expect(true).toBe(true) +}) diff --git a/packages/fcl-ethereum-provider/src/events/event-manager.ts b/packages/fcl-ethereum-provider/src/events/event-manager.ts new file mode 100644 index 000000000..14c1120ca --- /dev/null +++ b/packages/fcl-ethereum-provider/src/events/event-manager.ts @@ -0,0 +1,32 @@ +import {EventCallback, ProviderEvents} from "../types/provider" +import EventEmitter from "events" + +export class EventManager { + private eventEmitter = new EventEmitter() + + constructor() {} + + // Listen to events + on( + event: E, + listener: EventCallback + ): void { + this.eventEmitter.on(event, listener) + } + + // Remove event listeners + off( + event: E, + listener: EventCallback + ): void { + this.eventEmitter.off(event, listener) + } + + // Emit events (to be called internally) + protected emit( + event: E, + data: ProviderEvents[E] + ) { + this.eventEmitter.emit(event, data) + } +} diff --git a/packages/fcl-ethereum-provider/src/index.ts b/packages/fcl-ethereum-provider/src/index.ts new file mode 100644 index 000000000..746b52b0a --- /dev/null +++ b/packages/fcl-ethereum-provider/src/index.ts @@ -0,0 +1 @@ +export {createProvider} from "./create-provider" diff --git a/packages/fcl-ethereum-provider/src/provider.test.ts b/packages/fcl-ethereum-provider/src/provider.test.ts new file mode 100644 index 000000000..b64601995 --- /dev/null +++ b/packages/fcl-ethereum-provider/src/provider.test.ts @@ -0,0 +1,5 @@ +describe("provider", () => { + test("should be tested", () => { + expect(true).toBe(true) + }) +}) diff --git a/packages/fcl-ethereum-provider/src/provider.ts b/packages/fcl-ethereum-provider/src/provider.ts new file mode 100644 index 000000000..c4f08cdb3 --- /dev/null +++ b/packages/fcl-ethereum-provider/src/provider.ts @@ -0,0 +1,48 @@ +import { + Eip1193Provider, + EventCallback, + ProviderEvents, + ProviderRequest, + ProviderResponse, +} from "./types/provider" +import {RpcProcessor} from "./rpc/rpc-processor" +import {EventManager} from "./events/event-manager" + +export class FclEthereumProvider implements Eip1193Provider { + constructor( + private rpcProcessor: RpcProcessor, + private eventService: EventManager + ) {} + + // Handle requests + async request({ + method, + params, + }: ProviderRequest): Promise> { + try { + if (!method) { + throw new Error("Method is required") + } + const result = await this.rpcProcessor.handleRequest({method, params}) + return result + } catch (error) { + throw new Error(`Request failed: ${(error as Error).message}`) + } + } + + // Listen to events + on( + event: E, + listener: EventCallback + ): void { + this.eventService.on(event, listener) + } + + // Remove event listeners + removeListener( + event: E, + listener: EventCallback + ): void { + this.eventService.off(event, listener) + } +} diff --git a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.ts b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.ts new file mode 100644 index 000000000..168081895 --- /dev/null +++ b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.ts @@ -0,0 +1,3 @@ +export function eth_accounts() { + throw new Error("Not implemented") +} diff --git a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.test.ts b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.test.ts new file mode 100644 index 000000000..d97f91dde --- /dev/null +++ b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.test.ts @@ -0,0 +1,5 @@ +describe("rpc", () => { + test("should be tested", () => { + expect(true).toBe(true) + }) +}) diff --git a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts new file mode 100644 index 000000000..48e8204af --- /dev/null +++ b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts @@ -0,0 +1,19 @@ +import {ProviderRequest} from "../types/provider" +import {eth_accounts} from "./handlers/eth-accounts" + +const handlers = { + eth_accounts, +} + +export class RpcProcessor { + constructor() {} + + async handleRequest({method, params}: ProviderRequest): Promise { + switch (method) { + case "eth_requestAccounts": + throw new Error("Not implemented") + default: + throw new Error(`Method ${method} not supported`) + } + } +} diff --git a/packages/fcl-ethereum-provider/src/rpc/types.ts b/packages/fcl-ethereum-provider/src/rpc/types.ts new file mode 100644 index 000000000..a201cf91f --- /dev/null +++ b/packages/fcl-ethereum-provider/src/rpc/types.ts @@ -0,0 +1 @@ +export type RpcHandler = (params: any) => Promise diff --git a/packages/fcl-ethereum-provider/src/types/provider.ts b/packages/fcl-ethereum-provider/src/types/provider.ts new file mode 100644 index 000000000..7ff6fc0e9 --- /dev/null +++ b/packages/fcl-ethereum-provider/src/types/provider.ts @@ -0,0 +1,36 @@ +// Types for RPC request and events +export type ProviderRequest = { + method: string + params?: unknown[] | Record +} + +export type ProviderResponse = T + +// Event types for the provider +export type ProviderEvents = { + connect: {chainId: string} + disconnect: {reason: string} + chainChanged: string + accountsChanged: string[] +} + +// Event callback +export type EventCallback = (event: T) => void + +// Base EIP-1193 Provider Interface +export interface Eip1193Provider { + request(args: ProviderRequest): Promise> + on( + event: E, + listener: EventCallback + ): void + removeListener( + event: E, + listener: EventCallback + ): void +} + +export enum FLOW_CHAIN_ID { + MAINNET = 747, + TESTNET = 646, +} diff --git a/packages/fcl-ethereum-provider/tsconfig.json b/packages/fcl-ethereum-provider/tsconfig.json new file mode 100644 index 000000000..617d9a8ae --- /dev/null +++ b/packages/fcl-ethereum-provider/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig", + // Change this to match your project + "include": ["src/**/*"], + "compilerOptions": { + "declarationDir": "types", + "rootDir": "src" + } +} From 05c441d874c5cda0182973c30158241b0eaf8a11 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> Date: Mon, 27 Jan 2025 10:29:47 -0800 Subject: [PATCH 02/56] Fix CI (#2077) --- .github/scripts/prevent-major-bumps.js | 12 +++++++++--- packages/fcl-ethereum-provider/package.json | 10 +++++----- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/.github/scripts/prevent-major-bumps.js b/.github/scripts/prevent-major-bumps.js index 0836ddad3..29894053a 100644 --- a/.github/scripts/prevent-major-bumps.js +++ b/.github/scripts/prevent-major-bumps.js @@ -26,9 +26,15 @@ for (const packageJson of packageJsons) { const newVersion = newPackageJson.version // Get the version from the main branch (or latest release) - const prevPackageJson = JSON.parse( - execSync(`git show origin/master:${packageJson}`).toString() - ) + let prevPackageJson + try { + prevPackageJson = JSON.parse( + execSync(`git show origin/master:${packageJson}`).toString() + ) + } catch (error) { + console.info("Skipping new package", newPackageName) + continue + } const prevPackageName = prevPackageJson.name const prevVersion = prevPackageJson.version diff --git a/packages/fcl-ethereum-provider/package.json b/packages/fcl-ethereum-provider/package.json index 693eba13d..e652f9930 100644 --- a/packages/fcl-ethereum-provider/package.json +++ b/packages/fcl-ethereum-provider/package.json @@ -22,11 +22,11 @@ "eslint-plugin-jsdoc": "^46.10.1", "jest": "^29.7.0" }, - "source": "src/util-uid.ts", - "main": "dist/util-uid.js", - "module": "dist/util-uid.module.js", - "unpkg": "dist/util-uid.umd.js", - "types": "types/util-uid.d.ts", + "source": "src/index.ts", + "main": "dist/index.js", + "module": "dist/index.module.js", + "unpkg": "dist/index.umd.js", + "types": "types/index.d.ts", "scripts": { "prepublishOnly": "npm test && npm run build", "test": "jest", From 4af4ae4e8e5f395425ef1fa714cae68ab48b756a Mon Sep 17 00:00:00 2001 From: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> Date: Mon, 27 Jan 2025 10:51:26 -0800 Subject: [PATCH 03/56] Add FCL Etheruem Provider AccountManager stub (#2078) --- .../src/accounts/account-manager.ts | 14 ++++++++++++++ .../fcl-ethereum-provider/src/create-provider.ts | 10 ++++++---- ...nt-manager.test.ts => event-dispatcher.test.ts} | 0 .../{event-manager.ts => event-dispatcher.ts} | 2 +- packages/fcl-ethereum-provider/src/provider.ts | 8 ++++---- 5 files changed, 25 insertions(+), 9 deletions(-) create mode 100644 packages/fcl-ethereum-provider/src/accounts/account-manager.ts rename packages/fcl-ethereum-provider/src/events/{event-manager.test.ts => event-dispatcher.test.ts} (100%) rename packages/fcl-ethereum-provider/src/events/{event-manager.ts => event-dispatcher.ts} (95%) diff --git a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts new file mode 100644 index 000000000..c275a05c0 --- /dev/null +++ b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts @@ -0,0 +1,14 @@ +import * as fcl from "@onflow/fcl" + +export class AccountManager { + constructor(private user: typeof fcl.currentUser) {} + + getAccounts(): string[] { + // ... get coa address + return [] + } + + subscribe(callback: (accounts: string[]) => void) { + // ... + } +} diff --git a/packages/fcl-ethereum-provider/src/create-provider.ts b/packages/fcl-ethereum-provider/src/create-provider.ts index 821b57871..39ef0c610 100644 --- a/packages/fcl-ethereum-provider/src/create-provider.ts +++ b/packages/fcl-ethereum-provider/src/create-provider.ts @@ -3,7 +3,8 @@ import {Eip1193Provider} from "./types/provider" import {FclEthereumProvider} from "./provider" import {RpcProcessor} from "./rpc/rpc-processor" import {Service} from "@onflow/typedefs" -import {EventManager} from "./events/event-manager" +import {EventDispatcher} from "./events/event-dispatcher" +import {AccountManager} from "./accounts/account-manager" /** * Create a new FCL Ethereum provider @@ -29,8 +30,9 @@ export function createProvider(config: { service?: Service gateway?: string }): Eip1193Provider { - const rpcService = new RpcProcessor() - const eventService = new EventManager() - const provider = new FclEthereumProvider(rpcService, eventService) + const accountManager = new AccountManager(config.user) + const rpcProcessor = new RpcProcessor() + const eventProcessor = new EventDispatcher() + const provider = new FclEthereumProvider(rpcProcessor, eventProcessor) return provider } diff --git a/packages/fcl-ethereum-provider/src/events/event-manager.test.ts b/packages/fcl-ethereum-provider/src/events/event-dispatcher.test.ts similarity index 100% rename from packages/fcl-ethereum-provider/src/events/event-manager.test.ts rename to packages/fcl-ethereum-provider/src/events/event-dispatcher.test.ts diff --git a/packages/fcl-ethereum-provider/src/events/event-manager.ts b/packages/fcl-ethereum-provider/src/events/event-dispatcher.ts similarity index 95% rename from packages/fcl-ethereum-provider/src/events/event-manager.ts rename to packages/fcl-ethereum-provider/src/events/event-dispatcher.ts index 14c1120ca..0d119c732 100644 --- a/packages/fcl-ethereum-provider/src/events/event-manager.ts +++ b/packages/fcl-ethereum-provider/src/events/event-dispatcher.ts @@ -1,7 +1,7 @@ import {EventCallback, ProviderEvents} from "../types/provider" import EventEmitter from "events" -export class EventManager { +export class EventDispatcher { private eventEmitter = new EventEmitter() constructor() {} diff --git a/packages/fcl-ethereum-provider/src/provider.ts b/packages/fcl-ethereum-provider/src/provider.ts index c4f08cdb3..01ab36dc7 100644 --- a/packages/fcl-ethereum-provider/src/provider.ts +++ b/packages/fcl-ethereum-provider/src/provider.ts @@ -6,12 +6,12 @@ import { ProviderResponse, } from "./types/provider" import {RpcProcessor} from "./rpc/rpc-processor" -import {EventManager} from "./events/event-manager" +import {EventDispatcher} from "./events/event-dispatcher" export class FclEthereumProvider implements Eip1193Provider { constructor( private rpcProcessor: RpcProcessor, - private eventService: EventManager + private eventDispatcher: EventDispatcher ) {} // Handle requests @@ -35,7 +35,7 @@ export class FclEthereumProvider implements Eip1193Provider { event: E, listener: EventCallback ): void { - this.eventService.on(event, listener) + this.eventDispatcher.on(event, listener) } // Remove event listeners @@ -43,6 +43,6 @@ export class FclEthereumProvider implements Eip1193Provider { event: E, listener: EventCallback ): void { - this.eventService.off(event, listener) + this.eventDispatcher.off(event, listener) } } From 1452dc074373177fe73c71c298758ed4ece867c8 Mon Sep 17 00:00:00 2001 From: Chase Fleming Date: Mon, 27 Jan 2025 15:04:47 -0800 Subject: [PATCH 04/56] Setup eth accounts handler (#2079) * Setup eth accounts handler * Run prettier --------- Co-authored-by: Chase Fleming <1666730+chasefleming@users.noreply.github.com> --- .../src/create-provider.ts | 2 +- .../src/rpc/handlers/eth-accounts.test.ts | 31 +++++++++++++++++++ .../src/rpc/handlers/eth-accounts.ts | 8 ++++- .../src/rpc/rpc-processor.ts | 11 +++---- 4 files changed, 44 insertions(+), 8 deletions(-) create mode 100644 packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.test.ts diff --git a/packages/fcl-ethereum-provider/src/create-provider.ts b/packages/fcl-ethereum-provider/src/create-provider.ts index 39ef0c610..ec0dc8bc9 100644 --- a/packages/fcl-ethereum-provider/src/create-provider.ts +++ b/packages/fcl-ethereum-provider/src/create-provider.ts @@ -31,7 +31,7 @@ export function createProvider(config: { gateway?: string }): Eip1193Provider { const accountManager = new AccountManager(config.user) - const rpcProcessor = new RpcProcessor() + const rpcProcessor = new RpcProcessor(accountManager) const eventProcessor = new EventDispatcher() const provider = new FclEthereumProvider(rpcProcessor, eventProcessor) return provider diff --git a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.test.ts b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.test.ts new file mode 100644 index 000000000..2d8135b8e --- /dev/null +++ b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.test.ts @@ -0,0 +1,31 @@ +import {ethAccounts} from "./eth-accounts" +import {AccountManager} from "../../accounts/account-manager" + +describe("ethAccounts handler", () => { + let accountManagerMock: jest.Mocked + + beforeEach(() => { + accountManagerMock = { + getAccounts: jest.fn(), + subscribe: jest.fn(), + } as unknown as jest.Mocked + }) + + it("should return accounts from the AccountManager", () => { + accountManagerMock.getAccounts.mockReturnValue(["0x1234...", "0x5678..."]) + + const accounts = ethAccounts(accountManagerMock) + + expect(accounts).toEqual(["0x1234...", "0x5678..."]) + expect(accountManagerMock.getAccounts).toHaveBeenCalled() + }) + + it("should return an empty array if no accounts are available", () => { + accountManagerMock.getAccounts.mockReturnValue([]) + + const accounts = ethAccounts(accountManagerMock) + + expect(accounts).toEqual([]) + expect(accountManagerMock.getAccounts).toHaveBeenCalled() + }) +}) diff --git a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.ts b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.ts index 168081895..4848ce600 100644 --- a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.ts +++ b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.ts @@ -1,3 +1,9 @@ -export function eth_accounts() { +import {AccountManager} from "../../accounts/account-manager" + +export function ethAccounts(accountManager: AccountManager): string[] { + return accountManager.getAccounts() +} + +export function ethRequestAccounts() { throw new Error("Not implemented") } diff --git a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts index 48e8204af..eda4bbc6b 100644 --- a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts +++ b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts @@ -1,15 +1,14 @@ import {ProviderRequest} from "../types/provider" -import {eth_accounts} from "./handlers/eth-accounts" - -const handlers = { - eth_accounts, -} +import {ethAccounts} from "./handlers/eth-accounts" +import {AccountManager} from "../accounts/account-manager" export class RpcProcessor { - constructor() {} + constructor(private accountManager: AccountManager) {} async handleRequest({method, params}: ProviderRequest): Promise { switch (method) { + case "eth_accounts": + return ethAccounts(this.accountManager) case "eth_requestAccounts": throw new Error("Not implemented") default: From 64487cad8d63255fc3bd01c7e27c28633fb07f55 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> Date: Mon, 27 Jan 2025 16:27:04 -0800 Subject: [PATCH 05/56] Add fallback gateway (#2083) --- package-lock.json | 19 +- packages/fcl-ethereum-provider/package.json | 4 +- .../fcl-ethereum-provider/src/constants.ts | 15 ++ .../src/create-provider.ts | 18 +- .../src/gateway/gateway.test.ts | 201 ++++++++++++++++++ .../src/gateway/gateway.ts | 35 +++ .../src/rpc/rpc-processor.test.ts | 31 ++- .../src/rpc/rpc-processor.ts | 9 +- .../src/types/provider.ts | 5 - 9 files changed, 322 insertions(+), 15 deletions(-) create mode 100644 packages/fcl-ethereum-provider/src/constants.ts create mode 100644 packages/fcl-ethereum-provider/src/gateway/gateway.test.ts create mode 100644 packages/fcl-ethereum-provider/src/gateway/gateway.ts diff --git a/package-lock.json b/package-lock.json index 6ebc973f5..1366e79d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9780,8 +9780,22 @@ "events": "^3.3.0" } }, + "node_modules/@walletconnect/jsonrpc-http-connection": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-http-connection/-/jsonrpc-http-connection-1.0.8.tgz", + "integrity": "sha512-+B7cRuaxijLeFDJUq5hAzNyef3e3tBDIxyaCNmFtjwnod5AGis3RToNqzFU33vpVcxFhofkpE7Cx+5MYejbMGw==", + "license": "MIT", + "dependencies": { + "@walletconnect/jsonrpc-utils": "^1.0.6", + "@walletconnect/safe-json": "^1.0.1", + "cross-fetch": "^3.1.4", + "events": "^3.3.0" + } + }, "node_modules/@walletconnect/jsonrpc-provider": { "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-provider/-/jsonrpc-provider-1.0.14.tgz", + "integrity": "sha512-rtsNY1XqHvWj0EtITNeuf8PHMvlCLiS3EjQL+WOkxEOA4KPxsohFnBDeyPYiNm4ZvkQdLnece36opYidmtbmow==", "license": "MIT", "dependencies": { "@walletconnect/jsonrpc-utils": "^1.0.8", @@ -12541,7 +12555,6 @@ "version": "3.1.8", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", "integrity": "sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==", - "peer": true, "dependencies": { "node-fetch": "^2.6.12" } @@ -29188,7 +29201,9 @@ "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.25.7", - "@onflow/fcl": "1.13.4" + "@onflow/fcl": "1.13.4", + "@walletconnect/jsonrpc-http-connection": "^1.0.8", + "@walletconnect/jsonrpc-provider": "^1.0.14" }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", diff --git a/packages/fcl-ethereum-provider/package.json b/packages/fcl-ethereum-provider/package.json index e652f9930..3f957c475 100644 --- a/packages/fcl-ethereum-provider/package.json +++ b/packages/fcl-ethereum-provider/package.json @@ -36,6 +36,8 @@ }, "dependencies": { "@babel/runtime": "^7.25.7", - "@onflow/fcl": "1.13.4" + "@onflow/fcl": "1.13.4", + "@walletconnect/jsonrpc-http-connection": "^1.0.8", + "@walletconnect/jsonrpc-provider": "^1.0.14" } } diff --git a/packages/fcl-ethereum-provider/src/constants.ts b/packages/fcl-ethereum-provider/src/constants.ts new file mode 100644 index 000000000..542f5c8c8 --- /dev/null +++ b/packages/fcl-ethereum-provider/src/constants.ts @@ -0,0 +1,15 @@ +export enum FlowNetwork { + MAINNET = "mainnet", + TESTNET = "testnet", +} + +export const FLOW_CHAINS = { + [FlowNetwork.MAINNET]: { + eip155ChainId: 747, + publicRpcUrl: "https://access.mainnet.nodes.onflow.org", + }, + [FlowNetwork.TESTNET]: { + eip155ChainId: 646, + publicRpcUrl: "https://access.testnet.nodes.onflow.org", + }, +} diff --git a/packages/fcl-ethereum-provider/src/create-provider.ts b/packages/fcl-ethereum-provider/src/create-provider.ts index ec0dc8bc9..a440e8a42 100644 --- a/packages/fcl-ethereum-provider/src/create-provider.ts +++ b/packages/fcl-ethereum-provider/src/create-provider.ts @@ -5,6 +5,8 @@ import {RpcProcessor} from "./rpc/rpc-processor" import {Service} from "@onflow/typedefs" import {EventDispatcher} from "./events/event-dispatcher" import {AccountManager} from "./accounts/account-manager" +import {FLOW_CHAINS} from "./constants" +import {Gateway} from "./gateway/gateway" /** * Create a new FCL Ethereum provider @@ -28,10 +30,22 @@ import {AccountManager} from "./accounts/account-manager" export function createProvider(config: { user: typeof fcl.currentUser service?: Service - gateway?: string + rpcUrls?: {[chainId: string]: number} }): Eip1193Provider { + const defaultRpcUrls = Object.values(FLOW_CHAINS).reduce( + (acc, chain) => { + acc[chain.eip155ChainId] = chain.publicRpcUrl + return acc + }, + {} as {[chainId: number]: string} + ) + const accountManager = new AccountManager(config.user) - const rpcProcessor = new RpcProcessor(accountManager) + const gateway = new Gateway({ + ...defaultRpcUrls, + ...(config.rpcUrls || {}), + }) + const rpcProcessor = new RpcProcessor(gateway, accountManager) const eventProcessor = new EventDispatcher() const provider = new FclEthereumProvider(rpcProcessor, eventProcessor) return provider diff --git a/packages/fcl-ethereum-provider/src/gateway/gateway.test.ts b/packages/fcl-ethereum-provider/src/gateway/gateway.test.ts new file mode 100644 index 000000000..031e1a5bb --- /dev/null +++ b/packages/fcl-ethereum-provider/src/gateway/gateway.test.ts @@ -0,0 +1,201 @@ +import HttpConnection from "@walletconnect/jsonrpc-http-connection" +import {Gateway} from "./gateway" +import * as fcl from "@onflow/fcl" +import {JsonRpcProvider} from "@walletconnect/jsonrpc-provider" + +jest.mock("@walletconnect/jsonrpc-http-connection") +jest.mock("@walletconnect/jsonrpc-provider") +jest.mock("@onflow/fcl", () => ({ + getChainId: jest.fn(), +})) + +describe("gateway", () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + test("request should work for mainnet", async () => { + const gateway = new Gateway({ + 747: "https://example.com", + 646: "https://example.com/testnet", + }) + + jest.mocked(fcl.getChainId).mockResolvedValue("mainnet") + jest.mocked(JsonRpcProvider).mockImplementation( + jest.fn( + () => + ({ + request: jest.fn().mockResolvedValue(["0x123"]), + }) as any as JsonRpcProvider + ) + ) + + const returnValue = await gateway.request({ + method: "eth_accounts", + params: [], + }) + + // Check that the arguments are correct + expect( + jest.mocked(JsonRpcProvider).mock.results[0].value.request + ).toHaveBeenCalledWith({method: "eth_accounts", params: []}) + + // Check that the return value propogates correctly + expect(returnValue).toEqual(["0x123"]) + + // Verify that the mainnet provider was used + expect(JsonRpcProvider).toHaveBeenCalled() + expect(JsonRpcProvider).toHaveBeenCalledTimes(1) + expect(JsonRpcProvider).toHaveBeenCalledWith( + jest.mocked(HttpConnection).mock.instances[0] + ) + expect(HttpConnection).toHaveBeenCalledWith("https://example.com") + }) + + test("request should work for testnet", async () => { + const gateway = new Gateway({ + 747: "https://example.com", + 646: "https://example.com/testnet", + }) + + jest.mocked(fcl.getChainId).mockResolvedValue("testnet") + jest.mocked(JsonRpcProvider).mockImplementation( + jest.fn( + () => + ({ + request: jest.fn().mockResolvedValue(["0x123"]), + }) as any as JsonRpcProvider + ) + ) + + const returnValue = await gateway.request({ + method: "eth_accounts", + params: [], + }) + + // Check that the arguments are correct + expect( + jest.mocked(JsonRpcProvider).mock.results[0].value.request + ).toHaveBeenCalledWith({method: "eth_accounts", params: []}) + + // Check that the return value propogates correctly + expect(returnValue).toEqual(["0x123"]) + + // Verify that the testnet provider was used + expect(JsonRpcProvider).toHaveBeenCalled() + expect(JsonRpcProvider).toHaveBeenCalledTimes(1) + expect(JsonRpcProvider).toHaveBeenCalledWith( + jest.mocked(HttpConnection).mock.instances[0] + ) + expect(HttpConnection).toHaveBeenCalledWith("https://example.com/testnet") + }) + + test("subsequent requests should use the same provider", async () => { + const gateway = new Gateway({ + 747: "https://example.com", + 646: "https://example.com/testnet", + }) + + jest.mocked(fcl.getChainId).mockResolvedValue("testnet") + jest.mocked(JsonRpcProvider).mockImplementation( + jest.fn( + () => + ({ + request: jest.fn().mockResolvedValue(["0x123"]), + }) as any as JsonRpcProvider + ) + ) + + await gateway.request({ + method: "eth_accounts", + params: [], + }) + + await gateway.request({ + method: "eth_accounts", + params: [], + }) + + // Verify that the testnet provider was used + expect(JsonRpcProvider).toHaveBeenCalled() + expect(JsonRpcProvider).toHaveBeenCalledTimes(1) + expect(JsonRpcProvider).toHaveBeenCalledWith( + jest.mocked(HttpConnection).mock.instances[0] + ) + expect(HttpConnection).toHaveBeenCalledWith("https://example.com/testnet") + }) + + test("request should throw if chainId is not found", async () => { + const gateway = new Gateway({ + 747: "https://example.com", + 646: "https://example.com/testnet", + }) + + jest.mocked(fcl.getChainId).mockResolvedValue("unknown") + + await expect( + gateway.request({ + method: "eth_accounts", + params: [], + }) + ).rejects.toThrow("Unsupported chainId unknown") + }) + + test("should default to public gateway mainnet", async () => { + const gateway = new Gateway({}) + + jest.mocked(fcl.getChainId).mockResolvedValue("mainnet") + jest.mocked(JsonRpcProvider).mockImplementation( + jest.fn( + () => + ({ + request: jest.fn().mockResolvedValue(["0x123"]), + }) as any as JsonRpcProvider + ) + ) + + await gateway.request({ + method: "eth_accounts", + params: [], + }) + + // Verify that the mainnet provider was used + expect(JsonRpcProvider).toHaveBeenCalled() + expect(JsonRpcProvider).toHaveBeenCalledTimes(1) + expect(JsonRpcProvider).toHaveBeenCalledWith( + jest.mocked(HttpConnection).mock.instances[0] + ) + expect(HttpConnection).toHaveBeenCalledWith( + "https://access.mainnet.nodes.onflow.org" + ) + }) + + test("should default to public gateway testnet", async () => { + const gateway = new Gateway({}) + + jest.mocked(fcl.getChainId).mockResolvedValue("testnet") + jest.mocked(JsonRpcProvider).mockImplementation( + jest.fn( + () => + ({ + request: jest.fn().mockResolvedValue(["0x123"]), + }) as any as JsonRpcProvider + ) + ) + + await gateway.request({ + method: "eth_accounts", + params: [], + }) + + // Verify that the testnet provider was used + expect(JsonRpcProvider).toHaveBeenCalled() + expect(JsonRpcProvider).toHaveBeenCalledTimes(1) + expect(JsonRpcProvider).toHaveBeenCalledWith( + jest.mocked(HttpConnection).mock.instances[0] + ) + expect(HttpConnection).toHaveBeenCalledWith( + "https://access.testnet.nodes.onflow.org" + ) + }) +}) diff --git a/packages/fcl-ethereum-provider/src/gateway/gateway.ts b/packages/fcl-ethereum-provider/src/gateway/gateway.ts new file mode 100644 index 000000000..ea56ed4dc --- /dev/null +++ b/packages/fcl-ethereum-provider/src/gateway/gateway.ts @@ -0,0 +1,35 @@ +import HTTPConnection from "@walletconnect/jsonrpc-http-connection" +import * as fcl from "@onflow/fcl" +import {JsonRpcProvider} from "@walletconnect/jsonrpc-provider" +import {FLOW_CHAINS, FlowNetwork} from "../constants" + +export class Gateway { + private providers: {[chainId: number]: JsonRpcProvider} = {} + + constructor(private rpcUrls: {[chainId: number]: string}) {} + + async request({method, params}: {method: string; params: any}) { + return this.getProvider().then(provider => + provider.request({method, params}) + ) + } + + private async getProvider(): Promise { + const flowChainId = await fcl.getChainId() + if (!(flowChainId in FLOW_CHAINS)) { + throw new Error(`Unsupported chainId ${flowChainId}`) + } + + const {eip155ChainId} = FLOW_CHAINS[flowChainId as FlowNetwork] + if (this.providers[eip155ChainId]) { + return this.providers[eip155ChainId] + } + + const rpcUrl = + this.rpcUrls[eip155ChainId] || + FLOW_CHAINS[flowChainId as FlowNetwork].publicRpcUrl + const provider = new JsonRpcProvider(new HTTPConnection(rpcUrl)) + this.providers[eip155ChainId] = provider + return provider + } +} diff --git a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.test.ts b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.test.ts index d97f91dde..91723755c 100644 --- a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.test.ts +++ b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.test.ts @@ -1,5 +1,30 @@ -describe("rpc", () => { - test("should be tested", () => { - expect(true).toBe(true) +import {AccountManager} from "../accounts/account-manager" +import {Gateway} from "../gateway/gateway" +import {RpcProcessor} from "./rpc-processor" + +jest.mock("../gateway/gateway") +jest.mock("../accounts/account-manager") + +describe("rpc processor", () => { + test("fallback to gateway", async () => { + const gateway: jest.Mocked = new (Gateway as any)() + const accountManager: jest.Mocked = + new (AccountManager as any)() + const rpcProcessor = new RpcProcessor(gateway, accountManager) + + jest.mocked(gateway).request.mockResolvedValue("0x0") + + const response = await rpcProcessor.handleRequest({ + method: "eth_blockNumber", + params: [], + }) + + expect(response).toEqual("0x0") + expect(gateway.request).toHaveBeenCalled() + expect(gateway.request).toHaveBeenCalledTimes(1) + expect(gateway.request).toHaveBeenCalledWith({ + method: "eth_blockNumber", + params: [], + }) }) }) diff --git a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts index eda4bbc6b..b30031468 100644 --- a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts +++ b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts @@ -1,9 +1,14 @@ import {ProviderRequest} from "../types/provider" import {ethAccounts} from "./handlers/eth-accounts" +import {JsonRpcProvider} from "@walletconnect/jsonrpc-provider" +import {Gateway} from "../gateway/gateway" import {AccountManager} from "../accounts/account-manager" export class RpcProcessor { - constructor(private accountManager: AccountManager) {} + constructor( + private gateway: Gateway, + private accountManager: AccountManager + ) {} async handleRequest({method, params}: ProviderRequest): Promise { switch (method) { @@ -12,7 +17,7 @@ export class RpcProcessor { case "eth_requestAccounts": throw new Error("Not implemented") default: - throw new Error(`Method ${method} not supported`) + return await this.gateway.request({method, params}) } } } diff --git a/packages/fcl-ethereum-provider/src/types/provider.ts b/packages/fcl-ethereum-provider/src/types/provider.ts index 7ff6fc0e9..e49dfbd2e 100644 --- a/packages/fcl-ethereum-provider/src/types/provider.ts +++ b/packages/fcl-ethereum-provider/src/types/provider.ts @@ -29,8 +29,3 @@ export interface Eip1193Provider { listener: EventCallback ): void } - -export enum FLOW_CHAIN_ID { - MAINNET = 747, - TESTNET = 646, -} From 8c69e9c39e3c116a95b8f43c30647e135156e99b Mon Sep 17 00:00:00 2001 From: Chase Fleming Date: Tue, 28 Jan 2025 16:45:58 -0800 Subject: [PATCH 06/56] Implement eth request accounts (#2088) * Implement eth_requestAccounts * Add handler code * Pass account manager and use * Set coa address * Move coa fetcher * Remove comment * Only check if changed * Stop race conditions * Fix types and update address * Add basic tests * Fix * Use mock user * Add test * Add more tests * Add test * Add test * Add test * Add test * Add test * Add tests * Import * Fix prettier * Fix prettier --------- Co-authored-by: Chase Fleming <1666730+chasefleming@users.noreply.github.com> --- package-lock.json | 1 + packages/fcl-ethereum-provider/package.json | 1 + .../src/accounts/account-manager.test.ts | 224 ++++++++++++++++++ .../src/accounts/account-manager.ts | 99 +++++++- .../src/rpc/handlers/eth-accounts.test.ts | 53 ++++- .../src/rpc/handlers/eth-accounts.ts | 9 +- .../src/rpc/rpc-processor.ts | 4 +- 7 files changed, 380 insertions(+), 11 deletions(-) create mode 100644 packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts diff --git a/package-lock.json b/package-lock.json index 1366e79d0..7fda0fe47 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29208,6 +29208,7 @@ "devDependencies": { "@babel/preset-typescript": "^7.25.7", "@onflow/fcl-bundle": "1.6.0", + "@onflow/typedefs": "^1.4.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", diff --git a/packages/fcl-ethereum-provider/package.json b/packages/fcl-ethereum-provider/package.json index 3f957c475..a25e0e6c3 100644 --- a/packages/fcl-ethereum-provider/package.json +++ b/packages/fcl-ethereum-provider/package.json @@ -15,6 +15,7 @@ "devDependencies": { "@babel/preset-typescript": "^7.25.7", "@onflow/fcl-bundle": "1.6.0", + "@onflow/typedefs": "^1.4.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", diff --git a/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts b/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts new file mode 100644 index 000000000..b24c6bb38 --- /dev/null +++ b/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts @@ -0,0 +1,224 @@ +import {AccountManager} from "./account-manager" +import * as fcl from "@onflow/fcl" +import {CurrentUser} from "@onflow/typedefs" + +jest.mock("@onflow/fcl", () => ({ + currentUser: { + subscribe: jest.fn(), + snapshot: jest.fn(), + }, + query: jest.fn(), + arg: jest.fn(), + t: { + Address: "Address", + }, +})) + +function mockUser(): jest.Mocked { + const currentUser = { + authenticate: jest.fn(), + unauthenticate: jest.fn(), + authorization: jest.fn(), + signUserMessage: jest.fn(), + subscribe: jest.fn(), + snapshot: jest.fn(), + resolveArgument: jest.fn(), + } + + return Object.assign( + jest.fn(() => currentUser), + currentUser + ) +} + +describe("AccountManager", () => { + let accountManager: AccountManager + let mockQuery: jest.Mock + let user: jest.Mocked + + beforeEach(() => { + mockQuery = jest.fn() + + user = mockUser() + + jest.spyOn(fcl, "query").mockImplementation(mockQuery) + + accountManager = new AccountManager(user) + }) + + afterEach(() => { + jest.clearAllMocks() + }) + + it("should initialize with null COA address", () => { + expect(accountManager.getCOAAddress()).toBeNull() + expect(accountManager.getAccounts()).toEqual([]) + }) + + it("should reset state when the user is not logged in", async () => { + user.snapshot.mockResolvedValueOnce({addr: undefined} as CurrentUser) + + await accountManager.updateCOAAddress() + + expect(accountManager.getCOAAddress()).toBeNull() + expect(accountManager.getAccounts()).toEqual([]) + }) + + it("should fetch and update COA address when user logs in", async () => { + user.snapshot.mockResolvedValue({addr: "0x1"} as CurrentUser) + mockQuery.mockResolvedValue("0x123") + + await accountManager.updateCOAAddress() + + expect(accountManager.getCOAAddress()).toBe("0x123") + expect(accountManager.getAccounts()).toEqual(["0x123"]) + expect(fcl.query).toHaveBeenCalledWith({ + cadence: expect.any(String), + args: expect.any(Function), + }) + }) + + it("should not update COA address if user has not changed and force is false", async () => { + user.snapshot.mockResolvedValue({addr: "0x1"} as CurrentUser) + mockQuery.mockResolvedValue("0x123") + user.subscribe.mockImplementation(fn => { + fn({addr: "0x1"}) + }) + + await accountManager.updateCOAAddress() + expect(accountManager.getCOAAddress()).toBe("0x123") + expect(fcl.query).toHaveBeenCalledTimes(1) + + // Re-run without changing the address + await accountManager.updateCOAAddress() + expect(accountManager.getCOAAddress()).toBe("0x123") + expect(fcl.query).toHaveBeenCalledTimes(1) // Should not have fetched again + }) + + it("should force update COA address even if user has not changed", async () => { + user.snapshot.mockResolvedValue({addr: "0x1"} as CurrentUser) + mockQuery.mockResolvedValue("0x123") + user.subscribe.mockImplementation(fn => { + fn({addr: "0x1"}) + }) + + await accountManager.updateCOAAddress() + expect(fcl.query).toHaveBeenCalledTimes(1) + + // Force update + await accountManager.updateCOAAddress(true) + expect(fcl.query).toHaveBeenCalledTimes(2) + }) + + it("should not update COA address if fetch is outdated when user changes", async () => { + // Simulates the user address changing from 0x1 to 0x2 + user.snapshot + .mockResolvedValueOnce({addr: "0x1"} as CurrentUser) // for 1st call + .mockResolvedValueOnce({addr: "0x2"} as CurrentUser) // for 2nd call + + mockQuery + // 1st fetch: delayed + .mockImplementationOnce( + () => new Promise(resolve => setTimeout(() => resolve("0x123"), 500)) + ) + // 2nd fetch: immediate + .mockResolvedValueOnce("0x456") + + const updatePromise1 = accountManager.updateCOAAddress() + const updatePromise2 = accountManager.updateCOAAddress() + await Promise.all([updatePromise1, updatePromise2]) + + // The second fetch (for address 0x2) is the latest, so "0x456" + expect(accountManager.getCOAAddress()).toBe("0x456") + }) + + it("should clear COA address if fetch fails and is the latest", async () => { + user.snapshot.mockResolvedValueOnce({addr: "0x1"} as CurrentUser) + mockQuery.mockRejectedValueOnce(new Error("Fetch failed")) + + await expect(accountManager.updateCOAAddress()).rejects.toThrow( + "Fetch failed" + ) + + expect(accountManager.getCOAAddress()).toBeNull() + }) + + it("should handle user changes correctly", async () => { + user.snapshot + .mockResolvedValueOnce({addr: "0x1"} as CurrentUser) + .mockResolvedValueOnce({addr: "0x2"} as CurrentUser) + + mockQuery + .mockResolvedValueOnce("0x123") // for user 0x1 + .mockResolvedValueOnce("0x456") // for user 0x2 + + await accountManager.updateCOAAddress() + expect(accountManager.getCOAAddress()).toBe("0x123") + + await accountManager.updateCOAAddress() + expect(accountManager.getCOAAddress()).toBe("0x456") + }) + + it("should call the callback with updated accounts in subscribe", async () => { + user.snapshot.mockResolvedValue({addr: "0x1"} as CurrentUser) + mockQuery.mockResolvedValue("0x123") + + const callback = jest.fn() + user.subscribe.mockImplementation(fn => { + fn({addr: "0x1"}) + }) + + mockQuery.mockResolvedValueOnce("0x123") + + accountManager.subscribe(callback) + + await new Promise(setImmediate) + + expect(callback).toHaveBeenCalledWith(["0x123"]) + }) + + it("should reset accounts in subscribe if user is not authenticated", () => { + mockQuery.mockResolvedValue("0x123") + user.snapshot.mockResolvedValue({addr: undefined} as CurrentUser) + + const callback = jest.fn() + + user.subscribe.mockImplementation(fn => { + fn({addr: null}) + }) + + accountManager.subscribe(callback) + + expect(callback).toHaveBeenCalledWith([]) + }) + + it("should call the callback when COA address is updated", async () => { + const callback = jest.fn() + + user.snapshot.mockResolvedValueOnce({addr: "0x1"} as CurrentUser) + + user.subscribe.mockImplementation(fn => { + fn({addr: "0x1"} as CurrentUser) + }) + + mockQuery.mockResolvedValueOnce("0x123") + + accountManager.subscribe(callback) + + await new Promise(setImmediate) + + expect(callback).toHaveBeenCalledWith(["0x123"]) + }) + + it("should return an empty array when COA address is null", () => { + expect(accountManager.getAccounts()).toEqual([]) + }) + + it("should return COA address array when available", async () => { + user.snapshot.mockResolvedValueOnce({addr: "0x1"} as CurrentUser) + mockQuery.mockResolvedValueOnce("0x123") + + await accountManager.updateCOAAddress() + expect(accountManager.getAccounts()).toEqual(["0x123"]) + }) +}) diff --git a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts index c275a05c0..a0e458311 100644 --- a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts +++ b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts @@ -1,14 +1,101 @@ import * as fcl from "@onflow/fcl" +import {CurrentUser} from "@onflow/typedefs" export class AccountManager { - constructor(private user: typeof fcl.currentUser) {} + private user: typeof fcl.currentUser - getAccounts(): string[] { - // ... get coa address - return [] + // For race-condition checks: + private currentFetchId = 0 + + // Track the last Flow address we fetched for + private lastFlowAddr: string | null = null + + // The COA address (or null if none/not fetched) + private coaAddress: string | null = null + + constructor(user: typeof fcl.currentUser) { + this.user = user } - subscribe(callback: (accounts: string[]) => void) { - // ... + private async fetchCOAFromFlowAddress(flowAddr: string): Promise { + const cadenceScript = ` + import EVM + + access(all) + fun main(address: Address): String? { + if let coa = getAuthAccount(address) + .storage + .borrow<&EVM.CadenceOwnedAccount>(from: /storage/evm) { + return coa.address().toString() + } + return nil + } + ` + const response = await fcl.query({ + cadence: cadenceScript, + args: (arg: typeof fcl.arg, t: typeof fcl.t) => [ + arg(flowAddr, t.Address), + ], + }) + + if (!response) { + throw new Error("COA account not found for the authenticated user") + } + return response as string + } + + public async updateCOAAddress(force = false): Promise { + const snapshot = await this.user.snapshot() + const currentFlowAddr = snapshot?.addr + + // If user not logged in, reset everything + if (!currentFlowAddr) { + this.lastFlowAddr = null + this.coaAddress = null + return + } + + const userChanged = this.lastFlowAddr !== currentFlowAddr + if (force || userChanged) { + this.lastFlowAddr = currentFlowAddr + const fetchId = ++this.currentFetchId + + try { + const address = await this.fetchCOAFromFlowAddress(currentFlowAddr) + // Only update if this fetch is still the latest + if (fetchId === this.currentFetchId) { + this.coaAddress = address + } + } catch (error) { + // If this fetch is the latest, clear + if (fetchId === this.currentFetchId) { + this.coaAddress = null + } + throw error + } + } + } + + public getCOAAddress(): string | null { + return this.coaAddress + } + + public getAccounts(): string[] { + return this.coaAddress ? [this.coaAddress] : [] + } + + public subscribe(callback: (accounts: string[]) => void) { + this.user.subscribe(async (snapshot: CurrentUser) => { + if (!snapshot.addr) { + // user not authenticated => clear out + this.lastFlowAddr = null + this.coaAddress = null + callback(this.getAccounts()) + return + } + + await this.updateCOAAddress() + callback(this.getAccounts()) + }) } } diff --git a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.test.ts b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.test.ts index 2d8135b8e..5d3e4abf7 100644 --- a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.test.ts +++ b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.test.ts @@ -1,5 +1,16 @@ -import {ethAccounts} from "./eth-accounts" +// eth-accounts.spec.ts +import {ethAccounts, ethRequestAccounts} from "./eth-accounts" import {AccountManager} from "../../accounts/account-manager" +import * as fcl from "@onflow/fcl" +import {CurrentUser} from "@onflow/typedefs" + +// Mock FCL at the top-level +jest.mock("@onflow/fcl", () => ({ + currentUser: jest.fn().mockReturnValue({ + authenticate: jest.fn(), + snapshot: jest.fn(), + }), +})) describe("ethAccounts handler", () => { let accountManagerMock: jest.Mocked @@ -8,6 +19,7 @@ describe("ethAccounts handler", () => { accountManagerMock = { getAccounts: jest.fn(), subscribe: jest.fn(), + updateCOAAddress: jest.fn(), } as unknown as jest.Mocked }) @@ -29,3 +41,42 @@ describe("ethAccounts handler", () => { expect(accountManagerMock.getAccounts).toHaveBeenCalled() }) }) + +describe("ethRequestAccounts handler", () => { + let accountManagerMock: jest.Mocked + let userMock: ReturnType + + beforeEach(() => { + userMock = fcl.currentUser() + + userMock.snapshot.mockResolvedValue({addr: null}) + + accountManagerMock = { + getAccounts: jest.fn(), + updateCOAAddress: jest.fn(), + subscribe: jest.fn(), + } as unknown as jest.Mocked + }) + + it("should call authenticate, updateCOAAddress, and return the manager's accounts", async () => { + accountManagerMock.getAccounts.mockReturnValue(["0x1234..."]) + + const accounts = await ethRequestAccounts(accountManagerMock) + + expect(userMock.authenticate).toHaveBeenCalled() + expect(accountManagerMock.updateCOAAddress).toHaveBeenCalled() + expect(accountManagerMock.getAccounts).toHaveBeenCalled() + expect(accounts).toEqual(["0x1234..."]) + }) + + it("should handle empty accounts scenario", async () => { + accountManagerMock.getAccounts.mockReturnValue([]) + + const accounts = await ethRequestAccounts(accountManagerMock) + + expect(userMock.authenticate).toHaveBeenCalled() + expect(accountManagerMock.updateCOAAddress).toHaveBeenCalled() + expect(accountManagerMock.getAccounts).toHaveBeenCalled() + expect(accounts).toEqual([]) + }) +}) diff --git a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.ts b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.ts index 4848ce600..6aa1e5696 100644 --- a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.ts +++ b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.ts @@ -1,9 +1,14 @@ +import * as fcl from "@onflow/fcl" import {AccountManager} from "../../accounts/account-manager" export function ethAccounts(accountManager: AccountManager): string[] { return accountManager.getAccounts() } -export function ethRequestAccounts() { - throw new Error("Not implemented") +export async function ethRequestAccounts(accountManager: AccountManager) { + await fcl.currentUser().authenticate() + + await accountManager.updateCOAAddress() + + return accountManager.getAccounts() } diff --git a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts index b30031468..0911ab55b 100644 --- a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts +++ b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts @@ -1,5 +1,5 @@ import {ProviderRequest} from "../types/provider" -import {ethAccounts} from "./handlers/eth-accounts" +import {ethAccounts, ethRequestAccounts} from "./handlers/eth-accounts" import {JsonRpcProvider} from "@walletconnect/jsonrpc-provider" import {Gateway} from "../gateway/gateway" import {AccountManager} from "../accounts/account-manager" @@ -15,7 +15,7 @@ export class RpcProcessor { case "eth_accounts": return ethAccounts(this.accountManager) case "eth_requestAccounts": - throw new Error("Not implemented") + return ethRequestAccounts(this.accountManager) default: return await this.gateway.request({method, params}) } From d651fd235c50b7b51be5cd0fca415ef168d806f2 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> Date: Wed, 29 Jan 2025 15:13:04 -0800 Subject: [PATCH 07/56] Implement `eth_sendTransaction` RPC Method (#2089) --- .../src/__mocks__/fcl.ts | 20 ++ .../src/accounts/account-manager.test.ts | 177 ++++++++++++++---- .../src/accounts/account-manager.ts | 95 ++++++++++ .../fcl-ethereum-provider/src/constants.ts | 38 ++++ .../src/gateway/gateway.test.ts | 20 +- .../src/gateway/gateway.ts | 39 ++-- .../src/rpc/handlers/eth-accounts.test.ts | 11 +- .../rpc/handlers/eth-send-transaction.test.ts | 24 +++ .../src/rpc/handlers/eth-send-transaction.ts | 8 + .../src/rpc/rpc-processor.test.ts | 32 +++- .../src/rpc/rpc-processor.ts | 18 +- .../fcl-ethereum-provider/src/types/events.ts | 15 ++ 12 files changed, 430 insertions(+), 67 deletions(-) create mode 100644 packages/fcl-ethereum-provider/src/__mocks__/fcl.ts create mode 100644 packages/fcl-ethereum-provider/src/rpc/handlers/eth-send-transaction.test.ts create mode 100644 packages/fcl-ethereum-provider/src/rpc/handlers/eth-send-transaction.ts create mode 100644 packages/fcl-ethereum-provider/src/types/events.ts diff --git a/packages/fcl-ethereum-provider/src/__mocks__/fcl.ts b/packages/fcl-ethereum-provider/src/__mocks__/fcl.ts new file mode 100644 index 000000000..ad62d70b7 --- /dev/null +++ b/packages/fcl-ethereum-provider/src/__mocks__/fcl.ts @@ -0,0 +1,20 @@ +import * as fcl from "@onflow/fcl" + +export function mockUser(): jest.Mocked { + const currentUser = { + authenticate: jest.fn(), + unauthenticate: jest.fn(), + authorization: jest.fn(), + signUserMessage: jest.fn(), + subscribe: jest.fn(), + snapshot: jest.fn(), + resolveArgument: jest.fn(), + } + + return Object.assign( + () => { + return {...currentUser} + }, + {...currentUser} + ) +} diff --git a/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts b/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts index b24c6bb38..7186b0ddb 100644 --- a/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts +++ b/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts @@ -1,48 +1,28 @@ import {AccountManager} from "./account-manager" +import {mockUser} from "../__mocks__/fcl" import * as fcl from "@onflow/fcl" import {CurrentUser} from "@onflow/typedefs" -jest.mock("@onflow/fcl", () => ({ - currentUser: { - subscribe: jest.fn(), - snapshot: jest.fn(), - }, - query: jest.fn(), - arg: jest.fn(), - t: { - Address: "Address", - }, -})) - -function mockUser(): jest.Mocked { - const currentUser = { - authenticate: jest.fn(), - unauthenticate: jest.fn(), - authorization: jest.fn(), - signUserMessage: jest.fn(), - subscribe: jest.fn(), - snapshot: jest.fn(), - resolveArgument: jest.fn(), +jest.mock("@onflow/fcl", () => { + const fcl = jest.requireActual("@onflow/fcl") + return { + withPrefix: fcl.withPrefix, + sansPrefix: fcl.sansPrefix, + tx: jest.fn(), + mutate: jest.fn(), + query: jest.fn(), } +}) - return Object.assign( - jest.fn(() => currentUser), - currentUser - ) -} +const mockFcl = jest.mocked(fcl) +const mockQuery = jest.mocked(fcl.query) describe("AccountManager", () => { let accountManager: AccountManager - let mockQuery: jest.Mock let user: jest.Mocked beforeEach(() => { - mockQuery = jest.fn() - user = mockUser() - - jest.spyOn(fcl, "query").mockImplementation(mockQuery) - accountManager = new AccountManager(user) }) @@ -83,6 +63,7 @@ describe("AccountManager", () => { mockQuery.mockResolvedValue("0x123") user.subscribe.mockImplementation(fn => { fn({addr: "0x1"}) + return () => {} }) await accountManager.updateCOAAddress() @@ -100,6 +81,7 @@ describe("AccountManager", () => { mockQuery.mockResolvedValue("0x123") user.subscribe.mockImplementation(fn => { fn({addr: "0x1"}) + return () => {} }) await accountManager.updateCOAAddress() @@ -140,7 +122,7 @@ describe("AccountManager", () => { "Fetch failed" ) - expect(accountManager.getCOAAddress()).toBeNull() + expect(await accountManager.getCOAAddress()).toBeNull() }) it("should handle user changes correctly", async () => { @@ -166,6 +148,7 @@ describe("AccountManager", () => { const callback = jest.fn() user.subscribe.mockImplementation(fn => { fn({addr: "0x1"}) + return () => {} }) mockQuery.mockResolvedValueOnce("0x123") @@ -185,6 +168,7 @@ describe("AccountManager", () => { user.subscribe.mockImplementation(fn => { fn({addr: null}) + return () => {} }) accountManager.subscribe(callback) @@ -199,6 +183,7 @@ describe("AccountManager", () => { user.subscribe.mockImplementation(fn => { fn({addr: "0x1"} as CurrentUser) + return () => {} }) mockQuery.mockResolvedValueOnce("0x123") @@ -222,3 +207,129 @@ describe("AccountManager", () => { expect(accountManager.getAccounts()).toEqual(["0x123"]) }) }) + +describe("send transaction", () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + test("send transaction mainnet", async () => { + const user = mockUser() + const accountManager = new AccountManager(user) + + const mockTxResult = { + onceExecuted: jest.fn().mockResolvedValue({ + events: [ + { + type: "A.e467b9dd11fa00df.EVM.TransactionExecuted", + data: { + hash: ["12", "34"], + }, + }, + ], + }), + } as any as jest.Mocked> + + jest.mocked(fcl.tx).mockReturnValue(mockTxResult) + jest.mocked(fcl.mutate).mockResolvedValue("1111") + + const tx = { + to: "0x1234", + from: "0x1234", + value: "0", + data: "0x1234", + nonce: "0", + gas: "0", + chainId: "747", + } + + const result = await accountManager.sendTransaction(tx) + + expect(result).toEqual("1852") + expect(fcl.mutate).toHaveBeenCalled() + expect(mockFcl.mutate.mock.calls[0][0]).toMatchObject({ + cadence: expect.any(String), + args: expect.any(Function), + authz: user, + limit: 9999, + }) + + expect(mockFcl.tx).toHaveBeenCalledWith("1111") + expect(mockFcl.tx).toHaveBeenCalledTimes(1) + expect(mockTxResult.onceExecuted).toHaveBeenCalledTimes(1) + }) + + test("send transaction testnet", async () => { + const user = mockUser() + const accountManager = new AccountManager(user) + + const mockTxResult = { + onceExecuted: jest.fn().mockResolvedValue({ + events: [ + { + type: "A.8c5303eaa26202d6.EVM.TransactionExecuted", + data: { + hash: ["12", "34"], + }, + }, + ], + }), + } as any as jest.Mocked> + + jest.mocked(fcl.tx).mockReturnValue(mockTxResult) + jest.mocked(fcl.mutate).mockResolvedValue("1111") + + const tx = { + to: "0x1234", + from: "0x1234", + value: "0", + data: "0x1234", + nonce: "0", + gas: "0", + chainId: "646", + } + + const result = await accountManager.sendTransaction(tx) + + expect(result).toEqual("1852") + expect(fcl.mutate).toHaveBeenCalled() + expect(mockFcl.mutate.mock.calls[0][0]).toMatchObject({ + cadence: expect.any(String), + args: expect.any(Function), + authz: user, + limit: 9999, + }) + + expect(mockFcl.tx).toHaveBeenCalledWith("1111") + expect(mockFcl.tx).toHaveBeenCalledTimes(1) + expect(mockTxResult.onceExecuted).toHaveBeenCalledTimes(1) + }) + + test("throws error if no executed event not found", async () => { + const user = mockUser() + const accountManager = new AccountManager(user) + + const mockTxResult = { + onceExecuted: jest.fn().mockResolvedValue({ + events: [], + }), + } as any as jest.Mocked> + + jest.mocked(fcl.tx).mockReturnValue(mockTxResult) + jest.mocked(fcl.mutate).mockResolvedValue("1111") + + const tx = { + to: "0x1234", + from: "0x1234", + value: "0", + data: "0x1234", + nonce: "0", + gas: "0", + chainId: "646", + } + + await expect(accountManager.sendTransaction(tx)).rejects.toThrow( + "EVM transaction hash not found" + ) + }) +}) diff --git a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts index a0e458311..7e8084a3b 100644 --- a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts +++ b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts @@ -1,5 +1,14 @@ import * as fcl from "@onflow/fcl" import {CurrentUser} from "@onflow/typedefs" +import { + ContractType, + EVENT_IDENTIFIERS, + EventType, + FLOW_CHAINS, + FLOW_CONTRACTS, + FlowNetwork, +} from "../constants" +import {TransactionExecutedEvent} from "../types/events" export class AccountManager { private user: typeof fcl.currentUser @@ -98,4 +107,90 @@ export class AccountManager { callback(this.getAccounts()) }) } + + async sendTransaction({ + to, + from, + value, + data, + gas, + chainId, + }: { + to: string + from: string + value: string + data: string + gas: string + chainId: string + }) { + // Find the Flow network based on the chain ID + const flowNetwork = Object.entries(FLOW_CHAINS).find( + ([, chain]) => chain.eip155ChainId === parseInt(chainId) + )?.[0] as FlowNetwork | undefined + + if (!flowNetwork) { + throw new Error("Flow network not found for chain ID") + } + + const evmContractAddress = fcl.withPrefix( + FLOW_CONTRACTS[ContractType.EVM][flowNetwork] + ) + + const txId = await fcl.mutate({ + cadence: `import EVM from ${evmContractAddress} + + /// Executes the calldata from the signer's COA + /// + transaction(evmContractAddressHex: String, calldata: String, gasLimit: UInt64, value: UInt256) { + + let evmAddress: EVM.EVMAddress + let coa: auth(EVM.Call) &EVM.CadenceOwnedAccount + + prepare(signer: auth(BorrowValue) &Account) { + self.evmAddress = EVM.addressFromString(evmContractAddressHex) + + self.coa = signer.storage.borrow(from: /storage/evm) + ?? panic("Could not borrow COA from provided gateway address") + } + + execute { + let valueBalance = EVM.Balance(attoflow: value) + let callResult = self.coa.call( + to: self.evmAddress, + data: calldata.decodeHex(), + gasLimit: gasLimit, + value: valueBalance + ) + assert(callResult.status == EVM.Status.successful, message: "Call failed") + } + }`, + limit: 9999, + args: (arg: typeof fcl.arg, t: typeof fcl.t) => [ + arg(to, t.String), + arg(data, t.String), + arg(gas, t.UInt64), + arg(value, t.UInt256), + ], + authz: this.user, + }) + + const result = await fcl.tx(txId).onceExecuted() + const {events} = result + + const evmTxExecutedEvent = events.find( + event => + event.type === + EVENT_IDENTIFIERS[EventType.TRANSACTION_EXECUTED][flowNetwork] + ) + if (!evmTxExecutedEvent) { + throw new Error("EVM transaction hash not found") + } + + const eventData: TransactionExecutedEvent = evmTxExecutedEvent.data + const evmTxHash = eventData.hash + .map(h => parseInt(h, 16).toString().padStart(2, "0")) + .join("") + + return evmTxHash + } } diff --git a/packages/fcl-ethereum-provider/src/constants.ts b/packages/fcl-ethereum-provider/src/constants.ts index 542f5c8c8..311b8e8d5 100644 --- a/packages/fcl-ethereum-provider/src/constants.ts +++ b/packages/fcl-ethereum-provider/src/constants.ts @@ -13,3 +13,41 @@ export const FLOW_CHAINS = { publicRpcUrl: "https://access.testnet.nodes.onflow.org", }, } + +export enum ContractType { + EVM = "EVM", +} + +export enum EventType { + TRANSACTION_EXECUTED = "TRANSACTION_EXECUTED", +} + +export const EVENT_IDENTIFIERS = { + [EventType.TRANSACTION_EXECUTED]: { + [FlowNetwork.TESTNET]: "A.8c5303eaa26202d6.EVM.TransactionExecuted", + [FlowNetwork.MAINNET]: "A.e467b9dd11fa00df.EVM.TransactionExecuted", + }, +} + +export const FLOW_CONTRACTS = { + [ContractType.EVM]: { + [FlowNetwork.TESTNET]: "0x8c5303eaa26202d6", + [FlowNetwork.MAINNET]: "0xe467b9dd11fa00df", + }, +} + +export interface TransactionExecutedEvent { + hash: string[] + index: string + type: string + payload: string[] + errorCode: string + errorMessage: string + gasConsumed: string + contractAddress: string + logs: string[] + blockHeight: string + returnedData: string[] + precompiledCalls: string[] + stateUpdateChecksum: string +} diff --git a/packages/fcl-ethereum-provider/src/gateway/gateway.test.ts b/packages/fcl-ethereum-provider/src/gateway/gateway.test.ts index 031e1a5bb..aa2bad31d 100644 --- a/packages/fcl-ethereum-provider/src/gateway/gateway.test.ts +++ b/packages/fcl-ethereum-provider/src/gateway/gateway.test.ts @@ -1,13 +1,9 @@ import HttpConnection from "@walletconnect/jsonrpc-http-connection" import {Gateway} from "./gateway" -import * as fcl from "@onflow/fcl" import {JsonRpcProvider} from "@walletconnect/jsonrpc-provider" jest.mock("@walletconnect/jsonrpc-http-connection") jest.mock("@walletconnect/jsonrpc-provider") -jest.mock("@onflow/fcl", () => ({ - getChainId: jest.fn(), -})) describe("gateway", () => { beforeEach(() => { @@ -20,7 +16,6 @@ describe("gateway", () => { 646: "https://example.com/testnet", }) - jest.mocked(fcl.getChainId).mockResolvedValue("mainnet") jest.mocked(JsonRpcProvider).mockImplementation( jest.fn( () => @@ -33,6 +28,7 @@ describe("gateway", () => { const returnValue = await gateway.request({ method: "eth_accounts", params: [], + chainId: 747, }) // Check that the arguments are correct @@ -58,7 +54,6 @@ describe("gateway", () => { 646: "https://example.com/testnet", }) - jest.mocked(fcl.getChainId).mockResolvedValue("testnet") jest.mocked(JsonRpcProvider).mockImplementation( jest.fn( () => @@ -71,6 +66,7 @@ describe("gateway", () => { const returnValue = await gateway.request({ method: "eth_accounts", params: [], + chainId: 646, }) // Check that the arguments are correct @@ -96,7 +92,6 @@ describe("gateway", () => { 646: "https://example.com/testnet", }) - jest.mocked(fcl.getChainId).mockResolvedValue("testnet") jest.mocked(JsonRpcProvider).mockImplementation( jest.fn( () => @@ -109,11 +104,13 @@ describe("gateway", () => { await gateway.request({ method: "eth_accounts", params: [], + chainId: 646, }) await gateway.request({ method: "eth_accounts", params: [], + chainId: 646, }) // Verify that the testnet provider was used @@ -131,20 +128,18 @@ describe("gateway", () => { 646: "https://example.com/testnet", }) - jest.mocked(fcl.getChainId).mockResolvedValue("unknown") - await expect( gateway.request({ method: "eth_accounts", params: [], + chainId: 123, }) - ).rejects.toThrow("Unsupported chainId unknown") + ).rejects.toThrow("RPC URL not found for chainId 123") }) test("should default to public gateway mainnet", async () => { const gateway = new Gateway({}) - jest.mocked(fcl.getChainId).mockResolvedValue("mainnet") jest.mocked(JsonRpcProvider).mockImplementation( jest.fn( () => @@ -157,6 +152,7 @@ describe("gateway", () => { await gateway.request({ method: "eth_accounts", params: [], + chainId: 747, }) // Verify that the mainnet provider was used @@ -173,7 +169,6 @@ describe("gateway", () => { test("should default to public gateway testnet", async () => { const gateway = new Gateway({}) - jest.mocked(fcl.getChainId).mockResolvedValue("testnet") jest.mocked(JsonRpcProvider).mockImplementation( jest.fn( () => @@ -186,6 +181,7 @@ describe("gateway", () => { await gateway.request({ method: "eth_accounts", params: [], + chainId: 646, }) // Verify that the testnet provider was used diff --git a/packages/fcl-ethereum-provider/src/gateway/gateway.ts b/packages/fcl-ethereum-provider/src/gateway/gateway.ts index ea56ed4dc..54760d955 100644 --- a/packages/fcl-ethereum-provider/src/gateway/gateway.ts +++ b/packages/fcl-ethereum-provider/src/gateway/gateway.ts @@ -1,33 +1,44 @@ import HTTPConnection from "@walletconnect/jsonrpc-http-connection" -import * as fcl from "@onflow/fcl" import {JsonRpcProvider} from "@walletconnect/jsonrpc-provider" -import {FLOW_CHAINS, FlowNetwork} from "../constants" +import {FLOW_CHAINS} from "../constants" export class Gateway { private providers: {[chainId: number]: JsonRpcProvider} = {} constructor(private rpcUrls: {[chainId: number]: string}) {} - async request({method, params}: {method: string; params: any}) { - return this.getProvider().then(provider => + public async request({ + method, + params, + chainId, + }: { + method: string + params: any + chainId: number + }): Promise { + return this.getProvider(chainId).then(provider => provider.request({method, params}) ) } - private async getProvider(): Promise { - const flowChainId = await fcl.getChainId() - if (!(flowChainId in FLOW_CHAINS)) { - throw new Error(`Unsupported chainId ${flowChainId}`) - } - - const {eip155ChainId} = FLOW_CHAINS[flowChainId as FlowNetwork] + private async getProvider(eip155ChainId: number): Promise { if (this.providers[eip155ChainId]) { return this.providers[eip155ChainId] } - const rpcUrl = - this.rpcUrls[eip155ChainId] || - FLOW_CHAINS[flowChainId as FlowNetwork].publicRpcUrl + let rpcUrl: string | undefined = this.rpcUrls[eip155ChainId] + if (!rpcUrl) { + for (const chain of Object.values(FLOW_CHAINS)) { + if (chain.eip155ChainId === eip155ChainId) { + rpcUrl = chain.publicRpcUrl + break + } + } + } + if (!rpcUrl) { + throw new Error(`RPC URL not found for chainId ${eip155ChainId}`) + } + const provider = new JsonRpcProvider(new HTTPConnection(rpcUrl)) this.providers[eip155ChainId] = provider return provider diff --git a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.test.ts b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.test.ts index 5d3e4abf7..2df9449e1 100644 --- a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.test.ts +++ b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.test.ts @@ -3,6 +3,7 @@ import {ethAccounts, ethRequestAccounts} from "./eth-accounts" import {AccountManager} from "../../accounts/account-manager" import * as fcl from "@onflow/fcl" import {CurrentUser} from "@onflow/typedefs" +import {mockUser} from "../../__mocks__/fcl" // Mock FCL at the top-level jest.mock("@onflow/fcl", () => ({ @@ -23,7 +24,7 @@ describe("ethAccounts handler", () => { } as unknown as jest.Mocked }) - it("should return accounts from the AccountManager", () => { + it("should return accounts from the AccountManager", async () => { accountManagerMock.getAccounts.mockReturnValue(["0x1234...", "0x5678..."]) const accounts = ethAccounts(accountManagerMock) @@ -32,7 +33,7 @@ describe("ethAccounts handler", () => { expect(accountManagerMock.getAccounts).toHaveBeenCalled() }) - it("should return an empty array if no accounts are available", () => { + it("should return an empty array if no accounts are available", async () => { accountManagerMock.getAccounts.mockReturnValue([]) const accounts = ethAccounts(accountManagerMock) @@ -44,12 +45,12 @@ describe("ethAccounts handler", () => { describe("ethRequestAccounts handler", () => { let accountManagerMock: jest.Mocked - let userMock: ReturnType + let userMock: jest.Mocked beforeEach(() => { - userMock = fcl.currentUser() + userMock = fcl.currentUser() as jest.Mocked - userMock.snapshot.mockResolvedValue({addr: null}) + userMock.snapshot.mockResolvedValue({addr: null} as unknown as CurrentUser) accountManagerMock = { getAccounts: jest.fn(), diff --git a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-send-transaction.test.ts b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-send-transaction.test.ts new file mode 100644 index 000000000..14ad5ae28 --- /dev/null +++ b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-send-transaction.test.ts @@ -0,0 +1,24 @@ +import {AccountManager} from "../../accounts/account-manager" +import {ethSendTransaction} from "./eth-send-transaction" + +jest.mock("../../accounts/account-manager") + +describe("eth_sendTransaction handler", () => { + test("should call the AccountManager to send a transaction", async () => { + const mockAccountManager = new (AccountManager as any)() + mockAccountManager.sendTransaction.mockResolvedValue("0x123456") + + const params = { + from: "0x1234", + to: "0x5678", + value: "0x100", + } + + const evmTxHash = await ethSendTransaction(mockAccountManager, params) + + expect(mockAccountManager.sendTransaction).toHaveBeenCalled() + expect(mockAccountManager.sendTransaction).toHaveBeenCalledTimes(1) + expect(mockAccountManager.sendTransaction).toHaveBeenCalledWith(params) + expect(evmTxHash).toEqual("0x123456") + }) +}) diff --git a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-send-transaction.ts b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-send-transaction.ts new file mode 100644 index 000000000..b8c015eb4 --- /dev/null +++ b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-send-transaction.ts @@ -0,0 +1,8 @@ +import {AccountManager} from "../../accounts/account-manager" + +export async function ethSendTransaction( + accountManager: AccountManager, + params: any +) { + return await accountManager.sendTransaction(params) +} diff --git a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.test.ts b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.test.ts index 91723755c..ea18f7e6d 100644 --- a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.test.ts +++ b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.test.ts @@ -1,18 +1,23 @@ import {AccountManager} from "../accounts/account-manager" import {Gateway} from "../gateway/gateway" import {RpcProcessor} from "./rpc-processor" +import * as fcl from "@onflow/fcl" jest.mock("../gateway/gateway") jest.mock("../accounts/account-manager") +jest.mock("@onflow/fcl", () => ({ + getChainId: jest.fn(), +})) describe("rpc processor", () => { - test("fallback to gateway", async () => { + test("fallback to gateway mainnet", async () => { const gateway: jest.Mocked = new (Gateway as any)() const accountManager: jest.Mocked = new (AccountManager as any)() const rpcProcessor = new RpcProcessor(gateway, accountManager) jest.mocked(gateway).request.mockResolvedValue("0x0") + jest.mocked(fcl.getChainId).mockResolvedValue("mainnet") const response = await rpcProcessor.handleRequest({ method: "eth_blockNumber", @@ -25,6 +30,31 @@ describe("rpc processor", () => { expect(gateway.request).toHaveBeenCalledWith({ method: "eth_blockNumber", params: [], + chainId: 747, + }) + }) + + test("fallback to gateway testnet", async () => { + const gateway: jest.Mocked = new (Gateway as any)() + const accountManager: jest.Mocked = + new (AccountManager as any)() + const rpcProcessor = new RpcProcessor(gateway, accountManager) + + jest.mocked(gateway).request.mockResolvedValue("0x0") + jest.mocked(fcl.getChainId).mockResolvedValue("testnet") + + const response = await rpcProcessor.handleRequest({ + method: "eth_blockNumber", + params: [], + }) + + expect(response).toEqual("0x0") + expect(gateway.request).toHaveBeenCalled() + expect(gateway.request).toHaveBeenCalledTimes(1) + expect(gateway.request).toHaveBeenCalledWith({ + method: "eth_blockNumber", + params: [], + chainId: 646, }) }) }) diff --git a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts index 0911ab55b..d42bf092f 100644 --- a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts +++ b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts @@ -1,8 +1,10 @@ import {ProviderRequest} from "../types/provider" import {ethAccounts, ethRequestAccounts} from "./handlers/eth-accounts" -import {JsonRpcProvider} from "@walletconnect/jsonrpc-provider" import {Gateway} from "../gateway/gateway" import {AccountManager} from "../accounts/account-manager" +import * as fcl from "@onflow/fcl" +import {FLOW_CHAINS, FlowNetwork} from "../constants" +import {ethSendTransaction} from "./handlers/eth-send-transaction" export class RpcProcessor { constructor( @@ -11,13 +13,25 @@ export class RpcProcessor { ) {} async handleRequest({method, params}: ProviderRequest): Promise { + const flowNetwork = await fcl.getChainId() + if (!(flowNetwork in FLOW_CHAINS)) { + throw new Error(`Unsupported chainId ${flowNetwork}`) + } + const {eip155ChainId} = FLOW_CHAINS[flowNetwork as FlowNetwork] + switch (method) { case "eth_accounts": return ethAccounts(this.accountManager) case "eth_requestAccounts": return ethRequestAccounts(this.accountManager) + case "eth_sendTransaction": + return await ethSendTransaction(this.accountManager, params) default: - return await this.gateway.request({method, params}) + return await this.gateway.request({ + chainId: eip155ChainId, + method, + params, + }) } } } diff --git a/packages/fcl-ethereum-provider/src/types/events.ts b/packages/fcl-ethereum-provider/src/types/events.ts new file mode 100644 index 000000000..51b1fb39d --- /dev/null +++ b/packages/fcl-ethereum-provider/src/types/events.ts @@ -0,0 +1,15 @@ +export type TransactionExecutedEvent = { + hash: string[] + index: string + type: string + payload: string[] + errorCode: string + errorMessage: string + gasConsumed: string + contractAddress: string + logs: string[] + blockHeight: string + returnedData: string[] + precompiledCalls: string[] + stateUpdateChecksum: string +} From 5c9de1205ea7eeff4dbffa437e7abd5159fc02dc Mon Sep 17 00:00:00 2001 From: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> Date: Wed, 29 Jan 2025 15:13:56 -0800 Subject: [PATCH 08/56] Implement FCL Ethereum Provider `accountsChanged` Event (#2091) --- .../src/create-provider.ts | 2 +- .../src/events/event-dispatcher.test.ts | 91 ++++++++++++++++++- .../src/events/event-dispatcher.ts | 9 +- 3 files changed, 97 insertions(+), 5 deletions(-) diff --git a/packages/fcl-ethereum-provider/src/create-provider.ts b/packages/fcl-ethereum-provider/src/create-provider.ts index a440e8a42..505ddbde9 100644 --- a/packages/fcl-ethereum-provider/src/create-provider.ts +++ b/packages/fcl-ethereum-provider/src/create-provider.ts @@ -46,7 +46,7 @@ export function createProvider(config: { ...(config.rpcUrls || {}), }) const rpcProcessor = new RpcProcessor(gateway, accountManager) - const eventProcessor = new EventDispatcher() + const eventProcessor = new EventDispatcher(accountManager) const provider = new FclEthereumProvider(rpcProcessor, eventProcessor) return provider } diff --git a/packages/fcl-ethereum-provider/src/events/event-dispatcher.test.ts b/packages/fcl-ethereum-provider/src/events/event-dispatcher.test.ts index c52bf6fdb..41ad945e8 100644 --- a/packages/fcl-ethereum-provider/src/events/event-dispatcher.test.ts +++ b/packages/fcl-ethereum-provider/src/events/event-dispatcher.test.ts @@ -1,3 +1,90 @@ -test("notifications service", () => { - expect(true).toBe(true) +import {AccountManager} from "../accounts/account-manager" +import {EventDispatcher} from "./event-dispatcher" + +jest.mock("../accounts/account-manager") + +describe("event dispatcher", () => { + test("unsubscribe should remove listener", () => { + const accountManager: jest.Mocked = + new (AccountManager as any)() + + let mockSubscribeCallback: (accounts: string[]) => void + accountManager.subscribe.mockImplementation(cb => { + mockSubscribeCallback = cb + }) + const listener = jest.fn() + + const eventDispatcher = new EventDispatcher(accountManager) + eventDispatcher.on("accountsChanged", listener) + + expect(accountManager.subscribe).toHaveBeenCalled() + expect(accountManager.subscribe).toHaveBeenCalledTimes(1) + expect(accountManager.subscribe).toHaveBeenCalledWith(expect.any(Function)) + + // Simulate account change from account manager + mockSubscribeCallback!(["0x1234"]) + + expect(listener).toHaveBeenCalled() + expect(listener).toHaveBeenCalledTimes(1) + expect(listener).toHaveBeenCalledWith(["0x1234"]) + + eventDispatcher.off("accountsChanged", listener) + + // Simulate account change from account manager + mockSubscribeCallback!(["0x5678"]) + + expect(listener).toHaveBeenCalledTimes(1) + }) + + test("should emit accountsChanged", () => { + const accountManager: jest.Mocked = + new (AccountManager as any)() + + let mockSubscribeCallback: (accounts: string[]) => void + accountManager.subscribe.mockImplementation(cb => { + mockSubscribeCallback = cb + }) + const listener = jest.fn() + + const eventDispatcher = new EventDispatcher(accountManager) + eventDispatcher.on("accountsChanged", listener) + + expect(accountManager.subscribe).toHaveBeenCalled() + expect(accountManager.subscribe).toHaveBeenCalledTimes(1) + expect(accountManager.subscribe).toHaveBeenCalledWith(expect.any(Function)) + + // Simulate account change from account manager + mockSubscribeCallback!(["0x1234"]) + + expect(listener).toHaveBeenCalled() + expect(listener).toHaveBeenCalledTimes(1) + expect(listener).toHaveBeenCalledWith(["0x1234"]) + }) + + test("should emit accountsChanged multiple times", () => { + const accountManager: jest.Mocked = + new (AccountManager as any)() + + let mockSubscribeCallback: (accounts: string[]) => void + accountManager.subscribe.mockImplementation(cb => { + mockSubscribeCallback = cb + }) + const listener = jest.fn() + + const eventDispatcher = new EventDispatcher(accountManager) + eventDispatcher.on("accountsChanged", listener) + + expect(accountManager.subscribe).toHaveBeenCalled() + expect(accountManager.subscribe).toHaveBeenCalledTimes(1) + expect(accountManager.subscribe).toHaveBeenCalledWith(expect.any(Function)) + + // Simulate account change from account manager + mockSubscribeCallback!(["0x1234"]) + mockSubscribeCallback!(["0x5678"]) + + expect(listener).toHaveBeenCalled() + expect(listener).toHaveBeenCalledTimes(2) + expect(listener).toHaveBeenNthCalledWith(1, ["0x1234"]) + expect(listener).toHaveBeenNthCalledWith(2, ["0x5678"]) + }) }) diff --git a/packages/fcl-ethereum-provider/src/events/event-dispatcher.ts b/packages/fcl-ethereum-provider/src/events/event-dispatcher.ts index 0d119c732..019b21e4f 100644 --- a/packages/fcl-ethereum-provider/src/events/event-dispatcher.ts +++ b/packages/fcl-ethereum-provider/src/events/event-dispatcher.ts @@ -1,10 +1,15 @@ +import {AccountManager} from "../accounts/account-manager" import {EventCallback, ProviderEvents} from "../types/provider" import EventEmitter from "events" export class EventDispatcher { private eventEmitter = new EventEmitter() - constructor() {} + constructor(accountManager: AccountManager) { + accountManager.subscribe(accounts => { + this.emit("accountsChanged", accounts) + }) + } // Listen to events on( @@ -23,7 +28,7 @@ export class EventDispatcher { } // Emit events (to be called internally) - protected emit( + private emit( event: E, data: ProviderEvents[E] ) { From a9659aa7babda46ca08375f924accbd6002629ad Mon Sep 17 00:00:00 2001 From: Chase Fleming Date: Wed, 29 Jan 2025 15:37:53 -0800 Subject: [PATCH 09/56] Return unsubscribe from account manager (#2094) Co-authored-by: Chase Fleming <1666730+chasefleming@users.noreply.github.com> --- .../src/accounts/account-manager.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts index 7e8084a3b..4b41d83fb 100644 --- a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts +++ b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts @@ -93,10 +93,9 @@ export class AccountManager { return this.coaAddress ? [this.coaAddress] : [] } - public subscribe(callback: (accounts: string[]) => void) { - this.user.subscribe(async (snapshot: CurrentUser) => { + public subscribe(callback: (accounts: string[]) => void): () => void { + const unsubscribe = this.user.subscribe(async (snapshot: CurrentUser) => { if (!snapshot.addr) { - // user not authenticated => clear out this.lastFlowAddr = null this.coaAddress = null callback(this.getAccounts()) @@ -105,7 +104,9 @@ export class AccountManager { await this.updateCOAAddress() callback(this.getAccounts()) - }) + }) as () => void + + return unsubscribe } async sendTransaction({ From 2fda71b43bf87559b451621140e0b273b42ea8a3 Mon Sep 17 00:00:00 2001 From: Chase Fleming Date: Thu, 30 Jan 2025 13:50:44 -0800 Subject: [PATCH 10/56] Implement `personal_sign` (#2095) * Add sign message * Update packages/fcl-ethereum-provider/src/accounts/account-manager.ts Co-authored-by: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> * Push fix * Remove comment * Update packages/fcl-ethereum-provider/src/rpc/handlers/personal-sign.ts Co-authored-by: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> * Update packages/fcl-ethereum-provider/src/accounts/account-manager.ts Co-authored-by: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> * Update packages/fcl-ethereum-provider/src/rpc/handlers/personal-sign.ts Co-authored-by: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> * Check auth with coa address * Add tests * Fix params and prettier * Run prettier * Use RLP * Change path * hex array * Fix RLP test * Run prettier --------- Co-authored-by: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Co-authored-by: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> --- package-lock.json | 1 + packages/fcl-ethereum-provider/package.json | 1 + .../src/accounts/account-manager.test.ts | 85 +++++++++++++++++++ .../src/accounts/account-manager.ts | 45 +++++++++- .../src/rpc/handlers/personal-sign.ts | 11 +++ .../src/rpc/rpc-processor.ts | 7 ++ .../fcl-ethereum-provider/src/types/eth.ts | 3 + 7 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 packages/fcl-ethereum-provider/src/rpc/handlers/personal-sign.ts create mode 100644 packages/fcl-ethereum-provider/src/types/eth.ts diff --git a/package-lock.json b/package-lock.json index 7fda0fe47..c638df32a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29202,6 +29202,7 @@ "dependencies": { "@babel/runtime": "^7.25.7", "@onflow/fcl": "1.13.4", + "@onflow/rlp": "^1.2.3", "@walletconnect/jsonrpc-http-connection": "^1.0.8", "@walletconnect/jsonrpc-provider": "^1.0.14" }, diff --git a/packages/fcl-ethereum-provider/package.json b/packages/fcl-ethereum-provider/package.json index a25e0e6c3..7986990a3 100644 --- a/packages/fcl-ethereum-provider/package.json +++ b/packages/fcl-ethereum-provider/package.json @@ -38,6 +38,7 @@ "dependencies": { "@babel/runtime": "^7.25.7", "@onflow/fcl": "1.13.4", + "@onflow/rlp": "^1.2.3", "@walletconnect/jsonrpc-http-connection": "^1.0.8", "@walletconnect/jsonrpc-provider": "^1.0.14" } diff --git a/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts b/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts index 7186b0ddb..ab21c7eaa 100644 --- a/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts +++ b/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts @@ -1,6 +1,7 @@ import {AccountManager} from "./account-manager" import {mockUser} from "../__mocks__/fcl" import * as fcl from "@onflow/fcl" +import * as rlp from "@onflow/rlp" import {CurrentUser} from "@onflow/typedefs" jest.mock("@onflow/fcl", () => { @@ -14,6 +15,11 @@ jest.mock("@onflow/fcl", () => { } }) +jest.mock("@onflow/rlp", () => ({ + encode: jest.fn(), + Buffer: jest.requireActual("@onflow/rlp").Buffer, +})) + const mockFcl = jest.mocked(fcl) const mockQuery = jest.mocked(fcl.query) @@ -333,3 +339,82 @@ describe("send transaction", () => { ) }) }) + +describe("signMessage", () => { + let accountManager: AccountManager + let user: jest.Mocked + + beforeEach(() => { + user = mockUser() + accountManager = new AccountManager(user) + }) + + afterEach(() => { + jest.clearAllMocks() + }) + + it("should throw an error if the COA address is not available", async () => { + accountManager["coaAddress"] = null + + await expect( + accountManager.signMessage("Test message", "0x1234") + ).rejects.toThrow( + "COA address is not available. User might not be authenticated." + ) + }) + + it("should throw an error if the signer address does not match the COA address", async () => { + accountManager["coaAddress"] = "0xCOA1" + + await expect( + accountManager.signMessage("Test message", "0xDIFFERENT") + ).rejects.toThrow("Signer address does not match authenticated COA address") + }) + + it("should successfully sign a message and return an RLP-encoded proof", async () => { + accountManager["coaAddress"] = "0xCOA1" + const mockSignature = "0xabcdef1234567890" + const mockRlpEncoded = "f86a808683abcdef682f73746f726167652f65766d" + + user.signUserMessage = jest + .fn() + .mockResolvedValue([{addr: "0xCOA1", keyId: 0, signature: mockSignature}]) + + jest.mocked(rlp.encode).mockReturnValue(Buffer.from(mockRlpEncoded, "hex")) + + const proof = await accountManager.signMessage("Test message", "0xCOA1") + + expect(proof).toBe(`0x${mockRlpEncoded}`) + + expect(user.signUserMessage).toHaveBeenCalledWith("Test message") + + expect(rlp.encode).toHaveBeenCalledWith([ + [0], + expect.any(Buffer), + "/public/evm", + [mockSignature], + ]) + }) + + it("should throw an error if signUserMessage returns an empty array", async () => { + accountManager["coaAddress"] = "0xCOA1" + + user.signUserMessage = jest.fn().mockResolvedValue([]) + + await expect( + accountManager.signMessage("Test message", "0xCOA1") + ).rejects.toThrow("Failed to sign message") + }) + + it("should throw an error if signUserMessage fails", async () => { + accountManager["coaAddress"] = "0xCOA1" + + user.signUserMessage = jest + .fn() + .mockRejectedValue(new Error("Signing failed")) + + await expect( + accountManager.signMessage("Test message", "0xCOA1") + ).rejects.toThrow("Signing failed") + }) +}) diff --git a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts index 4b41d83fb..a1fcbaf76 100644 --- a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts +++ b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts @@ -1,5 +1,6 @@ import * as fcl from "@onflow/fcl" -import {CurrentUser} from "@onflow/typedefs" +import * as rlp from "@onflow/rlp" +import {CompositeSignature, CurrentUser} from "@onflow/typedefs" import { ContractType, EVENT_IDENTIFIERS, @@ -9,6 +10,7 @@ import { FlowNetwork, } from "../constants" import {TransactionExecutedEvent} from "../types/events" +import {EthSignatureResponse} from "../types/eth" export class AccountManager { private user: typeof fcl.currentUser @@ -194,4 +196,45 @@ export class AccountManager { return evmTxHash } + + public async signMessage( + message: string, + from: string + ): Promise { + if (!this.coaAddress) { + throw new Error( + "COA address is not available. User might not be authenticated." + ) + } + + if (from.toLowerCase() !== this.coaAddress.toLowerCase()) { + throw new Error("Signer address does not match authenticated COA address") + } + + try { + const response: CompositeSignature[] = + await this.user.signUserMessage(message) + + if (!response || response.length === 0) { + throw new Error("Failed to sign message") + } + + const keyIndices = response.map(sig => sig.keyId) + const signatures = response.map(sig => sig.signature) + + const addressHexArray = Buffer.from(from.replace(/^0x/, ""), "hex") + + const capabilityPath = "/public/evm" + + const rlpEncodedProof = rlp + .encode([keyIndices, addressHexArray, capabilityPath, signatures]) + .toString("hex") + + return rlpEncodedProof.startsWith("0x") + ? rlpEncodedProof + : `0x${rlpEncodedProof}` // Return 0x-prefix for Ethereum compatibility + } catch (error) { + throw error + } + } } diff --git a/packages/fcl-ethereum-provider/src/rpc/handlers/personal-sign.ts b/packages/fcl-ethereum-provider/src/rpc/handlers/personal-sign.ts new file mode 100644 index 000000000..4b2d786c3 --- /dev/null +++ b/packages/fcl-ethereum-provider/src/rpc/handlers/personal-sign.ts @@ -0,0 +1,11 @@ +import {AccountManager} from "../../accounts/account-manager" +import {PersonalSignParams} from "../../types/eth" + +export async function personalSign( + accountManager: AccountManager, + params: PersonalSignParams +) { + const [message, from] = params + + return await accountManager.signMessage(message, from) +} diff --git a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts index d42bf092f..b6fa22db1 100644 --- a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts +++ b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts @@ -5,6 +5,8 @@ import {AccountManager} from "../accounts/account-manager" import * as fcl from "@onflow/fcl" import {FLOW_CHAINS, FlowNetwork} from "../constants" import {ethSendTransaction} from "./handlers/eth-send-transaction" +import {personalSign} from "./handlers/personal-sign" +import {PersonalSignParams} from "../types/eth" export class RpcProcessor { constructor( @@ -26,6 +28,11 @@ export class RpcProcessor { return ethRequestAccounts(this.accountManager) case "eth_sendTransaction": return await ethSendTransaction(this.accountManager, params) + case "personal_sign": + return await personalSign( + this.accountManager, + params as PersonalSignParams + ) default: return await this.gateway.request({ chainId: eip155ChainId, diff --git a/packages/fcl-ethereum-provider/src/types/eth.ts b/packages/fcl-ethereum-provider/src/types/eth.ts new file mode 100644 index 000000000..4cd8076db --- /dev/null +++ b/packages/fcl-ethereum-provider/src/types/eth.ts @@ -0,0 +1,3 @@ +export type EthSignatureResponse = string + +export type PersonalSignParams = [string, string] From 167277eece52d4a691eccb03a75699dc4c04c09f Mon Sep 17 00:00:00 2001 From: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> Date: Fri, 31 Jan 2025 15:42:16 -0800 Subject: [PATCH 11/56] Implement Reactive State Management using Observabales (#2098) --- .../src/__mocks__/fcl.ts | 76 ++++- .../src/accounts/account-manager.test.ts | 190 +++++------ .../src/accounts/account-manager.ts | 148 +++++---- .../src/create-provider.ts | 2 + .../src/events/event-dispatcher.test.ts | 27 +- .../src/events/event-dispatcher.ts | 57 +++- .../src/rpc/handlers/eth-accounts.test.ts | 14 +- .../src/rpc/handlers/eth-accounts.ts | 10 +- .../src/util/observable.ts | 309 ++++++++++++++++++ 9 files changed, 620 insertions(+), 213 deletions(-) create mode 100644 packages/fcl-ethereum-provider/src/util/observable.ts diff --git a/packages/fcl-ethereum-provider/src/__mocks__/fcl.ts b/packages/fcl-ethereum-provider/src/__mocks__/fcl.ts index ad62d70b7..ac83de332 100644 --- a/packages/fcl-ethereum-provider/src/__mocks__/fcl.ts +++ b/packages/fcl-ethereum-provider/src/__mocks__/fcl.ts @@ -1,20 +1,90 @@ import * as fcl from "@onflow/fcl" +import {CurrentUser} from "@onflow/typedefs" -export function mockUser(): jest.Mocked { +export function mockUser(initialValue?: CurrentUser | null) { + if (!initialValue) { + initialValue = { + loggedIn: false, + } as CurrentUser + } + let value: CurrentUser = initialValue + let subscribers: ((cfg: CurrentUser, err: Error | null) => void)[] = [] const currentUser = { authenticate: jest.fn(), unauthenticate: jest.fn(), authorization: jest.fn(), signUserMessage: jest.fn(), - subscribe: jest.fn(), + subscribe: jest.fn().mockImplementation(cb => { + cb(value) + subscribers.push(cb) + return () => { + subscribers = subscribers.filter(s => s !== cb) + } + }), snapshot: jest.fn(), resolveArgument: jest.fn(), } - return Object.assign( + const mock: jest.Mocked = Object.assign( () => { return {...currentUser} }, {...currentUser} ) + + return { + mock, + set: async (cfg: CurrentUser) => { + value = cfg + subscribers.forEach(s => s(cfg, null)) + await new Promise(resolve => setTimeout(resolve, 0)) + }, + } +} + +export function mockConfig( + { + initialValue, + }: { + initialValue: Record | null + } = {initialValue: null} +) { + let value = initialValue + let subscribers: ((cfg: Record, err: Error | null) => void)[] = + [] + + const config = { + put: jest.fn(), + get: jest.fn(), + all: jest.fn(), + first: jest.fn(), + update: jest.fn(), + delete: jest.fn(), + where: jest.fn(), + subscribe: jest.fn().mockImplementation(cb => { + cb(value, null) + subscribers.push(cb) + return () => { + subscribers = subscribers.filter(s => s !== cb) + } + }), + overload: jest.fn(), + load: jest.fn(), + } + + const cfg: jest.Mocked = Object.assign( + () => { + return {...config} + }, + {...config} + ) + + return { + mock: cfg, + set: async (cfg: Record) => { + value = cfg + subscribers.forEach(s => s(cfg, null)) + await new Promise(resolve => setTimeout(resolve, 0)) + }, + } } diff --git a/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts b/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts index ab21c7eaa..42b87a035 100644 --- a/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts +++ b/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts @@ -24,85 +24,66 @@ const mockFcl = jest.mocked(fcl) const mockQuery = jest.mocked(fcl.query) describe("AccountManager", () => { - let accountManager: AccountManager - let user: jest.Mocked - beforeEach(() => { - user = mockUser() - accountManager = new AccountManager(user) - }) - - afterEach(() => { jest.clearAllMocks() }) - it("should initialize with null COA address", () => { - expect(accountManager.getCOAAddress()).toBeNull() - expect(accountManager.getAccounts()).toEqual([]) + it("should initialize with null COA address", async () => { + const user = mockUser() + const accountManager = new AccountManager(user.mock) + expect(await accountManager.getCOAAddress()).toBeNull() + expect(await accountManager.getAccounts()).toEqual([]) }) it("should reset state when the user is not logged in", async () => { - user.snapshot.mockResolvedValueOnce({addr: undefined} as CurrentUser) + const user = mockUser() - await accountManager.updateCOAAddress() + const accountManager = new AccountManager(user.mock) - expect(accountManager.getCOAAddress()).toBeNull() - expect(accountManager.getAccounts()).toEqual([]) + expect(await accountManager.getCOAAddress()).toBeNull() + expect(await accountManager.getAccounts()).toEqual([]) }) it("should fetch and update COA address when user logs in", async () => { - user.snapshot.mockResolvedValue({addr: "0x1"} as CurrentUser) + const user = mockUser() mockQuery.mockResolvedValue("0x123") - await accountManager.updateCOAAddress() + const accountManager = new AccountManager(user.mock) + + expect(await accountManager.getCOAAddress()).toBe(null) - expect(accountManager.getCOAAddress()).toBe("0x123") - expect(accountManager.getAccounts()).toEqual(["0x123"]) + user.set!({addr: "0x1"} as CurrentUser) + + expect(await accountManager.getCOAAddress()).toBe("0x123") + expect(await accountManager.getAccounts()).toEqual(["0x123"]) expect(fcl.query).toHaveBeenCalledWith({ cadence: expect.any(String), args: expect.any(Function), }) }) - it("should not update COA address if user has not changed and force is false", async () => { - user.snapshot.mockResolvedValue({addr: "0x1"} as CurrentUser) + it("should not update COA address if user has not changed", async () => { + const user = mockUser() mockQuery.mockResolvedValue("0x123") - user.subscribe.mockImplementation(fn => { - fn({addr: "0x1"}) - return () => {} - }) - await accountManager.updateCOAAddress() - expect(accountManager.getCOAAddress()).toBe("0x123") - expect(fcl.query).toHaveBeenCalledTimes(1) + const accountManager = new AccountManager(user.mock) - // Re-run without changing the address - await accountManager.updateCOAAddress() - expect(accountManager.getCOAAddress()).toBe("0x123") - expect(fcl.query).toHaveBeenCalledTimes(1) // Should not have fetched again - }) + user.set!({addr: "0x1"} as CurrentUser) - it("should force update COA address even if user has not changed", async () => { - user.snapshot.mockResolvedValue({addr: "0x1"} as CurrentUser) - mockQuery.mockResolvedValue("0x123") - user.subscribe.mockImplementation(fn => { - fn({addr: "0x1"}) - return () => {} - }) + await new Promise(setImmediate) - await accountManager.updateCOAAddress() + expect(await accountManager.getCOAAddress()).toBe("0x123") expect(fcl.query).toHaveBeenCalledTimes(1) - // Force update - await accountManager.updateCOAAddress(true) - expect(fcl.query).toHaveBeenCalledTimes(2) + user.set!({addr: "0x1"} as CurrentUser) + + expect(await accountManager.getCOAAddress()).toBe("0x123") + expect(fcl.query).toHaveBeenCalledTimes(1) // Should not have fetched again }) it("should not update COA address if fetch is outdated when user changes", async () => { - // Simulates the user address changing from 0x1 to 0x2 - user.snapshot - .mockResolvedValueOnce({addr: "0x1"} as CurrentUser) // for 1st call - .mockResolvedValueOnce({addr: "0x2"} as CurrentUser) // for 2nd call + const user = mockUser() + mockQuery.mockResolvedValue("0x123") mockQuery // 1st fetch: delayed @@ -112,88 +93,85 @@ describe("AccountManager", () => { // 2nd fetch: immediate .mockResolvedValueOnce("0x456") - const updatePromise1 = accountManager.updateCOAAddress() - const updatePromise2 = accountManager.updateCOAAddress() - await Promise.all([updatePromise1, updatePromise2]) + const accountManager = new AccountManager(user.mock) + + await user.set!({addr: "0x1"} as CurrentUser) + await user.set!({addr: "0x2"} as CurrentUser) // The second fetch (for address 0x2) is the latest, so "0x456" - expect(accountManager.getCOAAddress()).toBe("0x456") + expect(await accountManager.getCOAAddress()).toBe("0x456") }) - it("should clear COA address if fetch fails and is the latest", async () => { - user.snapshot.mockResolvedValueOnce({addr: "0x1"} as CurrentUser) + it("should throw if COA address fetch fails", async () => { + const user = mockUser() mockQuery.mockRejectedValueOnce(new Error("Fetch failed")) - await expect(accountManager.updateCOAAddress()).rejects.toThrow( - "Fetch failed" - ) + const accountManager = new AccountManager(user.mock) - expect(await accountManager.getCOAAddress()).toBeNull() + await user.set!({addr: "0x1"} as CurrentUser) + + await expect(accountManager.getCOAAddress()).rejects.toThrow("Fetch failed") }) it("should handle user changes correctly", async () => { - user.snapshot - .mockResolvedValueOnce({addr: "0x1"} as CurrentUser) - .mockResolvedValueOnce({addr: "0x2"} as CurrentUser) + const user = mockUser() mockQuery .mockResolvedValueOnce("0x123") // for user 0x1 .mockResolvedValueOnce("0x456") // for user 0x2 - await accountManager.updateCOAAddress() - expect(accountManager.getCOAAddress()).toBe("0x123") + const accountManager = new AccountManager(user.mock) + + await user.set({addr: "0x1"} as CurrentUser) + expect(await accountManager.getCOAAddress()).toBe("0x123") - await accountManager.updateCOAAddress() - expect(accountManager.getCOAAddress()).toBe("0x456") + await user.set({addr: "0x2"} as CurrentUser) + + await new Promise(setImmediate) + expect(await accountManager.getCOAAddress()).toBe("0x456") }) it("should call the callback with updated accounts in subscribe", async () => { - user.snapshot.mockResolvedValue({addr: "0x1"} as CurrentUser) mockQuery.mockResolvedValue("0x123") - const callback = jest.fn() - user.subscribe.mockImplementation(fn => { - fn({addr: "0x1"}) - return () => {} - }) + const user = mockUser() - mockQuery.mockResolvedValueOnce("0x123") + const accountManager = new AccountManager(user.mock) + const callback = jest.fn() accountManager.subscribe(callback) + user.set({addr: "0x1"} as CurrentUser) + await new Promise(setImmediate) expect(callback).toHaveBeenCalledWith(["0x123"]) }) - it("should reset accounts in subscribe if user is not authenticated", () => { + it("should reset accounts in subscribe if user is not authenticated", async () => { mockQuery.mockResolvedValue("0x123") - user.snapshot.mockResolvedValue({addr: undefined} as CurrentUser) + const user = mockUser() const callback = jest.fn() - user.subscribe.mockImplementation(fn => { - fn({addr: null}) - return () => {} - }) + const accountManager = new AccountManager(user.mock) accountManager.subscribe(callback) + await new Promise(setImmediate) + expect(callback).toHaveBeenCalledWith([]) }) it("should call the callback when COA address is updated", async () => { const callback = jest.fn() - user.snapshot.mockResolvedValueOnce({addr: "0x1"} as CurrentUser) - - user.subscribe.mockImplementation(fn => { - fn({addr: "0x1"} as CurrentUser) - return () => {} - }) + const user = mockUser({addr: "0x1"} as CurrentUser) mockQuery.mockResolvedValueOnce("0x123") + const accountManager = new AccountManager(user.mock) + accountManager.subscribe(callback) await new Promise(setImmediate) @@ -201,16 +179,19 @@ describe("AccountManager", () => { expect(callback).toHaveBeenCalledWith(["0x123"]) }) - it("should return an empty array when COA address is null", () => { - expect(accountManager.getAccounts()).toEqual([]) + it("should return an empty array when COA address is null", async () => { + const {mock: user} = mockUser() + const accountManager = new AccountManager(user) + expect(await accountManager.getAccounts()).toEqual([]) }) it("should return COA address array when available", async () => { - user.snapshot.mockResolvedValueOnce({addr: "0x1"} as CurrentUser) mockQuery.mockResolvedValueOnce("0x123") + const {mock: user} = mockUser({addr: "0x1"} as CurrentUser) + + const accountManager = new AccountManager(user) - await accountManager.updateCOAAddress() - expect(accountManager.getAccounts()).toEqual(["0x123"]) + expect(await accountManager.getAccounts()).toEqual(["0x123"]) }) }) @@ -220,7 +201,7 @@ describe("send transaction", () => { }) test("send transaction mainnet", async () => { - const user = mockUser() + const user = mockUser().mock const accountManager = new AccountManager(user) const mockTxResult = { @@ -267,7 +248,7 @@ describe("send transaction", () => { test("send transaction testnet", async () => { const user = mockUser() - const accountManager = new AccountManager(user) + const accountManager = new AccountManager(user.mock) const mockTxResult = { onceExecuted: jest.fn().mockResolvedValue({ @@ -302,7 +283,7 @@ describe("send transaction", () => { expect(mockFcl.mutate.mock.calls[0][0]).toMatchObject({ cadence: expect.any(String), args: expect.any(Function), - authz: user, + authz: user.mock, limit: 9999, }) @@ -313,7 +294,7 @@ describe("send transaction", () => { test("throws error if no executed event not found", async () => { const user = mockUser() - const accountManager = new AccountManager(user) + const accountManager = new AccountManager(user.mock) const mockTxResult = { onceExecuted: jest.fn().mockResolvedValue({ @@ -342,18 +323,18 @@ describe("send transaction", () => { describe("signMessage", () => { let accountManager: AccountManager - let user: jest.Mocked + let user: ReturnType["mock"] + let updateUser: ReturnType["set"] beforeEach(() => { - user = mockUser() - accountManager = new AccountManager(user) - }) - - afterEach(() => { jest.clearAllMocks() + ;({mock: user, set: updateUser} = mockUser({addr: "0x123"} as CurrentUser)) + jest.mocked(fcl.query).mockResolvedValue("0xCOA1") + accountManager = new AccountManager(user) }) it("should throw an error if the COA address is not available", async () => { + await updateUser({addr: undefined} as CurrentUser) accountManager["coaAddress"] = null await expect( @@ -364,21 +345,18 @@ describe("signMessage", () => { }) it("should throw an error if the signer address does not match the COA address", async () => { - accountManager["coaAddress"] = "0xCOA1" - await expect( accountManager.signMessage("Test message", "0xDIFFERENT") ).rejects.toThrow("Signer address does not match authenticated COA address") }) it("should successfully sign a message and return an RLP-encoded proof", async () => { - accountManager["coaAddress"] = "0xCOA1" const mockSignature = "0xabcdef1234567890" const mockRlpEncoded = "f86a808683abcdef682f73746f726167652f65766d" - user.signUserMessage = jest - .fn() - .mockResolvedValue([{addr: "0xCOA1", keyId: 0, signature: mockSignature}]) + user.signUserMessage.mockResolvedValue([ + {addr: "0xCOA1", keyId: 0, signature: mockSignature} as any, + ]) jest.mocked(rlp.encode).mockReturnValue(Buffer.from(mockRlpEncoded, "hex")) @@ -407,8 +385,6 @@ describe("signMessage", () => { }) it("should throw an error if signUserMessage fails", async () => { - accountManager["coaAddress"] = "0xCOA1" - user.signUserMessage = jest .fn() .mockRejectedValue(new Error("Signing failed")) diff --git a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts index a1fcbaf76..154e1dea1 100644 --- a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts +++ b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts @@ -10,22 +10,76 @@ import { FlowNetwork, } from "../constants" import {TransactionExecutedEvent} from "../types/events" +import { + BehaviorSubject, + concat, + distinctUntilChanged, + filter, + firstValueFrom, + from, + map, + Observable, + of, + Subscription, + switchMap, +} from "../util/observable" import {EthSignatureResponse} from "../types/eth" export class AccountManager { - private user: typeof fcl.currentUser - - // For race-condition checks: - private currentFetchId = 0 - - // Track the last Flow address we fetched for - private lastFlowAddr: string | null = null - - // The COA address (or null if none/not fetched) - private coaAddress: string | null = null + private $addressStore = new BehaviorSubject<{ + isLoading: boolean + address: string | null + error: Error | null + }>({ + isLoading: true, + address: null, + error: null, + }) + + constructor(private user: typeof fcl.currentUser) { + // Create an observable from the user + const $user = new Observable(subscriber => { + return this.user.subscribe((currentUser: CurrentUser, error?: Error) => { + if (error) { + subscriber.error?.(error) + } else { + subscriber.next(currentUser) + } + }) as Subscription + }) - constructor(user: typeof fcl.currentUser) { - this.user = user + // Bind the address store to the user observable + $user + .pipe( + map(snapshot => snapshot.addr || null), + distinctUntilChanged(), + switchMap(addr => + concat( + of({isLoading: true} as { + isLoading: boolean + address: string | null + error: Error | null + }), + from( + (async () => { + try { + if (!addr) { + return {isLoading: false, address: null, error: null} + } + return { + isLoading: false, + address: await this.fetchCOAFromFlowAddress(addr), + error: null, + } + } catch (error: any) { + return {isLoading: false, address: null, error} + } + })() + ) + ) + ) + ) + .subscribe(this.$addressStore) } private async fetchCOAFromFlowAddress(flowAddr: string): Promise { @@ -55,60 +109,27 @@ export class AccountManager { return response as string } - public async updateCOAAddress(force = false): Promise { - const snapshot = await this.user.snapshot() - const currentFlowAddr = snapshot?.addr - - // If user not logged in, reset everything - if (!currentFlowAddr) { - this.lastFlowAddr = null - this.coaAddress = null - return - } - - const userChanged = this.lastFlowAddr !== currentFlowAddr - if (force || userChanged) { - this.lastFlowAddr = currentFlowAddr - const fetchId = ++this.currentFetchId - - try { - const address = await this.fetchCOAFromFlowAddress(currentFlowAddr) - // Only update if this fetch is still the latest - if (fetchId === this.currentFetchId) { - this.coaAddress = address - } - } catch (error) { - // If this fetch is the latest, clear - if (fetchId === this.currentFetchId) { - this.coaAddress = null - } - throw error - } + public async getCOAAddress(): Promise { + const {address, error} = await firstValueFrom( + this.$addressStore.pipe(filter(x => !x.isLoading)) + ) + if (error) { + throw error } + return address } - public getCOAAddress(): string | null { - return this.coaAddress + public async getAccounts(): Promise { + const coaAddress = await this.getCOAAddress() + return coaAddress ? [coaAddress] : [] } - public getAccounts(): string[] { - return this.coaAddress ? [this.coaAddress] : [] - } - - public subscribe(callback: (accounts: string[]) => void): () => void { - const unsubscribe = this.user.subscribe(async (snapshot: CurrentUser) => { - if (!snapshot.addr) { - this.lastFlowAddr = null - this.coaAddress = null - callback(this.getAccounts()) - return - } - - await this.updateCOAAddress() - callback(this.getAccounts()) - }) as () => void - - return unsubscribe + public subscribe(callback: (accounts: string[]) => void): Subscription { + return this.$addressStore + .pipe(filter(x => !x.isLoading && !x.error)) + .subscribe(({address}) => { + callback(address ? [address] : []) + }) } async sendTransaction({ @@ -201,13 +222,14 @@ export class AccountManager { message: string, from: string ): Promise { - if (!this.coaAddress) { + const coaAddress = await this.getCOAAddress() + if (!coaAddress) { throw new Error( "COA address is not available. User might not be authenticated." ) } - if (from.toLowerCase() !== this.coaAddress.toLowerCase()) { + if (from.toLowerCase() !== coaAddress.toLowerCase()) { throw new Error("Signer address does not match authenticated COA address") } diff --git a/packages/fcl-ethereum-provider/src/create-provider.ts b/packages/fcl-ethereum-provider/src/create-provider.ts index 505ddbde9..a20325e68 100644 --- a/packages/fcl-ethereum-provider/src/create-provider.ts +++ b/packages/fcl-ethereum-provider/src/create-provider.ts @@ -29,6 +29,7 @@ import {Gateway} from "./gateway/gateway" */ export function createProvider(config: { user: typeof fcl.currentUser + config: typeof fcl.config service?: Service rpcUrls?: {[chainId: string]: number} }): Eip1193Provider { @@ -48,5 +49,6 @@ export function createProvider(config: { const rpcProcessor = new RpcProcessor(gateway, accountManager) const eventProcessor = new EventDispatcher(accountManager) const provider = new FclEthereumProvider(rpcProcessor, eventProcessor) + return provider } diff --git a/packages/fcl-ethereum-provider/src/events/event-dispatcher.test.ts b/packages/fcl-ethereum-provider/src/events/event-dispatcher.test.ts index 41ad945e8..b86472c5e 100644 --- a/packages/fcl-ethereum-provider/src/events/event-dispatcher.test.ts +++ b/packages/fcl-ethereum-provider/src/events/event-dispatcher.test.ts @@ -8,9 +8,12 @@ describe("event dispatcher", () => { const accountManager: jest.Mocked = new (AccountManager as any)() - let mockSubscribeCallback: (accounts: string[]) => void + let subs: ((accounts: string[]) => void)[] = [] accountManager.subscribe.mockImplementation(cb => { - mockSubscribeCallback = cb + subs.push(cb) + return () => { + subs = subs.filter(sub => sub !== cb) + } }) const listener = jest.fn() @@ -22,7 +25,7 @@ describe("event dispatcher", () => { expect(accountManager.subscribe).toHaveBeenCalledWith(expect.any(Function)) // Simulate account change from account manager - mockSubscribeCallback!(["0x1234"]) + subs.forEach(sub => sub(["0x1234"])) expect(listener).toHaveBeenCalled() expect(listener).toHaveBeenCalledTimes(1) @@ -31,7 +34,7 @@ describe("event dispatcher", () => { eventDispatcher.off("accountsChanged", listener) // Simulate account change from account manager - mockSubscribeCallback!(["0x5678"]) + subs.forEach(sub => sub(["0x5678"])) expect(listener).toHaveBeenCalledTimes(1) }) @@ -40,9 +43,10 @@ describe("event dispatcher", () => { const accountManager: jest.Mocked = new (AccountManager as any)() - let mockSubscribeCallback: (accounts: string[]) => void + let mockMgrSubCb: (accounts: string[]) => void accountManager.subscribe.mockImplementation(cb => { - mockSubscribeCallback = cb + mockMgrSubCb = cb + return () => {} }) const listener = jest.fn() @@ -54,7 +58,7 @@ describe("event dispatcher", () => { expect(accountManager.subscribe).toHaveBeenCalledWith(expect.any(Function)) // Simulate account change from account manager - mockSubscribeCallback!(["0x1234"]) + mockMgrSubCb!(["0x1234"]) expect(listener).toHaveBeenCalled() expect(listener).toHaveBeenCalledTimes(1) @@ -65,9 +69,10 @@ describe("event dispatcher", () => { const accountManager: jest.Mocked = new (AccountManager as any)() - let mockSubscribeCallback: (accounts: string[]) => void + let mockMgrSubCb: (accounts: string[]) => void accountManager.subscribe.mockImplementation(cb => { - mockSubscribeCallback = cb + mockMgrSubCb = cb + return () => {} }) const listener = jest.fn() @@ -79,8 +84,8 @@ describe("event dispatcher", () => { expect(accountManager.subscribe).toHaveBeenCalledWith(expect.any(Function)) // Simulate account change from account manager - mockSubscribeCallback!(["0x1234"]) - mockSubscribeCallback!(["0x5678"]) + mockMgrSubCb!(["0x1234"]) + mockMgrSubCb!(["0x5678"]) expect(listener).toHaveBeenCalled() expect(listener).toHaveBeenCalledTimes(2) diff --git a/packages/fcl-ethereum-provider/src/events/event-dispatcher.ts b/packages/fcl-ethereum-provider/src/events/event-dispatcher.ts index 019b21e4f..18642daf8 100644 --- a/packages/fcl-ethereum-provider/src/events/event-dispatcher.ts +++ b/packages/fcl-ethereum-provider/src/events/event-dispatcher.ts @@ -1,14 +1,45 @@ -import {AccountManager} from "../accounts/account-manager" import {EventCallback, ProviderEvents} from "../types/provider" -import EventEmitter from "events" +import {AccountManager} from "../accounts/account-manager" +import {Observable, Subscription} from "../util/observable" export class EventDispatcher { - private eventEmitter = new EventEmitter() + private $emitters: { + [E in keyof ProviderEvents]: Observable + } + private subscriptions: { + [E in keyof ProviderEvents]: Map< + EventCallback, + Subscription + > + } constructor(accountManager: AccountManager) { - accountManager.subscribe(accounts => { - this.emit("accountsChanged", accounts) - }) + this.$emitters = { + accountsChanged: new Observable(subscriber => { + return accountManager.subscribe(accounts => { + subscriber.next(accounts) + }) + }), + chainChanged: new Observable(subscriber => { + subscriber.complete?.() + return () => {} + }), + connect: new Observable(subscriber => { + subscriber.complete?.() + return () => {} + }), + disconnect: new Observable(subscriber => { + subscriber.complete?.() + return () => {} + }), + } + + this.subscriptions = { + accountsChanged: new Map(), + chainChanged: new Map(), + connect: new Map(), + disconnect: new Map(), + } } // Listen to events @@ -16,7 +47,8 @@ export class EventDispatcher { event: E, listener: EventCallback ): void { - this.eventEmitter.on(event, listener) + const unsub = this.$emitters[event].subscribe(listener) + this.subscriptions[event].set(listener, unsub) } // Remove event listeners @@ -24,14 +56,7 @@ export class EventDispatcher { event: E, listener: EventCallback ): void { - this.eventEmitter.off(event, listener) - } - - // Emit events (to be called internally) - private emit( - event: E, - data: ProviderEvents[E] - ) { - this.eventEmitter.emit(event, data) + this.subscriptions[event].get(listener)?.() + this.subscriptions[event].delete(listener) } } diff --git a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.test.ts b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.test.ts index 2df9449e1..181916eef 100644 --- a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.test.ts +++ b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.test.ts @@ -25,18 +25,18 @@ describe("ethAccounts handler", () => { }) it("should return accounts from the AccountManager", async () => { - accountManagerMock.getAccounts.mockReturnValue(["0x1234...", "0x5678..."]) + accountManagerMock.getAccounts.mockResolvedValue(["0x1234...", "0x5678..."]) - const accounts = ethAccounts(accountManagerMock) + const accounts = await ethAccounts(accountManagerMock) expect(accounts).toEqual(["0x1234...", "0x5678..."]) expect(accountManagerMock.getAccounts).toHaveBeenCalled() }) it("should return an empty array if no accounts are available", async () => { - accountManagerMock.getAccounts.mockReturnValue([]) + accountManagerMock.getAccounts.mockResolvedValue([]) - const accounts = ethAccounts(accountManagerMock) + const accounts = await ethAccounts(accountManagerMock) expect(accounts).toEqual([]) expect(accountManagerMock.getAccounts).toHaveBeenCalled() @@ -60,23 +60,21 @@ describe("ethRequestAccounts handler", () => { }) it("should call authenticate, updateCOAAddress, and return the manager's accounts", async () => { - accountManagerMock.getAccounts.mockReturnValue(["0x1234..."]) + accountManagerMock.getAccounts.mockResolvedValue(["0x1234..."]) const accounts = await ethRequestAccounts(accountManagerMock) expect(userMock.authenticate).toHaveBeenCalled() - expect(accountManagerMock.updateCOAAddress).toHaveBeenCalled() expect(accountManagerMock.getAccounts).toHaveBeenCalled() expect(accounts).toEqual(["0x1234..."]) }) it("should handle empty accounts scenario", async () => { - accountManagerMock.getAccounts.mockReturnValue([]) + accountManagerMock.getAccounts.mockResolvedValue([]) const accounts = await ethRequestAccounts(accountManagerMock) expect(userMock.authenticate).toHaveBeenCalled() - expect(accountManagerMock.updateCOAAddress).toHaveBeenCalled() expect(accountManagerMock.getAccounts).toHaveBeenCalled() expect(accounts).toEqual([]) }) diff --git a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.ts b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.ts index 6aa1e5696..9ed4b2a9f 100644 --- a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.ts +++ b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.ts @@ -1,14 +1,14 @@ import * as fcl from "@onflow/fcl" import {AccountManager} from "../../accounts/account-manager" -export function ethAccounts(accountManager: AccountManager): string[] { - return accountManager.getAccounts() +export async function ethAccounts( + accountManager: AccountManager +): Promise { + return await accountManager.getAccounts() } export async function ethRequestAccounts(accountManager: AccountManager) { await fcl.currentUser().authenticate() - await accountManager.updateCOAAddress() - - return accountManager.getAccounts() + return await accountManager.getAccounts() } diff --git a/packages/fcl-ethereum-provider/src/util/observable.ts b/packages/fcl-ethereum-provider/src/util/observable.ts new file mode 100644 index 000000000..f34421b9d --- /dev/null +++ b/packages/fcl-ethereum-provider/src/util/observable.ts @@ -0,0 +1,309 @@ +/******************************* + * Core Observable/Types + *******************************/ + +export class Observable { + private _subscribe: (subscriber: Observer) => Subscription + + constructor(subscribe: (subscriber: Observer) => Subscription) { + this._subscribe = subscribe + } + + subscribe(observerOrNext: ObserverOrNext): Subscription { + const observer = normalizeObserver(observerOrNext) + return this._subscribe(observer) + } + + /** + * Pipe overloads — remove error type parameter + */ + pipe(op1: (source: Observable) => Observable): Observable + pipe( + op1: (source: Observable) => Observable, + op2: (source: Observable) => Observable + ): Observable + pipe( + op1: (source: Observable) => Observable, + op2: (source: Observable) => Observable, + op3: (source: Observable) => Observable + ): Observable + pipe( + op1: (source: Observable) => Observable, + op2: (source: Observable) => Observable, + op3: (source: Observable) => Observable, + op4: (source: Observable) => Observable + ): Observable + pipe( + op1: (source: Observable) => Observable, + op2: (source: Observable) => Observable, + op3: (source: Observable) => Observable, + op4: (source: Observable) => Observable, + op5: (source: Observable) => Observable + ): Observable + pipe( + op1: (source: Observable) => Observable, + op2: (source: Observable) => Observable, + op3: (source: Observable) => Observable, + op4: (source: Observable) => Observable, + op5: (source: Observable) => Observable, + op6: (source: Observable) => Observable + ): Observable + + pipe( + ...operators: Array<(input: Observable) => Observable> + ): Observable { + return operators.reduce( + (prev, operator) => operator(prev), + this as Observable + ) + } +} + +export type Subscription = () => void + +export type Observer = { + next: (value: T) => void + complete?: () => void + error?: (error: any) => void // In RxJS, `error` is usually typed as `any` +} + +// A type for either an Observer or a next callback +export type ObserverOrNext = Observer | ((value: T) => void) + +/******************************* + * Subjects + *******************************/ + +export class Subject extends Observable { + private subscribers: Observer[] = [] + + constructor() { + super(subscriber => { + this.subscribers.push(subscriber) + return () => { + this.subscribers = this.subscribers.filter(s => s !== subscriber) + } + }) + } + + next(value: T) { + this.subscribers.forEach(subscriber => subscriber.next(value)) + } + + error(error: any) { + this.subscribers.forEach(subscriber => subscriber.error?.(error)) + } + + complete() { + this.subscribers.forEach(subscriber => subscriber.complete?.()) + this.subscribers = [] + } +} + +export class BehaviorSubject extends Subject { + private value: T + + constructor(initialValue: T) { + super() + this.value = initialValue + } + + next(value: T) { + this.value = value + super.next(value) + } + + getValue() { + return this.value + } + + subscribe(observerOrNext: ObserverOrNext): Subscription { + const observer = normalizeObserver(observerOrNext) + // Emit the current value immediately + observer.next(this.value) + return super.subscribe(observer) + } +} + +/******************************* + * Operators + *******************************/ + +/** switchMap */ +export function switchMap( + project: (value: T) => Observable +): (source: Observable) => Observable { + return (source: Observable) => { + return new Observable(subscriber => { + let activeSubscription: Subscription | null = null + + const subscription = source.subscribe({ + next: value => { + if (activeSubscription) { + activeSubscription() + } + const innerObservable = project(value) + activeSubscription = innerObservable.subscribe({ + next: subscriber.next.bind(subscriber), + error: subscriber.error?.bind(subscriber), + complete: () => { + activeSubscription = null + }, + }) + }, + error: subscriber.error?.bind(subscriber), + complete: subscriber.complete?.bind(subscriber), + }) + + return () => { + if (activeSubscription) { + activeSubscription() + } + subscription() + } + }) + } +} + +/** map */ +export function map( + project: (value: T) => R +): (source: Observable) => Observable { + return (source: Observable) => { + return new Observable(subscriber => { + return source.subscribe({ + next: value => subscriber.next(project(value)), + error: subscriber.error?.bind(subscriber), + complete: subscriber.complete?.bind(subscriber), + }) + }) + } +} + +/** from (promise) */ +export function from(promise: Promise): Observable { + return new Observable(subscriber => { + let isCancelled = false + + promise + .then(value => { + if (!isCancelled) { + subscriber.next(value) + subscriber.complete?.() + } + }) + .catch(error => { + if (!isCancelled) { + subscriber.error?.(error) + } + }) + + return () => { + isCancelled = true + } + }) +} + +/** firstValueFrom */ +export async function firstValueFrom(source: Observable): Promise { + return await new Promise((resolve, reject) => { + const unsub = source.subscribe({ + next: value => { + resolve(value) + // wait until the next tick for unsub to be defined + setTimeout(() => unsub(), 0) + }, + error: reject, + complete: () => { + reject(new Error("Observable completed without emitting a value")) + }, + }) + }) +} + +/** distinctUntilChanged */ +export function distinctUntilChanged(): ( + source: Observable +) => Observable { + return source => { + return new Observable(subscriber => { + let lastValue: T | undefined + return source.subscribe({ + next: value => { + if (value !== lastValue) { + lastValue = value + subscriber.next(value) + } + }, + error: subscriber.error?.bind(subscriber), + complete: subscriber.complete?.bind(subscriber), + }) + }) + } +} + +/** concat */ +export function concat(...sources: Observable[]): Observable { + return new Observable(subscriber => { + let activeSubscription: Subscription | null = null + + function subscribeNext() { + if (sources.length === 0) { + subscriber.complete?.() + return + } + + const source = sources.shift()! + activeSubscription = source.subscribe({ + next: subscriber.next.bind(subscriber), + error: subscriber.error?.bind(subscriber), + complete: () => { + activeSubscription = null + subscribeNext() + }, + }) + } + + subscribeNext() + + return () => { + activeSubscription?.() + } + }) +} + +/** filter */ +export function filter( + predicate: (value: T) => boolean +): (source: Observable) => Observable { + return source => { + return new Observable(subscriber => { + return source.subscribe({ + next: value => { + if (predicate(value)) { + subscriber.next(value) + } + }, + error: subscriber.error?.bind(subscriber), + complete: subscriber.complete?.bind(subscriber), + }) + }) + } +} + +/** of */ +export function of(value: T): Observable { + return new Observable(subscriber => { + subscriber.next(value) + subscriber.complete?.() + return () => {} + }) +} + +/******************************* + * Internal utility + *******************************/ + +function normalizeObserver(observer: ObserverOrNext): Observer { + return typeof observer === "function" ? {next: observer} : observer +} From b2f90264ee3ca16d6075b001dfce9a55d9977662 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> Date: Mon, 3 Feb 2025 13:08:15 -0800 Subject: [PATCH 12/56] Implement FCL Ethereum Provider `chainChanged` Event (#2096) --- .../fcl-ethereum-provider/src/constants.ts | 2 + .../src/create-provider.ts | 6 +- .../src/events/event-dispatcher.test.ts | 46 +++++- .../src/events/event-dispatcher.ts | 9 +- .../src/network/network-manager.test.ts | 135 ++++++++++++++++++ .../src/network/network-manager.ts | 88 ++++++++++++ .../src/rpc/rpc-processor.test.ts | 26 ++-- .../src/rpc/rpc-processor.ts | 15 +- 8 files changed, 303 insertions(+), 24 deletions(-) create mode 100644 packages/fcl-ethereum-provider/src/network/network-manager.test.ts create mode 100644 packages/fcl-ethereum-provider/src/network/network-manager.ts diff --git a/packages/fcl-ethereum-provider/src/constants.ts b/packages/fcl-ethereum-provider/src/constants.ts index 311b8e8d5..c9c05a4eb 100644 --- a/packages/fcl-ethereum-provider/src/constants.ts +++ b/packages/fcl-ethereum-provider/src/constants.ts @@ -51,3 +51,5 @@ export interface TransactionExecutedEvent { precompiledCalls: string[] stateUpdateChecksum: string } + +export const ACCESS_NODE_API_KEY = "accessNode.api" diff --git a/packages/fcl-ethereum-provider/src/create-provider.ts b/packages/fcl-ethereum-provider/src/create-provider.ts index a20325e68..73a7f9e34 100644 --- a/packages/fcl-ethereum-provider/src/create-provider.ts +++ b/packages/fcl-ethereum-provider/src/create-provider.ts @@ -7,6 +7,7 @@ import {EventDispatcher} from "./events/event-dispatcher" import {AccountManager} from "./accounts/account-manager" import {FLOW_CHAINS} from "./constants" import {Gateway} from "./gateway/gateway" +import {NetworkManager} from "./network/network-manager" /** * Create a new FCL Ethereum provider @@ -41,13 +42,14 @@ export function createProvider(config: { {} as {[chainId: number]: string} ) + const networkManager = new NetworkManager(config.config) const accountManager = new AccountManager(config.user) const gateway = new Gateway({ ...defaultRpcUrls, ...(config.rpcUrls || {}), }) - const rpcProcessor = new RpcProcessor(gateway, accountManager) - const eventProcessor = new EventDispatcher(accountManager) + const rpcProcessor = new RpcProcessor(gateway, accountManager, networkManager) + const eventProcessor = new EventDispatcher(accountManager, networkManager) const provider = new FclEthereumProvider(rpcProcessor, eventProcessor) return provider diff --git a/packages/fcl-ethereum-provider/src/events/event-dispatcher.test.ts b/packages/fcl-ethereum-provider/src/events/event-dispatcher.test.ts index b86472c5e..2bdf35b44 100644 --- a/packages/fcl-ethereum-provider/src/events/event-dispatcher.test.ts +++ b/packages/fcl-ethereum-provider/src/events/event-dispatcher.test.ts @@ -1,13 +1,19 @@ import {AccountManager} from "../accounts/account-manager" +import {NetworkManager} from "../network/network-manager" +import {BehaviorSubject, Subject} from "../util/observable" import {EventDispatcher} from "./event-dispatcher" jest.mock("../accounts/account-manager") +jest.mock("../network/network-manager") describe("event dispatcher", () => { test("unsubscribe should remove listener", () => { const accountManager: jest.Mocked = new (AccountManager as any)() + const networkManager: jest.Mocked = + new (NetworkManager as any)() + let subs: ((accounts: string[]) => void)[] = [] accountManager.subscribe.mockImplementation(cb => { subs.push(cb) @@ -17,7 +23,7 @@ describe("event dispatcher", () => { }) const listener = jest.fn() - const eventDispatcher = new EventDispatcher(accountManager) + const eventDispatcher = new EventDispatcher(accountManager, networkManager) eventDispatcher.on("accountsChanged", listener) expect(accountManager.subscribe).toHaveBeenCalled() @@ -43,6 +49,9 @@ describe("event dispatcher", () => { const accountManager: jest.Mocked = new (AccountManager as any)() + const networkManager: jest.Mocked = + new (NetworkManager as any)() + let mockMgrSubCb: (accounts: string[]) => void accountManager.subscribe.mockImplementation(cb => { mockMgrSubCb = cb @@ -50,7 +59,7 @@ describe("event dispatcher", () => { }) const listener = jest.fn() - const eventDispatcher = new EventDispatcher(accountManager) + const eventDispatcher = new EventDispatcher(accountManager, networkManager) eventDispatcher.on("accountsChanged", listener) expect(accountManager.subscribe).toHaveBeenCalled() @@ -69,6 +78,9 @@ describe("event dispatcher", () => { const accountManager: jest.Mocked = new (AccountManager as any)() + const networkManager: jest.Mocked = + new (NetworkManager as any)() + let mockMgrSubCb: (accounts: string[]) => void accountManager.subscribe.mockImplementation(cb => { mockMgrSubCb = cb @@ -76,7 +88,7 @@ describe("event dispatcher", () => { }) const listener = jest.fn() - const eventDispatcher = new EventDispatcher(accountManager) + const eventDispatcher = new EventDispatcher(accountManager, networkManager) eventDispatcher.on("accountsChanged", listener) expect(accountManager.subscribe).toHaveBeenCalled() @@ -92,4 +104,32 @@ describe("event dispatcher", () => { expect(listener).toHaveBeenNthCalledWith(1, ["0x1234"]) expect(listener).toHaveBeenNthCalledWith(2, ["0x5678"]) }) + + test("should emit chainChanged", () => { + const accountManager: jest.Mocked = + new (AccountManager as any)() + + const networkManager: jest.Mocked = + new (NetworkManager as any)() + + let mockSubject = new Subject() + networkManager.subscribe.mockImplementation(cb => { + return mockSubject.subscribe(cb) + }) + const listener = jest.fn() + + const eventDispatcher = new EventDispatcher(accountManager, networkManager) + eventDispatcher.on("chainChanged", listener) + + expect(networkManager.subscribe).toHaveBeenCalled() + expect(networkManager.subscribe).toHaveBeenCalledTimes(1) + expect(networkManager.subscribe).toHaveBeenCalledWith(expect.any(Function)) + + // Simulate network change from network manager + mockSubject.next(0x2eb) + + expect(listener).toHaveBeenCalled() + expect(listener).toHaveBeenCalledTimes(1) + expect(listener).toHaveBeenCalledWith("0x2eb") + }) }) diff --git a/packages/fcl-ethereum-provider/src/events/event-dispatcher.ts b/packages/fcl-ethereum-provider/src/events/event-dispatcher.ts index 18642daf8..f7ccbf85e 100644 --- a/packages/fcl-ethereum-provider/src/events/event-dispatcher.ts +++ b/packages/fcl-ethereum-provider/src/events/event-dispatcher.ts @@ -1,5 +1,6 @@ import {EventCallback, ProviderEvents} from "../types/provider" import {AccountManager} from "../accounts/account-manager" +import {NetworkManager} from "../network/network-manager" import {Observable, Subscription} from "../util/observable" export class EventDispatcher { @@ -13,7 +14,7 @@ export class EventDispatcher { > } - constructor(accountManager: AccountManager) { + constructor(accountManager: AccountManager, networkManager: NetworkManager) { this.$emitters = { accountsChanged: new Observable(subscriber => { return accountManager.subscribe(accounts => { @@ -21,8 +22,10 @@ export class EventDispatcher { }) }), chainChanged: new Observable(subscriber => { - subscriber.complete?.() - return () => {} + return networkManager.subscribe(chainId => { + if (!chainId) return + subscriber.next(`0x${chainId.toString(16)}`) + }) }), connect: new Observable(subscriber => { subscriber.complete?.() diff --git a/packages/fcl-ethereum-provider/src/network/network-manager.test.ts b/packages/fcl-ethereum-provider/src/network/network-manager.test.ts new file mode 100644 index 000000000..e0dbcf0a9 --- /dev/null +++ b/packages/fcl-ethereum-provider/src/network/network-manager.test.ts @@ -0,0 +1,135 @@ +import {mockConfig} from "../__mocks__/fcl" +import {NetworkManager} from "./network-manager" +import * as fcl from "@onflow/fcl" + +jest.mock("@onflow/fcl", () => { + return { + getChainId: jest.fn(), + } +}) + +describe("network manager", () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + test("getChainId should return correct chain id mainnet", async () => { + jest.mocked(fcl.getChainId).mockResolvedValue("mainnet") + + const config = mockConfig() + await config.set({ + "accessNode.api": "https://example.com", + }) + const manager = new NetworkManager(config.mock) + const chainId = await manager.getChainId() + + expect(chainId).toBe(747) + }) + + test("getChainId should return correct chain id testnet", async () => { + jest.mocked(fcl.getChainId).mockResolvedValue("testnet") + + const config = mockConfig() + await config.set({ + "accessNode.api": "https://example.com", + }) + const manager = new NetworkManager(config.mock) + const chainId = await manager.getChainId() + + expect(chainId).toBe(646) + }) + + test("getChainId should throw error on unknown network", async () => { + jest.mocked(fcl.getChainId).mockResolvedValue("unknown") + + const config = mockConfig() + await config.set({ + "accessNode.api": "https://example.com", + }) + + const manager = new NetworkManager(config.mock) + await expect(manager.getChainId()).rejects.toThrow("Unknown network") + }) + + test("getChainId should throw error on error", async () => { + jest.mocked(fcl.getChainId).mockRejectedValue(new Error("error")) + + const config = mockConfig() + await config.set({ + "accessNode.api": "https://example.com", + }) + + const manager = new NetworkManager(config.mock) + await expect(manager.getChainId()).rejects.toThrow("error") + }) + + test("subscribe should return correct chain id", async () => { + jest.mocked(fcl.getChainId).mockResolvedValue("mainnet") + + const config = mockConfig() + await config.set({ + "accessNode.api": "https://example.com", + }) + + const manager = new NetworkManager(config.mock) + const chainId = await new Promise(resolve => { + const unsub = manager.subscribe(id => { + if (id) { + resolve(id) + unsub() + } + }) + }) + + expect(chainId).toBe(747) + }) + + test("subscribe should update chain id", async () => { + jest.mocked(fcl.getChainId).mockResolvedValue("mainnet") + + const config = mockConfig() + await config.set({ + "accessNode.api": "https://example.com", + }) + + const manager = new NetworkManager(config.mock) + + const chainIds: number[] = [] + + const unsub = manager.subscribe(id => { + if (id) { + chainIds.push(id) + } + }) + + await config.set({ + "accessNode.api": "https://example2.com", + }) + + await config.set({ + "accessNode.api": "https://example3.com", + }) + + unsub() + + expect(chainIds).toEqual([747, 747, 747]) + expect(fcl.getChainId).toHaveBeenCalledTimes(3) + }) + + test("should not query chain id multiple times for same access node", async () => { + jest.mocked(fcl.getChainId).mockResolvedValue("mainnet") + + const config = mockConfig() + await config.set({ + "accessNode.api": "https://example.com", + }) + + const manager = new NetworkManager(config.mock) + + await manager.getChainId() + await manager.getChainId() + await manager.getChainId() + + expect(fcl.getChainId).toHaveBeenCalledTimes(1) + }) +}) diff --git a/packages/fcl-ethereum-provider/src/network/network-manager.ts b/packages/fcl-ethereum-provider/src/network/network-manager.ts new file mode 100644 index 000000000..1bd920210 --- /dev/null +++ b/packages/fcl-ethereum-provider/src/network/network-manager.ts @@ -0,0 +1,88 @@ +import {ACCESS_NODE_API_KEY, FLOW_CHAINS, FlowNetwork} from "../constants" +import { + BehaviorSubject, + concat, + distinctUntilChanged, + filter, + firstValueFrom, + from, + map, + of, + Subscription, + switchMap, +} from "../util/observable" +import * as fcl from "@onflow/fcl" + +type ChainIdStore = { + isLoading: boolean + chainId: number | null + error: unknown | null +} +export class NetworkManager { + private $chainIdStore = new BehaviorSubject({ + isLoading: true, + chainId: null, + error: null, + }) + + constructor(config: typeof fcl.config) { + // Map FCL config to behavior subject + const $config = new BehaviorSubject | null>(null) + config.subscribe((cfg, err) => { + if (err) { + $config.error(err) + } else { + $config.next(cfg) + } + }) + + // Bind $network to chainId + $config + .pipe( + map(cfg => cfg?.[ACCESS_NODE_API_KEY]), + distinctUntilChanged(), + switchMap(accessNode => + concat( + of({isLoading: true} as ChainIdStore), + from( + (async () => { + try { + const flowNetwork = (await fcl.getChainId({ + node: accessNode, + })) as FlowNetwork + if (!(flowNetwork in FLOW_CHAINS)) { + throw new Error("Unknown network") + } + const {eip155ChainId: chainId} = FLOW_CHAINS[flowNetwork] + return {isLoading: false, chainId, error: null} + } catch (error) { + return {isLoading: false, chainId: null, error} + } + })() + ) + ) + ) + ) + .subscribe(this.$chainIdStore) + } + + public subscribe(callback: (chainId: number | null) => void): Subscription { + return this.$chainIdStore + .pipe( + filter(x => !x.isLoading && !x.error), + map(x => x.chainId) + ) + .subscribe(callback) + } + + public async getChainId(): Promise { + const {chainId, error} = await firstValueFrom( + this.$chainIdStore.pipe(filter(x => !x.isLoading)) + ) + + if (error) { + throw error + } + return chainId + } +} diff --git a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.test.ts b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.test.ts index ea18f7e6d..eb3749c2e 100644 --- a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.test.ts +++ b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.test.ts @@ -1,23 +1,27 @@ import {AccountManager} from "../accounts/account-manager" import {Gateway} from "../gateway/gateway" +import {NetworkManager} from "../network/network-manager" import {RpcProcessor} from "./rpc-processor" -import * as fcl from "@onflow/fcl" jest.mock("../gateway/gateway") jest.mock("../accounts/account-manager") -jest.mock("@onflow/fcl", () => ({ - getChainId: jest.fn(), -})) +jest.mock("../network/network-manager") describe("rpc processor", () => { test("fallback to gateway mainnet", async () => { const gateway: jest.Mocked = new (Gateway as any)() const accountManager: jest.Mocked = new (AccountManager as any)() - const rpcProcessor = new RpcProcessor(gateway, accountManager) + const networkManager: jest.Mocked = + new (NetworkManager as any)() + const rpcProcessor = new RpcProcessor( + gateway, + accountManager, + networkManager + ) jest.mocked(gateway).request.mockResolvedValue("0x0") - jest.mocked(fcl.getChainId).mockResolvedValue("mainnet") + networkManager.getChainId.mockResolvedValue(747) const response = await rpcProcessor.handleRequest({ method: "eth_blockNumber", @@ -38,10 +42,16 @@ describe("rpc processor", () => { const gateway: jest.Mocked = new (Gateway as any)() const accountManager: jest.Mocked = new (AccountManager as any)() - const rpcProcessor = new RpcProcessor(gateway, accountManager) + const networkManager: jest.Mocked = + new (NetworkManager as any)() + const rpcProcessor = new RpcProcessor( + gateway, + accountManager, + networkManager + ) jest.mocked(gateway).request.mockResolvedValue("0x0") - jest.mocked(fcl.getChainId).mockResolvedValue("testnet") + networkManager.getChainId.mockResolvedValue(646) const response = await rpcProcessor.handleRequest({ method: "eth_blockNumber", diff --git a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts index b6fa22db1..f86bf7212 100644 --- a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts +++ b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts @@ -2,24 +2,23 @@ import {ProviderRequest} from "../types/provider" import {ethAccounts, ethRequestAccounts} from "./handlers/eth-accounts" import {Gateway} from "../gateway/gateway" import {AccountManager} from "../accounts/account-manager" -import * as fcl from "@onflow/fcl" -import {FLOW_CHAINS, FlowNetwork} from "../constants" import {ethSendTransaction} from "./handlers/eth-send-transaction" +import {NetworkManager} from "../network/network-manager" import {personalSign} from "./handlers/personal-sign" import {PersonalSignParams} from "../types/eth" export class RpcProcessor { constructor( private gateway: Gateway, - private accountManager: AccountManager + private accountManager: AccountManager, + private networkManager: NetworkManager ) {} async handleRequest({method, params}: ProviderRequest): Promise { - const flowNetwork = await fcl.getChainId() - if (!(flowNetwork in FLOW_CHAINS)) { - throw new Error(`Unsupported chainId ${flowNetwork}`) + const chainId = await this.networkManager.getChainId() + if (!chainId) { + throw new Error("No active chain") } - const {eip155ChainId} = FLOW_CHAINS[flowNetwork as FlowNetwork] switch (method) { case "eth_accounts": @@ -35,7 +34,7 @@ export class RpcProcessor { ) default: return await this.gateway.request({ - chainId: eip155ChainId, + chainId, method, params, }) From 4c7ec61e3782853557bc8df4051d4778708e04ea Mon Sep 17 00:00:00 2001 From: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> Date: Mon, 3 Feb 2025 13:13:12 -0800 Subject: [PATCH 13/56] Implement provider `connect` event (#2090) --- .../src/events/event-dispatcher.test.ts | 57 +++++++------------ .../src/events/event-dispatcher.ts | 39 ++++++++----- .../src/network/network-manager.test.ts | 12 ++-- .../src/network/network-manager.ts | 13 ++--- .../src/util/observable.ts | 44 ++++++++++++++ 5 files changed, 101 insertions(+), 64 deletions(-) diff --git a/packages/fcl-ethereum-provider/src/events/event-dispatcher.test.ts b/packages/fcl-ethereum-provider/src/events/event-dispatcher.test.ts index 2bdf35b44..7f338e749 100644 --- a/packages/fcl-ethereum-provider/src/events/event-dispatcher.test.ts +++ b/packages/fcl-ethereum-provider/src/events/event-dispatcher.test.ts @@ -1,5 +1,5 @@ import {AccountManager} from "../accounts/account-manager" -import {NetworkManager} from "../network/network-manager" +import {ChainIdStore, NetworkManager} from "../network/network-manager" import {BehaviorSubject, Subject} from "../util/observable" import {EventDispatcher} from "./event-dispatcher" @@ -7,13 +7,20 @@ jest.mock("../accounts/account-manager") jest.mock("../network/network-manager") describe("event dispatcher", () => { + let networkManager: jest.Mocked + let accountManager: jest.Mocked + let $mockChainId: Subject + + beforeEach(() => { + jest.clearAllMocks() + $mockChainId = new Subject() + networkManager = { + $chainId: $mockChainId, + getChainId: jest.fn(), + } as any + accountManager = new (AccountManager as any)() + }) test("unsubscribe should remove listener", () => { - const accountManager: jest.Mocked = - new (AccountManager as any)() - - const networkManager: jest.Mocked = - new (NetworkManager as any)() - let subs: ((accounts: string[]) => void)[] = [] accountManager.subscribe.mockImplementation(cb => { subs.push(cb) @@ -46,12 +53,6 @@ describe("event dispatcher", () => { }) test("should emit accountsChanged", () => { - const accountManager: jest.Mocked = - new (AccountManager as any)() - - const networkManager: jest.Mocked = - new (NetworkManager as any)() - let mockMgrSubCb: (accounts: string[]) => void accountManager.subscribe.mockImplementation(cb => { mockMgrSubCb = cb @@ -75,12 +76,6 @@ describe("event dispatcher", () => { }) test("should emit accountsChanged multiple times", () => { - const accountManager: jest.Mocked = - new (AccountManager as any)() - - const networkManager: jest.Mocked = - new (NetworkManager as any)() - let mockMgrSubCb: (accounts: string[]) => void accountManager.subscribe.mockImplementation(cb => { mockMgrSubCb = cb @@ -105,30 +100,20 @@ describe("event dispatcher", () => { expect(listener).toHaveBeenNthCalledWith(2, ["0x5678"]) }) - test("should emit chainChanged", () => { - const accountManager: jest.Mocked = - new (AccountManager as any)() - - const networkManager: jest.Mocked = - new (NetworkManager as any)() - - let mockSubject = new Subject() - networkManager.subscribe.mockImplementation(cb => { - return mockSubject.subscribe(cb) - }) + test("should emit chainChanged", async () => { const listener = jest.fn() const eventDispatcher = new EventDispatcher(accountManager, networkManager) eventDispatcher.on("chainChanged", listener) - expect(networkManager.subscribe).toHaveBeenCalled() - expect(networkManager.subscribe).toHaveBeenCalledTimes(1) - expect(networkManager.subscribe).toHaveBeenCalledWith(expect.any(Function)) + // Initial chain id, should not emit as a change + $mockChainId.next({isLoading: false, error: null, chainId: 0x286}) - // Simulate network change from network manager - mockSubject.next(0x2eb) + // Change chain id + $mockChainId.next({isLoading: false, error: null, chainId: 0x2eb}) + + await new Promise(resolve => setTimeout(resolve, 100)) - expect(listener).toHaveBeenCalled() expect(listener).toHaveBeenCalledTimes(1) expect(listener).toHaveBeenCalledWith("0x2eb") }) diff --git a/packages/fcl-ethereum-provider/src/events/event-dispatcher.ts b/packages/fcl-ethereum-provider/src/events/event-dispatcher.ts index f7ccbf85e..e94cc7a47 100644 --- a/packages/fcl-ethereum-provider/src/events/event-dispatcher.ts +++ b/packages/fcl-ethereum-provider/src/events/event-dispatcher.ts @@ -1,7 +1,14 @@ import {EventCallback, ProviderEvents} from "../types/provider" import {AccountManager} from "../accounts/account-manager" import {NetworkManager} from "../network/network-manager" -import {Observable, Subscription} from "../util/observable" +import { + filter, + map, + Observable, + skip, + Subscription, + takeFirst, +} from "../util/observable" export class EventDispatcher { private $emitters: { @@ -16,23 +23,29 @@ export class EventDispatcher { constructor(accountManager: AccountManager, networkManager: NetworkManager) { this.$emitters = { + // Emit changes to the accounts as an accountsChanged event accountsChanged: new Observable(subscriber => { return accountManager.subscribe(accounts => { subscriber.next(accounts) }) }), - chainChanged: new Observable(subscriber => { - return networkManager.subscribe(chainId => { - if (!chainId) return - subscriber.next(`0x${chainId.toString(16)}`) - }) - }), - connect: new Observable(subscriber => { - subscriber.complete?.() - return () => {} - }), - disconnect: new Observable(subscriber => { - subscriber.complete?.() + // Emit changes to the chainId as a chainChanged event + chainChanged: networkManager.$chainId.pipe( + filter(({isLoading, error}) => !isLoading && !error), + map(({chainId}) => { + return `0x${chainId!.toString(16)}` + }), + skip(1) + ) as Observable, + // Emit the first chainId as a connect event + connect: networkManager.$chainId.pipe( + filter(({isLoading, error}) => !isLoading && !error), + map(({chainId}) => { + return {chainId: `0x${chainId!.toString(16)}`} + }), + takeFirst() + ), + disconnect: new Observable<{reason: string}>(() => { return () => {} }), } diff --git a/packages/fcl-ethereum-provider/src/network/network-manager.test.ts b/packages/fcl-ethereum-provider/src/network/network-manager.test.ts index e0dbcf0a9..081abc8b2 100644 --- a/packages/fcl-ethereum-provider/src/network/network-manager.test.ts +++ b/packages/fcl-ethereum-provider/src/network/network-manager.test.ts @@ -73,9 +73,9 @@ describe("network manager", () => { const manager = new NetworkManager(config.mock) const chainId = await new Promise(resolve => { - const unsub = manager.subscribe(id => { - if (id) { - resolve(id) + const unsub = manager.$chainId.subscribe(({chainId}) => { + if (chainId) { + resolve(chainId) unsub() } }) @@ -96,9 +96,9 @@ describe("network manager", () => { const chainIds: number[] = [] - const unsub = manager.subscribe(id => { - if (id) { - chainIds.push(id) + const unsub = manager.$chainId.subscribe(({chainId}) => { + if (chainId) { + chainIds.push(chainId) } }) diff --git a/packages/fcl-ethereum-provider/src/network/network-manager.ts b/packages/fcl-ethereum-provider/src/network/network-manager.ts index 1bd920210..bb7d630e4 100644 --- a/packages/fcl-ethereum-provider/src/network/network-manager.ts +++ b/packages/fcl-ethereum-provider/src/network/network-manager.ts @@ -8,16 +8,16 @@ import { from, map, of, - Subscription, switchMap, } from "../util/observable" import * as fcl from "@onflow/fcl" -type ChainIdStore = { +export type ChainIdStore = { isLoading: boolean chainId: number | null error: unknown | null } + export class NetworkManager { private $chainIdStore = new BehaviorSubject({ isLoading: true, @@ -66,13 +66,8 @@ export class NetworkManager { .subscribe(this.$chainIdStore) } - public subscribe(callback: (chainId: number | null) => void): Subscription { - return this.$chainIdStore - .pipe( - filter(x => !x.isLoading && !x.error), - map(x => x.chainId) - ) - .subscribe(callback) + get $chainId() { + return this.$chainIdStore.asObservable() } public async getChainId(): Promise { diff --git a/packages/fcl-ethereum-provider/src/util/observable.ts b/packages/fcl-ethereum-provider/src/util/observable.ts index f34421b9d..ca975b040 100644 --- a/packages/fcl-ethereum-provider/src/util/observable.ts +++ b/packages/fcl-ethereum-provider/src/util/observable.ts @@ -57,6 +57,12 @@ export class Observable { this as Observable ) } + + asObservable(): Observable { + return new Observable(subscriber => { + return this.subscribe(subscriber) + }) + } } export type Subscription = () => void @@ -300,6 +306,44 @@ export function of(value: T): Observable { }) } +/** skip */ +export function skip( + count: number +): (source: Observable) => Observable { + return source => { + return new Observable(subscriber => { + let skipped = 0 + return source.subscribe({ + next: value => { + if (skipped >= count) { + subscriber.next(value) + } else { + skipped++ + } + }, + error: subscriber.error?.bind(subscriber), + complete: subscriber.complete?.bind(subscriber), + }) + }) + } +} + +/** takeFirst */ +export function takeFirst(): (source: Observable) => Observable { + return source => { + return new Observable(subscriber => { + return source.subscribe({ + next: value => { + subscriber.next(value) + subscriber.complete?.() + }, + error: subscriber.error?.bind(subscriber), + complete: subscriber.complete?.bind(subscriber), + }) + }) + } +} + /******************************* * Internal utility *******************************/ From 29d7f0764bf8890c0ac135b89195c12773e8075d Mon Sep 17 00:00:00 2001 From: Chase Fleming Date: Mon, 3 Feb 2025 13:44:37 -0800 Subject: [PATCH 14/56] Add `eth_signTypedData` (#2097) * Add eth signed typed data * Fix * Run prettier * Add tests * Run prettier * Remove comment * Match hashing closer to spec * Use util * Fix tests * Run prettier * Remove legacy support * Update packages/fcl-ethereum-provider/src/hash-utils.ts Co-authored-by: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> * Fix test --------- Co-authored-by: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Co-authored-by: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> --- package-lock.json | 21 ++++ package.json | 3 + packages/fcl-ethereum-provider/package.json | 2 + .../src/hash-utils.test.ts | 113 ++++++++++++++++++ .../fcl-ethereum-provider/src/hash-utils.ts | 41 +++++++ .../src/rpc/handlers/eth-signtypeddata.ts | 30 +++++ .../src/rpc/rpc-processor.ts | 29 ++++- .../fcl-ethereum-provider/src/types/eth.ts | 25 ++++ 8 files changed, 263 insertions(+), 1 deletion(-) create mode 100644 packages/fcl-ethereum-provider/src/hash-utils.test.ts create mode 100644 packages/fcl-ethereum-provider/src/hash-utils.ts create mode 100644 packages/fcl-ethereum-provider/src/rpc/handlers/eth-signtypeddata.ts diff --git a/package-lock.json b/package-lock.json index c638df32a..30b629384 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,9 @@ "workspaces": [ "./packages/*" ], + "dependencies": { + "@noble/hashes": "^1.7.1" + }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", "@changesets/changelog-github": "^0.4.8", @@ -2634,6 +2637,8 @@ }, "node_modules/@ethersproject/bytes": { "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.7.0.tgz", + "integrity": "sha512-nsbxwgFXWh9NyYWo+U8atvmMsSdKJprTcICAkvbBffT75qDocbuggBU0SJiVK2MuTrp0q+xvLkTnGMPK1+uA9A==", "funding": [ { "type": "individual", @@ -2668,6 +2673,8 @@ }, "node_modules/@ethersproject/hash": { "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.7.0.tgz", + "integrity": "sha512-qX5WrQfnah1EFnO5zJv1v46a8HW0+E5xuBBDTwMFZLuVTx0tbU2kkx15NqdjxecrLGatQN9FGQKpb1FKdHCt+g==", "funding": [ { "type": "individual", @@ -5768,6 +5775,18 @@ "@tybys/wasm-util": "^0.9.0" } }, + "node_modules/@noble/hashes": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.1.tgz", + "integrity": "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "license": "MIT", @@ -29201,6 +29220,8 @@ "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.25.7", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hash": "^5.7.0", "@onflow/fcl": "1.13.4", "@onflow/rlp": "^1.2.3", "@walletconnect/jsonrpc-http-connection": "^1.0.8", diff --git a/package.json b/package.json index 4699e1f07..c3e04d794 100644 --- a/package.json +++ b/package.json @@ -36,5 +36,8 @@ "@nx/nx-darwin-x64": "^17.3.2", "@nx/nx-linux-x64-gnu": "^17.3.2", "@nx/nx-win32-x64-msvc": "^17.3.2" + }, + "dependencies": { + "@noble/hashes": "^1.7.1" } } diff --git a/packages/fcl-ethereum-provider/package.json b/packages/fcl-ethereum-provider/package.json index 7986990a3..9907ac63d 100644 --- a/packages/fcl-ethereum-provider/package.json +++ b/packages/fcl-ethereum-provider/package.json @@ -37,6 +37,8 @@ }, "dependencies": { "@babel/runtime": "^7.25.7", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hash": "^5.7.0", "@onflow/fcl": "1.13.4", "@onflow/rlp": "^1.2.3", "@walletconnect/jsonrpc-http-connection": "^1.0.8", diff --git a/packages/fcl-ethereum-provider/src/hash-utils.test.ts b/packages/fcl-ethereum-provider/src/hash-utils.test.ts new file mode 100644 index 000000000..e57afbca1 --- /dev/null +++ b/packages/fcl-ethereum-provider/src/hash-utils.test.ts @@ -0,0 +1,113 @@ +import {keccak_256} from "@noble/hashes/sha3" +import {bytesToHex} from "@noble/hashes/utils" +import {concat, arrayify} from "@ethersproject/bytes" +import {_TypedDataEncoder as TypedDataEncoder} from "@ethersproject/hash" +import {TypedData} from "./types/eth" +import { + hashTypedDataLegacy, + hashTypedDataV3, + hashTypedDataV4, +} from "./hash-utils" + +jest.mock("@noble/hashes/sha3", () => ({ + keccak_256: jest.fn(() => + Uint8Array.from([0xab, 0xcd, 0xef, 0x12, 0x34, 0x56, 0x78, 0x90]) + ), +})) + +jest.mock("@ethersproject/hash", () => { + const original = jest.requireActual("@ethersproject/hash") + return { + ...original, + _TypedDataEncoder: { + hashDomain: jest.fn( + domain => + // Return a valid 32-byte hex string (64 hex characters after "0x") + "0x1111111111111111111111111111111111111111111111111111111111111111" + ), + hash: jest.fn( + (domain, types, message) => + "0x2222222222222222222222222222222222222222222222222222222222222222" + ), + }, + } +}) + +describe("Hash Utils", () => { + const mockTypedData: TypedData = { + domain: {name: "Ether Mail", chainId: 1}, + message: {from: "Alice", to: "Bob", contents: "Hello"}, + types: { + EIP712Domain: [ + {name: "name", type: "string"}, + {name: "chainId", type: "uint256"}, + ], + Mail: [ + {name: "from", type: "string"}, + {name: "to", type: "string"}, + {name: "contents", type: "string"}, + ], + }, + primaryType: "Mail", + } + + afterEach(() => { + jest.clearAllMocks() + }) + + describe("hashTypedDataLegacy", () => { + it("should throw an error for legacy (legacy support is not provided)", () => { + expect(() => hashTypedDataLegacy(mockTypedData)).toThrowError( + "Legacy eth_signTypedData is not supported. Please use eth_signTypedData_v3 or eth_signTypedData_v4 instead." + ) + }) + }) + + describe("hashTypedDataV3", () => { + it("should call the TypedDataEncoder functions and then keccak_256 correctly", () => { + const result = hashTypedDataV3(mockTypedData) + + expect(TypedDataEncoder.hashDomain).toHaveBeenCalledWith( + mockTypedData.domain + ) + + expect(TypedDataEncoder.hash).toHaveBeenCalledWith( + mockTypedData.domain, + mockTypedData.types, + mockTypedData.message + ) + + // The implementation concatenates: + // prefix (0x1901), domainSeparator, and messageHash. + const prefix = "0x1901" + const expectedConcat = concat([ + arrayify(prefix), + arrayify( + "0x1111111111111111111111111111111111111111111111111111111111111111" + ), + arrayify( + "0x2222222222222222222222222222222222222222222222222222222222222222" + ), + ]) + + expect(keccak_256).toHaveBeenCalledWith(expectedConcat) + + // The keccak_256 mock always returns our fixed Uint8Array, + // so the expected hash is: + const expectedV3Hash = + "0x" + + bytesToHex( + Uint8Array.from([0xab, 0xcd, 0xef, 0x12, 0x34, 0x56, 0x78, 0x90]) + ) + expect(result).toBe(expectedV3Hash) + }) + }) + + describe("hashTypedDataV4", () => { + it("should produce the same result as v3 (for non-nested cases)", () => { + const v3Result = hashTypedDataV3(mockTypedData) + const v4Result = hashTypedDataV4(mockTypedData) + expect(v4Result).toBe(v3Result) + }) + }) +}) diff --git a/packages/fcl-ethereum-provider/src/hash-utils.ts b/packages/fcl-ethereum-provider/src/hash-utils.ts new file mode 100644 index 000000000..a986dc6f1 --- /dev/null +++ b/packages/fcl-ethereum-provider/src/hash-utils.ts @@ -0,0 +1,41 @@ +import {keccak_256} from "@noble/hashes/sha3" +import {bytesToHex} from "@noble/hashes/utils" +import {arrayify, concat} from "@ethersproject/bytes" +import {_TypedDataEncoder as TypedDataEncoder} from "@ethersproject/hash" +import {TypedData} from "./types/eth" + +export function hashTypedDataLegacy(data: TypedData): string { + throw new Error( + "Legacy eth_signTypedData is not supported. Please use eth_signTypedData_v3 or eth_signTypedData_v4 instead." + ) +} + +/** + * Hash for `eth_signTypedData_v3` + * + * Uses EIP‑712 encoding: + * digest = keccak_256( "\x19\x01" || domainSeparator || messageHash ) + */ +export function hashTypedDataV3(data: TypedData): string { + const domainSeparator = TypedDataEncoder.hashDomain(data.domain) + const messageHash = TypedDataEncoder.hash( + data.domain, + data.types, + data.message + ) + // The EIP‑191 prefix is "0x1901". + const prefix = "0x1901" + const digest = keccak_256( + concat([arrayify(prefix), arrayify(domainSeparator), arrayify(messageHash)]) + ) + return "0x" + bytesToHex(digest) +} + +/** + * Hash for `eth_signTypedData_v4` + * + * For many cases, v3 and v4 yield the same result (if you’re not using arrays or nested dynamic types). + */ +export function hashTypedDataV4(data: TypedData): string { + return hashTypedDataV3(data) +} diff --git a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-signtypeddata.ts b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-signtypeddata.ts new file mode 100644 index 000000000..4a3350cfa --- /dev/null +++ b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-signtypeddata.ts @@ -0,0 +1,30 @@ +import {AccountManager} from "../../accounts/account-manager" +import {SignTypedDataParams} from "../../types/eth" +import { + hashTypedDataLegacy, + hashTypedDataV3, + hashTypedDataV4, +} from "../../hash-utils" + +export async function signTypedData( + accountManager: AccountManager, + params: SignTypedDataParams, + version: "eth_signTypedData" | "eth_signTypedData_v3" | "eth_signTypedData_v4" +) { + const {address, data} = params + + if (!address || !data) { + throw new Error("Missing signer address or typed data") + } + + let hashedMessage: string + if (version === "eth_signTypedData_v3") { + hashedMessage = hashTypedDataV3(data) + } else if (version === "eth_signTypedData_v4") { + hashedMessage = hashTypedDataV4(data) + } else { + hashedMessage = hashTypedDataLegacy(data) + } + + return await accountManager.signMessage(hashedMessage, address) +} diff --git a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts index f86bf7212..efd82c269 100644 --- a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts +++ b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts @@ -5,7 +5,8 @@ import {AccountManager} from "../accounts/account-manager" import {ethSendTransaction} from "./handlers/eth-send-transaction" import {NetworkManager} from "../network/network-manager" import {personalSign} from "./handlers/personal-sign" -import {PersonalSignParams} from "../types/eth" +import {PersonalSignParams, SignTypedDataParams, TypedData} from "../types/eth" +import {signTypedData} from "./handlers/eth-signtypeddata" export class RpcProcessor { constructor( @@ -27,6 +28,32 @@ export class RpcProcessor { return ethRequestAccounts(this.accountManager) case "eth_sendTransaction": return await ethSendTransaction(this.accountManager, params) + case "eth_signTypedData": + case "eth_signTypedData_v3": + case "eth_signTypedData_v4": { + if (!params || typeof params !== "object") { + throw new Error(`${method} requires valid parameters.`) + } + + const {address, data} = params as {address?: unknown; data?: unknown} + + if ( + typeof address !== "string" || + typeof data !== "object" || + data === null + ) { + throw new Error( + `${method} requires 'address' (string) and a valid 'data' object.` + ) + } + + const validParams: SignTypedDataParams = { + address, + data: data as TypedData, + } + + return await signTypedData(this.accountManager, validParams, method) + } case "personal_sign": return await personalSign( this.accountManager, diff --git a/packages/fcl-ethereum-provider/src/types/eth.ts b/packages/fcl-ethereum-provider/src/types/eth.ts index 4cd8076db..92652d89f 100644 --- a/packages/fcl-ethereum-provider/src/types/eth.ts +++ b/packages/fcl-ethereum-provider/src/types/eth.ts @@ -1,3 +1,28 @@ export type EthSignatureResponse = string export type PersonalSignParams = [string, string] + +export interface SignTypedDataParams { + address: string + data: TypedData // This represents the EIP-712 structured data +} + +export interface TypedDataField { + name: string + type: string +} + +export interface TypedDataDomain { + name?: string + version?: string + chainId?: number + verifyingContract?: string + salt?: string +} + +export interface TypedData { + types: Record + domain: TypedDataDomain + primaryType: string + message: Record +} From 4829b5afbb2a0e00569f0d9fde83007808827828 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> Date: Mon, 3 Feb 2025 14:08:04 -0800 Subject: [PATCH 15/56] Add check for `from` address in `AccountManager.sendTransaction` (#2103) --- .../src/accounts/account-manager.test.ts | 43 +++++++++++++++---- .../src/accounts/account-manager.ts | 8 ++++ 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts b/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts index 42b87a035..c0492a14c 100644 --- a/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts +++ b/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts @@ -201,9 +201,6 @@ describe("send transaction", () => { }) test("send transaction mainnet", async () => { - const user = mockUser().mock - const accountManager = new AccountManager(user) - const mockTxResult = { onceExecuted: jest.fn().mockResolvedValue({ events: [ @@ -219,6 +216,10 @@ describe("send transaction", () => { jest.mocked(fcl.tx).mockReturnValue(mockTxResult) jest.mocked(fcl.mutate).mockResolvedValue("1111") + jest.mocked(fcl.query).mockResolvedValue("0x1234") + + const user = mockUser({addr: "0x4444"} as CurrentUser).mock + const accountManager = new AccountManager(user) const tx = { to: "0x1234", @@ -247,9 +248,6 @@ describe("send transaction", () => { }) test("send transaction testnet", async () => { - const user = mockUser() - const accountManager = new AccountManager(user.mock) - const mockTxResult = { onceExecuted: jest.fn().mockResolvedValue({ events: [ @@ -265,6 +263,10 @@ describe("send transaction", () => { jest.mocked(fcl.tx).mockReturnValue(mockTxResult) jest.mocked(fcl.mutate).mockResolvedValue("1111") + jest.mocked(fcl.query).mockResolvedValue("0x1234") + + const user = mockUser({addr: "0x4444"} as CurrentUser) + const accountManager = new AccountManager(user.mock) const tx = { to: "0x1234", @@ -293,9 +295,6 @@ describe("send transaction", () => { }) test("throws error if no executed event not found", async () => { - const user = mockUser() - const accountManager = new AccountManager(user.mock) - const mockTxResult = { onceExecuted: jest.fn().mockResolvedValue({ events: [], @@ -304,6 +303,10 @@ describe("send transaction", () => { jest.mocked(fcl.tx).mockReturnValue(mockTxResult) jest.mocked(fcl.mutate).mockResolvedValue("1111") + jest.mocked(fcl.query).mockResolvedValue("0x1234") + + const user = mockUser({addr: "0x4444"} as CurrentUser) + const accountManager = new AccountManager(user.mock) const tx = { to: "0x1234", @@ -319,6 +322,28 @@ describe("send transaction", () => { "EVM transaction hash not found" ) }) + + test("throws error if from address does not match user address", async () => { + jest.mocked(fcl.query).mockResolvedValue("0x1234") + const user = mockUser({addr: "0x4444"} as CurrentUser) + const accountManager = new AccountManager(user.mock) + + const tx = { + to: "0x1234", + from: "0x4567", + value: "0", + data: "0x1234", + nonce: "0", + gas: "0", + chainId: "646", + } + + await expect(accountManager.sendTransaction(tx)).rejects.toThrow( + `From address does not match authenticated user address.\nUser: 0x1234\nFrom: 0x4567` + ) + + expect(fcl.mutate).not.toHaveBeenCalled() + }) }) describe("signMessage", () => { diff --git a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts index 154e1dea1..7d470e105 100644 --- a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts +++ b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts @@ -160,6 +160,14 @@ export class AccountManager { FLOW_CONTRACTS[ContractType.EVM][flowNetwork] ) + // Check if the from address matches the authenticated COA address + const expectedCOAAddress = await this.getCOAAddress() + if (from.toLowerCase() !== expectedCOAAddress?.toLowerCase()) { + throw new Error( + `From address does not match authenticated user address.\nUser: ${expectedCOAAddress}\nFrom: ${from}` + ) + } + const txId = await fcl.mutate({ cadence: `import EVM from ${evmContractAddress} From 7bb563bf69224c488b1b152f9a6599a7c050f4b9 Mon Sep 17 00:00:00 2001 From: Chase Fleming Date: Mon, 3 Feb 2025 19:58:26 -0800 Subject: [PATCH 16/56] Add noop for add eth chain (#2106) * Add noop for add eth chain * Run prettier --------- Co-authored-by: Chase Fleming <1666730+chasefleming@users.noreply.github.com> --- .../fcl-ethereum-provider/src/network/network-manager.ts | 8 ++++++++ packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts | 2 ++ 2 files changed, 10 insertions(+) diff --git a/packages/fcl-ethereum-provider/src/network/network-manager.ts b/packages/fcl-ethereum-provider/src/network/network-manager.ts index bb7d630e4..d5e7fd1df 100644 --- a/packages/fcl-ethereum-provider/src/network/network-manager.ts +++ b/packages/fcl-ethereum-provider/src/network/network-manager.ts @@ -80,4 +80,12 @@ export class NetworkManager { } return chainId } + + /** + * No-op implementation for wallet_addEthereumChain. + * Since FCL does support dynamic chain additions. + */ + public async wallet_addEthereumChain(): Promise { + return null + } } diff --git a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts index efd82c269..52d797610 100644 --- a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts +++ b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts @@ -59,6 +59,8 @@ export class RpcProcessor { this.accountManager, params as PersonalSignParams ) + case "wallet_addEthereumChain": + return await this.networkManager.wallet_addEthereumChain() default: return await this.gateway.request({ chainId, From 9a57857baae4d1bdbd813d7ba615d43a870e9720 Mon Sep 17 00:00:00 2001 From: Chase Fleming Date: Tue, 4 Feb 2025 10:24:10 -0800 Subject: [PATCH 17/56] Add typed params for wallet_addEthereumChain (#2109) * Add typed params for wallet_addEthereumChain * Run prettier --------- Co-authored-by: Chase Fleming <1666730+chasefleming@users.noreply.github.com> --- .../src/network/network-manager.ts | 5 ++++- .../src/rpc/rpc-processor.ts | 17 +++++++++++++++-- packages/fcl-ethereum-provider/src/types/eth.ts | 13 +++++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/packages/fcl-ethereum-provider/src/network/network-manager.ts b/packages/fcl-ethereum-provider/src/network/network-manager.ts index d5e7fd1df..b69d94964 100644 --- a/packages/fcl-ethereum-provider/src/network/network-manager.ts +++ b/packages/fcl-ethereum-provider/src/network/network-manager.ts @@ -11,6 +11,7 @@ import { switchMap, } from "../util/observable" import * as fcl from "@onflow/fcl" +import {AddEthereumChainParams} from "../types/eth" export type ChainIdStore = { isLoading: boolean @@ -85,7 +86,9 @@ export class NetworkManager { * No-op implementation for wallet_addEthereumChain. * Since FCL does support dynamic chain additions. */ - public async wallet_addEthereumChain(): Promise { + public async wallet_addEthereumChain( + _chainConfig: AddEthereumChainParams + ): Promise { return null } } diff --git a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts index 52d797610..255a20dcc 100644 --- a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts +++ b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts @@ -5,7 +5,12 @@ import {AccountManager} from "../accounts/account-manager" import {ethSendTransaction} from "./handlers/eth-send-transaction" import {NetworkManager} from "../network/network-manager" import {personalSign} from "./handlers/personal-sign" -import {PersonalSignParams, SignTypedDataParams, TypedData} from "../types/eth" +import { + AddEthereumChainParams, + PersonalSignParams, + SignTypedDataParams, + TypedData, +} from "../types/eth" import {signTypedData} from "./handlers/eth-signtypeddata" export class RpcProcessor { @@ -60,7 +65,15 @@ export class RpcProcessor { params as PersonalSignParams ) case "wallet_addEthereumChain": - return await this.networkManager.wallet_addEthereumChain() + // Expect params to be an array with one chain configuration object. + if (!params || !Array.isArray(params) || !params[0]) { + throw new Error( + "wallet_addEthereumChain requires an array with a chain configuration object." + ) + } + const chainConfig = params[0] as AddEthereumChainParams + + return await this.networkManager.wallet_addEthereumChain(chainConfig) default: return await this.gateway.request({ chainId, diff --git a/packages/fcl-ethereum-provider/src/types/eth.ts b/packages/fcl-ethereum-provider/src/types/eth.ts index 92652d89f..25f0ca059 100644 --- a/packages/fcl-ethereum-provider/src/types/eth.ts +++ b/packages/fcl-ethereum-provider/src/types/eth.ts @@ -26,3 +26,16 @@ export interface TypedData { primaryType: string message: Record } + +export interface AddEthereumChainParams { + chainId: string // Hex string, e.g. "0x1" + chainName: string + nativeCurrency: { + name: string + symbol: string + decimals: number + } + rpcUrls: string[] + blockExplorerUrls?: string[] + iconUrls?: string[] +} From 31df38bd0976baff445f6b5d6a310c226edd5da3 Mon Sep 17 00:00:00 2001 From: Chase Fleming Date: Tue, 4 Feb 2025 14:33:02 -0800 Subject: [PATCH 18/56] Implement wallet_switchEthereumChain (#2111) * Implement wallet_switchEthereumChain * Run prettier --------- Co-authored-by: Chase Fleming <1666730+chasefleming@users.noreply.github.com> --- .../src/network/network-manager.ts | 23 +++++++++++++++---- .../src/rpc/rpc-processor.ts | 12 +++++++++- .../fcl-ethereum-provider/src/types/eth.ts | 4 ++++ 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/packages/fcl-ethereum-provider/src/network/network-manager.ts b/packages/fcl-ethereum-provider/src/network/network-manager.ts index b69d94964..fc9790bb9 100644 --- a/packages/fcl-ethereum-provider/src/network/network-manager.ts +++ b/packages/fcl-ethereum-provider/src/network/network-manager.ts @@ -11,7 +11,7 @@ import { switchMap, } from "../util/observable" import * as fcl from "@onflow/fcl" -import {AddEthereumChainParams} from "../types/eth" +import {AddEthereumChainParams, SwitchEthereumChainParams} from "../types/eth" export type ChainIdStore = { isLoading: boolean @@ -86,9 +86,24 @@ export class NetworkManager { * No-op implementation for wallet_addEthereumChain. * Since FCL does support dynamic chain additions. */ - public async wallet_addEthereumChain( - _chainConfig: AddEthereumChainParams - ): Promise { + public async addChain(_chainConfig: AddEthereumChainParams): Promise { + return null + } + + public async switchChain(params: SwitchEthereumChainParams): Promise { + const activeChainId = await this.getChainId() + if (activeChainId === null) { + throw new Error("No active chain configured.") + } + + // Convert the chainId from hex (e.g., "0x64") to a number. + const requestedChainId = parseInt(params.chainId, 16) + + if (requestedChainId !== activeChainId) { + throw new Error( + "Network switch error: The requested chain ID does not match the currently configured network." + ) + } return null } } diff --git a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts index 255a20dcc..17b72959a 100644 --- a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts +++ b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts @@ -9,6 +9,7 @@ import { AddEthereumChainParams, PersonalSignParams, SignTypedDataParams, + SwitchEthereumChainParams, TypedData, } from "../types/eth" import {signTypedData} from "./handlers/eth-signtypeddata" @@ -73,7 +74,16 @@ export class RpcProcessor { } const chainConfig = params[0] as AddEthereumChainParams - return await this.networkManager.wallet_addEthereumChain(chainConfig) + return await this.networkManager.addChain(chainConfig) + case "wallet_switchEthereumChain": + // Expect params to be an array with one object. + if (!params || !Array.isArray(params) || !params[0]) { + throw new Error( + "wallet_switchEthereumChain requires an array with a chain configuration object." + ) + } + const switchParams = params[0] as SwitchEthereumChainParams + return await this.networkManager.switchChain(switchParams) default: return await this.gateway.request({ chainId, diff --git a/packages/fcl-ethereum-provider/src/types/eth.ts b/packages/fcl-ethereum-provider/src/types/eth.ts index 25f0ca059..5094dc7ca 100644 --- a/packages/fcl-ethereum-provider/src/types/eth.ts +++ b/packages/fcl-ethereum-provider/src/types/eth.ts @@ -39,3 +39,7 @@ export interface AddEthereumChainParams { blockExplorerUrls?: string[] iconUrls?: string[] } + +export interface SwitchEthereumChainParams { + chainId: string // Hex string, e.g., "0x64" +} From 2d651a36297e692b46d3467ae5315d1c7f9ca851 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> Date: Wed, 5 Feb 2025 13:31:34 -0800 Subject: [PATCH 19/56] Handle `eth_chainId` Locally in FCL Ethereum Provider (#2116) --- .../src/events/event-dispatcher.ts | 5 +-- .../src/rpc/handlers/eth-chain-id.test.ts | 33 +++++++++++++++++++ .../src/rpc/handlers/eth-chain-id.ts | 12 +++++++ .../src/rpc/rpc-processor.ts | 3 ++ .../fcl-ethereum-provider/src/util/eth.ts | 5 +++ 5 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 packages/fcl-ethereum-provider/src/rpc/handlers/eth-chain-id.test.ts create mode 100644 packages/fcl-ethereum-provider/src/rpc/handlers/eth-chain-id.ts create mode 100644 packages/fcl-ethereum-provider/src/util/eth.ts diff --git a/packages/fcl-ethereum-provider/src/events/event-dispatcher.ts b/packages/fcl-ethereum-provider/src/events/event-dispatcher.ts index e94cc7a47..081b36c67 100644 --- a/packages/fcl-ethereum-provider/src/events/event-dispatcher.ts +++ b/packages/fcl-ethereum-provider/src/events/event-dispatcher.ts @@ -9,6 +9,7 @@ import { Subscription, takeFirst, } from "../util/observable" +import {formatChainId} from "../util/eth" export class EventDispatcher { private $emitters: { @@ -33,7 +34,7 @@ export class EventDispatcher { chainChanged: networkManager.$chainId.pipe( filter(({isLoading, error}) => !isLoading && !error), map(({chainId}) => { - return `0x${chainId!.toString(16)}` + return formatChainId(chainId!) }), skip(1) ) as Observable, @@ -41,7 +42,7 @@ export class EventDispatcher { connect: networkManager.$chainId.pipe( filter(({isLoading, error}) => !isLoading && !error), map(({chainId}) => { - return {chainId: `0x${chainId!.toString(16)}`} + return {chainId: formatChainId(chainId!)} }), takeFirst() ), diff --git a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-chain-id.test.ts b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-chain-id.test.ts new file mode 100644 index 000000000..94d6e578f --- /dev/null +++ b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-chain-id.test.ts @@ -0,0 +1,33 @@ +import {formatChainId} from "../../util/eth" +import {ethChainId} from "./eth-chain-id" + +jest.mock("../../util/eth", () => ({ + ...jest.requireActual("../../util/eth"), + formatChainId: jest.fn().mockReturnValue("0x747"), +})) + +describe("eth_chainId handler", () => { + test("should return the formatted chain id", async () => { + const networkManagerMock = { + getChainId: jest.fn(), + } + networkManagerMock.getChainId.mockResolvedValue(747) + + const chainId = await ethChainId(networkManagerMock as any)() + + expect(chainId).toEqual("0x747") + expect(networkManagerMock.getChainId).toHaveBeenCalled() + }) + + test("should throw an error if no chain id is available", async () => { + const networkManagerMock = { + getChainId: jest.fn(), + } + networkManagerMock.getChainId.mockResolvedValue(null) + + await expect(ethChainId(networkManagerMock as any)()).rejects.toThrow( + "No active chain" + ) + expect(networkManagerMock.getChainId).toHaveBeenCalled() + }) +}) diff --git a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-chain-id.ts b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-chain-id.ts new file mode 100644 index 000000000..9246bcfe3 --- /dev/null +++ b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-chain-id.ts @@ -0,0 +1,12 @@ +import {NetworkManager} from "../../network/network-manager" +import {formatChainId} from "../../util/eth" + +export function ethChainId(networkManager: NetworkManager) { + return async function () { + const chainId = await networkManager.getChainId() + if (!chainId) { + throw new Error("No active chain") + } + return formatChainId(chainId) + } +} diff --git a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts index 17b72959a..31516a576 100644 --- a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts +++ b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts @@ -13,6 +13,7 @@ import { TypedData, } from "../types/eth" import {signTypedData} from "./handlers/eth-signtypeddata" +import {ethChainId} from "./handlers/eth-chain-id" export class RpcProcessor { constructor( @@ -84,6 +85,8 @@ export class RpcProcessor { } const switchParams = params[0] as SwitchEthereumChainParams return await this.networkManager.switchChain(switchParams) + case "eth_chainId": + return ethChainId(this.networkManager) default: return await this.gateway.request({ chainId, diff --git a/packages/fcl-ethereum-provider/src/util/eth.ts b/packages/fcl-ethereum-provider/src/util/eth.ts new file mode 100644 index 000000000..8cc15491e --- /dev/null +++ b/packages/fcl-ethereum-provider/src/util/eth.ts @@ -0,0 +1,5 @@ +export function formatChainId(chainId: string | number): `0x${string}` { + const numericChainId = + typeof chainId === "string" ? parseInt(chainId) : chainId + return `0x${numericChainId.toString(16)}` +} From e3dae26b3677f55d793bf09508aa866b5a353cf9 Mon Sep 17 00:00:00 2001 From: Chase Fleming Date: Wed, 5 Feb 2025 14:23:47 -0800 Subject: [PATCH 20/56] Setup disconnect (#2119) * Move to account manager * Setup disconnect * Run prettier * Fix test * Remove import * Push fix * Push fix --------- Co-authored-by: Chase Fleming <1666730+chasefleming@users.noreply.github.com> --- .../src/accounts/account-manager.ts | 8 +++++++ .../src/create-provider.ts | 6 ++++- .../fcl-ethereum-provider/src/provider.ts | 6 +++++ .../src/rpc/handlers/eth-accounts.test.ts | 22 +++---------------- .../src/rpc/handlers/eth-accounts.ts | 3 +-- 5 files changed, 23 insertions(+), 22 deletions(-) diff --git a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts index 7d470e105..4ff515efb 100644 --- a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts +++ b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts @@ -82,6 +82,14 @@ export class AccountManager { .subscribe(this.$addressStore) } + public async authenticate(): Promise { + return await this.user.authenticate() + } + + public async unauthenticate(): Promise { + await this.user.unauthenticate() + } + private async fetchCOAFromFlowAddress(flowAddr: string): Promise { const cadenceScript = ` import EVM diff --git a/packages/fcl-ethereum-provider/src/create-provider.ts b/packages/fcl-ethereum-provider/src/create-provider.ts index 73a7f9e34..da1f783df 100644 --- a/packages/fcl-ethereum-provider/src/create-provider.ts +++ b/packages/fcl-ethereum-provider/src/create-provider.ts @@ -50,7 +50,11 @@ export function createProvider(config: { }) const rpcProcessor = new RpcProcessor(gateway, accountManager, networkManager) const eventProcessor = new EventDispatcher(accountManager, networkManager) - const provider = new FclEthereumProvider(rpcProcessor, eventProcessor) + const provider = new FclEthereumProvider( + accountManager, + rpcProcessor, + eventProcessor + ) return provider } diff --git a/packages/fcl-ethereum-provider/src/provider.ts b/packages/fcl-ethereum-provider/src/provider.ts index 01ab36dc7..ee6d9ed34 100644 --- a/packages/fcl-ethereum-provider/src/provider.ts +++ b/packages/fcl-ethereum-provider/src/provider.ts @@ -7,9 +7,11 @@ import { } from "./types/provider" import {RpcProcessor} from "./rpc/rpc-processor" import {EventDispatcher} from "./events/event-dispatcher" +import {AccountManager} from "./accounts/account-manager" export class FclEthereumProvider implements Eip1193Provider { constructor( + private acountManager: AccountManager, private rpcProcessor: RpcProcessor, private eventDispatcher: EventDispatcher ) {} @@ -30,6 +32,10 @@ export class FclEthereumProvider implements Eip1193Provider { } } + disconnect(): void { + this.acountManager.unauthenticate() + } + // Listen to events on( event: E, diff --git a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.test.ts b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.test.ts index 181916eef..c849a9cd2 100644 --- a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.test.ts +++ b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.test.ts @@ -1,17 +1,5 @@ -// eth-accounts.spec.ts import {ethAccounts, ethRequestAccounts} from "./eth-accounts" import {AccountManager} from "../../accounts/account-manager" -import * as fcl from "@onflow/fcl" -import {CurrentUser} from "@onflow/typedefs" -import {mockUser} from "../../__mocks__/fcl" - -// Mock FCL at the top-level -jest.mock("@onflow/fcl", () => ({ - currentUser: jest.fn().mockReturnValue({ - authenticate: jest.fn(), - snapshot: jest.fn(), - }), -})) describe("ethAccounts handler", () => { let accountManagerMock: jest.Mocked @@ -45,14 +33,10 @@ describe("ethAccounts handler", () => { describe("ethRequestAccounts handler", () => { let accountManagerMock: jest.Mocked - let userMock: jest.Mocked beforeEach(() => { - userMock = fcl.currentUser() as jest.Mocked - - userMock.snapshot.mockResolvedValue({addr: null} as unknown as CurrentUser) - accountManagerMock = { + authenticate: jest.fn(), getAccounts: jest.fn(), updateCOAAddress: jest.fn(), subscribe: jest.fn(), @@ -64,7 +48,7 @@ describe("ethRequestAccounts handler", () => { const accounts = await ethRequestAccounts(accountManagerMock) - expect(userMock.authenticate).toHaveBeenCalled() + expect(accountManagerMock.authenticate).toHaveBeenCalled() expect(accountManagerMock.getAccounts).toHaveBeenCalled() expect(accounts).toEqual(["0x1234..."]) }) @@ -74,7 +58,7 @@ describe("ethRequestAccounts handler", () => { const accounts = await ethRequestAccounts(accountManagerMock) - expect(userMock.authenticate).toHaveBeenCalled() + expect(accountManagerMock.authenticate).toHaveBeenCalled() expect(accountManagerMock.getAccounts).toHaveBeenCalled() expect(accounts).toEqual([]) }) diff --git a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.ts b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.ts index 9ed4b2a9f..54508fdaf 100644 --- a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.ts +++ b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.ts @@ -1,4 +1,3 @@ -import * as fcl from "@onflow/fcl" import {AccountManager} from "../../accounts/account-manager" export async function ethAccounts( @@ -8,7 +7,7 @@ export async function ethAccounts( } export async function ethRequestAccounts(accountManager: AccountManager) { - await fcl.currentUser().authenticate() + await accountManager.authenticate() return await accountManager.getAccounts() } From d11dd4ecd82b3f6ead56e38d576a3e849029df3e Mon Sep 17 00:00:00 2001 From: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> Date: Wed, 5 Feb 2025 15:50:25 -0800 Subject: [PATCH 21/56] Add EVM contract address to scripts (#2121) --- .../src/accounts/account-manager.test.ts | 110 ++++++++++-------- .../src/accounts/account-manager.ts | 29 +++-- .../fcl-ethereum-provider/src/util/eth.ts | 27 +++++ 3 files changed, 111 insertions(+), 55 deletions(-) diff --git a/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts b/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts index c0492a14c..af911ef9c 100644 --- a/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts +++ b/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts @@ -3,6 +3,8 @@ import {mockUser} from "../__mocks__/fcl" import * as fcl from "@onflow/fcl" import * as rlp from "@onflow/rlp" import {CurrentUser} from "@onflow/typedefs" +import {ChainIdStore, NetworkManager} from "../network/network-manager" +import {BehaviorSubject, Subject} from "../util/observable" jest.mock("@onflow/fcl", () => { const fcl = jest.requireActual("@onflow/fcl") @@ -24,35 +26,41 @@ const mockFcl = jest.mocked(fcl) const mockQuery = jest.mocked(fcl.query) describe("AccountManager", () => { + let networkManager: jest.Mocked + let userMock: ReturnType + beforeEach(() => { jest.clearAllMocks() + + const chainId$ = new BehaviorSubject(747) + networkManager = { + $chainId: chainId$, + getChainId: () => chainId$.getValue(), + } as any as jest.Mocked + userMock = mockUser() }) it("should initialize with null COA address", async () => { - const user = mockUser() - const accountManager = new AccountManager(user.mock) + const accountManager = new AccountManager(userMock.mock, networkManager) expect(await accountManager.getCOAAddress()).toBeNull() expect(await accountManager.getAccounts()).toEqual([]) }) it("should reset state when the user is not logged in", async () => { - const user = mockUser() - - const accountManager = new AccountManager(user.mock) + const accountManager = new AccountManager(userMock.mock, networkManager) expect(await accountManager.getCOAAddress()).toBeNull() expect(await accountManager.getAccounts()).toEqual([]) }) it("should fetch and update COA address when user logs in", async () => { - const user = mockUser() mockQuery.mockResolvedValue("0x123") - const accountManager = new AccountManager(user.mock) + const accountManager = new AccountManager(userMock.mock, networkManager) expect(await accountManager.getCOAAddress()).toBe(null) - user.set!({addr: "0x1"} as CurrentUser) + userMock.set!({addr: "0x1"} as CurrentUser) expect(await accountManager.getCOAAddress()).toBe("0x123") expect(await accountManager.getAccounts()).toEqual(["0x123"]) @@ -63,26 +71,24 @@ describe("AccountManager", () => { }) it("should not update COA address if user has not changed", async () => { - const user = mockUser() mockQuery.mockResolvedValue("0x123") - const accountManager = new AccountManager(user.mock) + const accountManager = new AccountManager(userMock.mock, networkManager) - user.set!({addr: "0x1"} as CurrentUser) + userMock.set!({addr: "0x1"} as CurrentUser) await new Promise(setImmediate) expect(await accountManager.getCOAAddress()).toBe("0x123") expect(fcl.query).toHaveBeenCalledTimes(1) - user.set!({addr: "0x1"} as CurrentUser) + userMock.set!({addr: "0x1"} as CurrentUser) expect(await accountManager.getCOAAddress()).toBe("0x123") expect(fcl.query).toHaveBeenCalledTimes(1) // Should not have fetched again }) it("should not update COA address if fetch is outdated when user changes", async () => { - const user = mockUser() mockQuery.mockResolvedValue("0x123") mockQuery @@ -93,39 +99,36 @@ describe("AccountManager", () => { // 2nd fetch: immediate .mockResolvedValueOnce("0x456") - const accountManager = new AccountManager(user.mock) + const accountManager = new AccountManager(userMock.mock, networkManager) - await user.set!({addr: "0x1"} as CurrentUser) - await user.set!({addr: "0x2"} as CurrentUser) + await userMock.set!({addr: "0x1"} as CurrentUser) + await userMock.set!({addr: "0x2"} as CurrentUser) // The second fetch (for address 0x2) is the latest, so "0x456" expect(await accountManager.getCOAAddress()).toBe("0x456") }) it("should throw if COA address fetch fails", async () => { - const user = mockUser() mockQuery.mockRejectedValueOnce(new Error("Fetch failed")) - const accountManager = new AccountManager(user.mock) + const accountManager = new AccountManager(userMock.mock, networkManager) - await user.set!({addr: "0x1"} as CurrentUser) + await userMock.set!({addr: "0x1"} as CurrentUser) await expect(accountManager.getCOAAddress()).rejects.toThrow("Fetch failed") }) it("should handle user changes correctly", async () => { - const user = mockUser() - mockQuery .mockResolvedValueOnce("0x123") // for user 0x1 .mockResolvedValueOnce("0x456") // for user 0x2 - const accountManager = new AccountManager(user.mock) + const accountManager = new AccountManager(userMock.mock, networkManager) - await user.set({addr: "0x1"} as CurrentUser) + await userMock.set({addr: "0x1"} as CurrentUser) expect(await accountManager.getCOAAddress()).toBe("0x123") - await user.set({addr: "0x2"} as CurrentUser) + await userMock.set({addr: "0x2"} as CurrentUser) await new Promise(setImmediate) expect(await accountManager.getCOAAddress()).toBe("0x456") @@ -134,14 +137,12 @@ describe("AccountManager", () => { it("should call the callback with updated accounts in subscribe", async () => { mockQuery.mockResolvedValue("0x123") - const user = mockUser() - - const accountManager = new AccountManager(user.mock) + const accountManager = new AccountManager(userMock.mock, networkManager) const callback = jest.fn() accountManager.subscribe(callback) - user.set({addr: "0x1"} as CurrentUser) + userMock.set({addr: "0x1"} as CurrentUser) await new Promise(setImmediate) @@ -150,11 +151,10 @@ describe("AccountManager", () => { it("should reset accounts in subscribe if user is not authenticated", async () => { mockQuery.mockResolvedValue("0x123") - const user = mockUser() const callback = jest.fn() - const accountManager = new AccountManager(user.mock) + const accountManager = new AccountManager(userMock.mock, networkManager) accountManager.subscribe(callback) @@ -166,11 +166,11 @@ describe("AccountManager", () => { it("should call the callback when COA address is updated", async () => { const callback = jest.fn() - const user = mockUser({addr: "0x1"} as CurrentUser) - mockQuery.mockResolvedValueOnce("0x123") - const accountManager = new AccountManager(user.mock) + const accountManager = new AccountManager(userMock.mock, networkManager) + + userMock.set({addr: "0x1"} as CurrentUser) accountManager.subscribe(callback) @@ -180,23 +180,31 @@ describe("AccountManager", () => { }) it("should return an empty array when COA address is null", async () => { - const {mock: user} = mockUser() - const accountManager = new AccountManager(user) + const accountManager = new AccountManager(userMock.mock, networkManager) expect(await accountManager.getAccounts()).toEqual([]) }) it("should return COA address array when available", async () => { mockQuery.mockResolvedValueOnce("0x123") - const {mock: user} = mockUser({addr: "0x1"} as CurrentUser) + userMock.set({addr: "0x1"} as CurrentUser) - const accountManager = new AccountManager(user) + const accountManager = new AccountManager(userMock.mock, networkManager) expect(await accountManager.getAccounts()).toEqual(["0x123"]) }) }) describe("send transaction", () => { + let networkManager: jest.Mocked + let $mockChainId: BehaviorSubject + beforeEach(() => { + $mockChainId = new BehaviorSubject(747) + networkManager = { + $chainId: $mockChainId, + getChainId: () => $mockChainId.getValue(), + } as any as jest.Mocked + jest.clearAllMocks() }) @@ -219,7 +227,7 @@ describe("send transaction", () => { jest.mocked(fcl.query).mockResolvedValue("0x1234") const user = mockUser({addr: "0x4444"} as CurrentUser).mock - const accountManager = new AccountManager(user) + const accountManager = new AccountManager(user, networkManager) const tx = { to: "0x1234", @@ -248,6 +256,9 @@ describe("send transaction", () => { }) test("send transaction testnet", async () => { + // Set chainId to testnet + $mockChainId.next(646) + const mockTxResult = { onceExecuted: jest.fn().mockResolvedValue({ events: [ @@ -266,7 +277,7 @@ describe("send transaction", () => { jest.mocked(fcl.query).mockResolvedValue("0x1234") const user = mockUser({addr: "0x4444"} as CurrentUser) - const accountManager = new AccountManager(user.mock) + const accountManager = new AccountManager(user.mock, networkManager) const tx = { to: "0x1234", @@ -306,7 +317,7 @@ describe("send transaction", () => { jest.mocked(fcl.query).mockResolvedValue("0x1234") const user = mockUser({addr: "0x4444"} as CurrentUser) - const accountManager = new AccountManager(user.mock) + const accountManager = new AccountManager(user.mock, networkManager) const tx = { to: "0x1234", @@ -315,7 +326,7 @@ describe("send transaction", () => { data: "0x1234", nonce: "0", gas: "0", - chainId: "646", + chainId: "747", } await expect(accountManager.sendTransaction(tx)).rejects.toThrow( @@ -326,7 +337,7 @@ describe("send transaction", () => { test("throws error if from address does not match user address", async () => { jest.mocked(fcl.query).mockResolvedValue("0x1234") const user = mockUser({addr: "0x4444"} as CurrentUser) - const accountManager = new AccountManager(user.mock) + const accountManager = new AccountManager(user.mock, networkManager) const tx = { to: "0x1234", @@ -339,7 +350,7 @@ describe("send transaction", () => { } await expect(accountManager.sendTransaction(tx)).rejects.toThrow( - `From address does not match authenticated user address.\nUser: 0x1234\nFrom: 0x4567` + `Chain ID does not match the current network. Expected: 747, Received: 646` ) expect(fcl.mutate).not.toHaveBeenCalled() @@ -347,6 +358,7 @@ describe("send transaction", () => { }) describe("signMessage", () => { + let networkManager: jest.Mocked let accountManager: AccountManager let user: ReturnType["mock"] let updateUser: ReturnType["set"] @@ -355,7 +367,12 @@ describe("signMessage", () => { jest.clearAllMocks() ;({mock: user, set: updateUser} = mockUser({addr: "0x123"} as CurrentUser)) jest.mocked(fcl.query).mockResolvedValue("0xCOA1") - accountManager = new AccountManager(user) + const $mockChainId = new BehaviorSubject(747) + networkManager = { + $chainId: $mockChainId, + getChainId: () => $mockChainId.getValue(), + } as any as jest.Mocked + accountManager = new AccountManager(user, networkManager) }) it("should throw an error if the COA address is not available", async () => { @@ -400,10 +417,7 @@ describe("signMessage", () => { }) it("should throw an error if signUserMessage returns an empty array", async () => { - accountManager["coaAddress"] = "0xCOA1" - - user.signUserMessage = jest.fn().mockResolvedValue([]) - + user.signUserMessage.mockResolvedValue([]) await expect( accountManager.signMessage("Test message", "0xCOA1") ).rejects.toThrow("Failed to sign message") diff --git a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts index 4ff515efb..804d00ca7 100644 --- a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts +++ b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts @@ -24,6 +24,8 @@ import { switchMap, } from "../util/observable" import {EthSignatureResponse} from "../types/eth" +import {NetworkManager} from "../network/network-manager" +import {formatChainId, getContractAddress} from "../util/eth" export class AccountManager { private $addressStore = new BehaviorSubject<{ @@ -36,7 +38,10 @@ export class AccountManager { error: null, }) - constructor(private user: typeof fcl.currentUser) { + constructor( + private user: typeof fcl.currentUser, + private networkManager: NetworkManager + ) { // Create an observable from the user const $user = new Observable(subscriber => { return this.user.subscribe((currentUser: CurrentUser, error?: Error) => { @@ -91,8 +96,13 @@ export class AccountManager { } private async fetchCOAFromFlowAddress(flowAddr: string): Promise { + const chainId = await this.networkManager.getChainId() + if (!chainId) { + throw new Error("No active chain") + } + const cadenceScript = ` - import EVM + import EVM from ${getContractAddress(ContractType.EVM, chainId)} access(all) fun main(address: Address): String? { @@ -156,17 +166,22 @@ export class AccountManager { chainId: string }) { // Find the Flow network based on the chain ID + const parsedChainId = parseInt(chainId) const flowNetwork = Object.entries(FLOW_CHAINS).find( - ([, chain]) => chain.eip155ChainId === parseInt(chainId) + ([, chain]) => chain.eip155ChainId === parsedChainId )?.[0] as FlowNetwork | undefined if (!flowNetwork) { throw new Error("Flow network not found for chain ID") } - const evmContractAddress = fcl.withPrefix( - FLOW_CONTRACTS[ContractType.EVM][flowNetwork] - ) + // Validate the chain ID + const currentChainId = await this.networkManager.getChainId() + if (parsedChainId !== currentChainId) { + throw new Error( + `Chain ID does not match the current network. Expected: ${currentChainId}, Received: ${parsedChainId}` + ) + } // Check if the from address matches the authenticated COA address const expectedCOAAddress = await this.getCOAAddress() @@ -177,7 +192,7 @@ export class AccountManager { } const txId = await fcl.mutate({ - cadence: `import EVM from ${evmContractAddress} + cadence: `import EVM from ${getContractAddress(ContractType.EVM, parsedChainId)} /// Executes the calldata from the signer's COA /// diff --git a/packages/fcl-ethereum-provider/src/util/eth.ts b/packages/fcl-ethereum-provider/src/util/eth.ts index 8cc15491e..98ad6499b 100644 --- a/packages/fcl-ethereum-provider/src/util/eth.ts +++ b/packages/fcl-ethereum-provider/src/util/eth.ts @@ -1,5 +1,32 @@ +import { + ContractType, + FLOW_CHAINS, + FLOW_CONTRACTS, + FlowNetwork, +} from "../constants" +import * as fcl from "@onflow/fcl" + export function formatChainId(chainId: string | number): `0x${string}` { const numericChainId = typeof chainId === "string" ? parseInt(chainId) : chainId return `0x${numericChainId.toString(16)}` } + +export function getContractAddress( + contractType: ContractType, + chainId: number +) { + // Find the Flow network based on the chain ID + const flowNetwork = Object.entries(FLOW_CHAINS).find( + ([, chain]) => chain.eip155ChainId === chainId + )?.[0] as FlowNetwork | undefined + + if (!flowNetwork) { + throw new Error("Flow network not found for chain ID") + } + + const evmContractAddress = fcl.withPrefix( + FLOW_CONTRACTS[contractType][flowNetwork] + ) + return evmContractAddress +} From e37469b2f1d86d6ff2e09391cca72edf6f3f879e Mon Sep 17 00:00:00 2001 From: Chase Fleming Date: Thu, 6 Feb 2025 13:32:12 -0800 Subject: [PATCH 22/56] Create COA on request accounts (#2120) * Create COA on request accounts * Add tests * Fix test * Remove * Update test * Refactor * Add events * Fix tests * Run prettier * Fix * Move tx * Move cadence --------- Co-authored-by: Chase Fleming <1666730+chasefleming@users.noreply.github.com> --- .../src/accounts/account-manager.test.ts | 60 ++++++-- .../src/accounts/account-manager.ts | 143 +++++++++++------- packages/fcl-ethereum-provider/src/cadence.ts | 62 ++++++++ .../fcl-ethereum-provider/src/constants.ts | 5 + .../src/create-provider.ts | 2 +- .../src/rpc/handlers/eth-accounts.test.ts | 9 +- .../src/rpc/handlers/eth-accounts.ts | 7 +- .../src/rpc/rpc-processor.ts | 2 +- 8 files changed, 215 insertions(+), 75 deletions(-) create mode 100644 packages/fcl-ethereum-provider/src/cadence.ts diff --git a/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts b/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts index af911ef9c..a15147d8e 100644 --- a/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts +++ b/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts @@ -3,8 +3,8 @@ import {mockUser} from "../__mocks__/fcl" import * as fcl from "@onflow/fcl" import * as rlp from "@onflow/rlp" import {CurrentUser} from "@onflow/typedefs" -import {ChainIdStore, NetworkManager} from "../network/network-manager" -import {BehaviorSubject, Subject} from "../util/observable" +import {NetworkManager} from "../network/network-manager" +import {BehaviorSubject} from "../util/observable" jest.mock("@onflow/fcl", () => { const fcl = jest.requireActual("@onflow/fcl") @@ -30,7 +30,7 @@ describe("AccountManager", () => { let userMock: ReturnType beforeEach(() => { - jest.clearAllMocks() + jest.resetAllMocks() const chainId$ = new BehaviorSubject(747) networkManager = { @@ -118,6 +118,51 @@ describe("AccountManager", () => { await expect(accountManager.getCOAAddress()).rejects.toThrow("Fetch failed") }) + it("getAndCreateAccounts should get a COA address if it already exists", async () => { + mockQuery.mockResolvedValue("0x123") + + const accountManager = new AccountManager(userMock.mock, networkManager) + + // Trigger the state update + await userMock.set!({addr: "0x1"} as CurrentUser) + + // Call getAndCreateAccounts. Since the COA already exists, it should just return it. + const accounts = await accountManager.getAndCreateAccounts(646) + + expect(accounts).toEqual(["0x123"]) + // Should not have created a new COA + expect(fcl.mutate).not.toHaveBeenCalled() + }) + + it("getAndCreateAccounts should create a COA if it does not exist", async () => { + const mockTxResult = { + onceExecuted: jest.fn().mockResolvedValue({ + events: [ + { + type: "A.e467b9dd11fa00df.EVM.CadenceOwnedAccountCreated", + data: { + address: "0x123", + }, + }, + ], + }), + } as any as jest.Mocked> + + jest.mocked(fcl.tx).mockReturnValue(mockTxResult) + jest.mocked(fcl.mutate).mockResolvedValue("1111") + + // For the subscription, simulate that initially no COA is found, then after creation the query returns "0x123" + mockQuery.mockResolvedValueOnce(null).mockResolvedValueOnce("0x123") + + const accountManager = new AccountManager(userMock.mock, networkManager) + + await userMock.set!({addr: "0x1"} as CurrentUser) + + const accounts = await accountManager.getAndCreateAccounts(747) + expect(accounts).toEqual(["0x123"]) + expect(fcl.mutate).toHaveBeenCalled() + }) + it("should handle user changes correctly", async () => { mockQuery .mockResolvedValueOnce("0x123") // for user 0x1 @@ -130,7 +175,6 @@ describe("AccountManager", () => { await userMock.set({addr: "0x2"} as CurrentUser) - await new Promise(setImmediate) expect(await accountManager.getCOAAddress()).toBe("0x456") }) @@ -142,9 +186,7 @@ describe("AccountManager", () => { const callback = jest.fn() accountManager.subscribe(callback) - userMock.set({addr: "0x1"} as CurrentUser) - - await new Promise(setImmediate) + await userMock.set({addr: "0x1"} as CurrentUser) expect(callback).toHaveBeenCalledWith(["0x123"]) }) @@ -205,7 +247,7 @@ describe("send transaction", () => { getChainId: () => $mockChainId.getValue(), } as any as jest.Mocked - jest.clearAllMocks() + jest.resetAllMocks() }) test("send transaction mainnet", async () => { @@ -364,7 +406,7 @@ describe("signMessage", () => { let updateUser: ReturnType["set"] beforeEach(() => { - jest.clearAllMocks() + jest.resetAllMocks() ;({mock: user, set: updateUser} = mockUser({addr: "0x123"} as CurrentUser)) jest.mocked(fcl.query).mockResolvedValue("0xCOA1") const $mockChainId = new BehaviorSubject(747) diff --git a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts index 804d00ca7..07aa81c2f 100644 --- a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts +++ b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts @@ -26,6 +26,7 @@ import { import {EthSignatureResponse} from "../types/eth" import {NetworkManager} from "../network/network-manager" import {formatChainId, getContractAddress} from "../util/eth" +import {createCOATx, getCOAScript, sendTransactionTx} from "../cadence" export class AccountManager { private $addressStore = new BehaviorSubject<{ @@ -95,36 +96,32 @@ export class AccountManager { await this.user.unauthenticate() } + private async waitForTxResult( + txId: string, + eventType: string, + errorMsg: string = `${eventType} event not found` + ): Promise { + const txResult = await fcl.tx(txId).onceExecuted() + + const event = txResult.events.find(e => e.type === eventType) + if (!event) { + throw new Error(errorMsg) + } + return event + } + private async fetchCOAFromFlowAddress(flowAddr: string): Promise { const chainId = await this.networkManager.getChainId() if (!chainId) { throw new Error("No active chain") } - const cadenceScript = ` - import EVM from ${getContractAddress(ContractType.EVM, chainId)} - - access(all) - fun main(address: Address): String? { - if let coa = getAuthAccount(address) - .storage - .borrow<&EVM.CadenceOwnedAccount>(from: /storage/evm) { - return coa.address().toString() - } - return nil - } - ` - const response = await fcl.query({ - cadence: cadenceScript, + return await fcl.query({ + cadence: getCOAScript(chainId), args: (arg: typeof fcl.arg, t: typeof fcl.t) => [ arg(flowAddr, t.Address), ], }) - - if (!response) { - throw new Error("COA account not found for the authenticated user") - } - return response as string } public async getCOAAddress(): Promise { @@ -142,6 +139,68 @@ export class AccountManager { return coaAddress ? [coaAddress] : [] } + /** + * Get the COA address and create it if it doesn't exist + */ + public async getAndCreateAccounts(chainId: number): Promise { + const accounts = await this.getAccounts() + + if (accounts.length === 0) { + const coaAddress = await this.createCOA(chainId) + return [coaAddress] + } + + if (accounts.length === 0) { + throw new Error("COA address is still missing after creation.") + } + + return accounts + } + + public async createCOA(chainId: number): Promise { + // Find the Flow network based on the chain ID + const flowNetwork = Object.entries(FLOW_CHAINS).find( + ([, chain]) => chain.eip155ChainId === chainId + )?.[0] as FlowNetwork | undefined + + if (!flowNetwork) { + throw new Error("Flow network not found for chain ID") + } + + // Validate the chain ID + const currentChainId = await this.networkManager.getChainId() + if (chainId !== currentChainId) { + throw new Error( + `Chain ID does not match the current network. Expected: ${currentChainId}, Received: ${chainId}` + ) + } + + const txId = await fcl.mutate({ + cadence: createCOATx(chainId), + limit: 9999, + authz: this.user, + }) + + const event = await this.waitForTxResult( + txId, + EVENT_IDENTIFIERS[EventType.CADENCE_OWNED_ACCOUNT_CREATED][flowNetwork], + "Failed to create COA: COACreated event not found" + ) + + const coaAddress = event.data.address + if (!coaAddress) { + throw new Error("COA created event did not include an address") + } + + this.$addressStore.next({ + isLoading: false, + address: coaAddress, + error: null, + }) + + return coaAddress + } + public subscribe(callback: (accounts: string[]) => void): Subscription { return this.$addressStore .pipe(filter(x => !x.isLoading && !x.error)) @@ -192,33 +251,7 @@ export class AccountManager { } const txId = await fcl.mutate({ - cadence: `import EVM from ${getContractAddress(ContractType.EVM, parsedChainId)} - - /// Executes the calldata from the signer's COA - /// - transaction(evmContractAddressHex: String, calldata: String, gasLimit: UInt64, value: UInt256) { - - let evmAddress: EVM.EVMAddress - let coa: auth(EVM.Call) &EVM.CadenceOwnedAccount - - prepare(signer: auth(BorrowValue) &Account) { - self.evmAddress = EVM.addressFromString(evmContractAddressHex) - - self.coa = signer.storage.borrow(from: /storage/evm) - ?? panic("Could not borrow COA from provided gateway address") - } - - execute { - let valueBalance = EVM.Balance(attoflow: value) - let callResult = self.coa.call( - to: self.evmAddress, - data: calldata.decodeHex(), - gasLimit: gasLimit, - value: valueBalance - ) - assert(callResult.status == EVM.Status.successful, message: "Call failed") - } - }`, + cadence: sendTransactionTx(parsedChainId), limit: 9999, args: (arg: typeof fcl.arg, t: typeof fcl.t) => [ arg(to, t.String), @@ -229,19 +262,13 @@ export class AccountManager { authz: this.user, }) - const result = await fcl.tx(txId).onceExecuted() - const {events} = result - - const evmTxExecutedEvent = events.find( - event => - event.type === - EVENT_IDENTIFIERS[EventType.TRANSACTION_EXECUTED][flowNetwork] + const event = await this.waitForTxResult( + txId, + EVENT_IDENTIFIERS[EventType.TRANSACTION_EXECUTED][flowNetwork], + "EVM transaction hash not found" ) - if (!evmTxExecutedEvent) { - throw new Error("EVM transaction hash not found") - } - const eventData: TransactionExecutedEvent = evmTxExecutedEvent.data + const eventData: TransactionExecutedEvent = event.data const evmTxHash = eventData.hash .map(h => parseInt(h, 16).toString().padStart(2, "0")) .join("") diff --git a/packages/fcl-ethereum-provider/src/cadence.ts b/packages/fcl-ethereum-provider/src/cadence.ts new file mode 100644 index 000000000..fc821101f --- /dev/null +++ b/packages/fcl-ethereum-provider/src/cadence.ts @@ -0,0 +1,62 @@ +import {getContractAddress} from "./util/eth" +import {ContractType} from "./constants" + +export const getCOAScript = (chainId: number) => ` +import EVM from ${getContractAddress(ContractType.EVM, chainId)} + +access(all) +fun main(address: Address): String? { + if let coa = getAuthAccount(address) + .storage + .borrow<&EVM.CadenceOwnedAccount>(from: /storage/evm) { + return coa.address().toString() + } + return nil +} +` + +export const createCOATx = (chainId: number) => ` +import EVM from ${getContractAddress(ContractType.EVM, chainId)} + +transaction() { + prepare(signer: auth(SaveValue, IssueStorageCapabilityController, PublishCapability) &Account) { + let storagePath = /storage/evm + let publicPath = /public/evm + + let coa: @EVM.CadenceOwnedAccount <- EVM.createCadenceOwnedAccount() + signer.storage.save(<-coa, to: storagePath) + + let cap = signer.capabilities.storage.issue<&EVM.CadenceOwnedAccount>(storagePath) + signer.capabilities.publish(cap, at: publicPath) + } +} +` + +export const sendTransactionTx = (chainId: number) => ` +import EVM from ${getContractAddress(ContractType.EVM, chainId)} + +/// Executes the calldata from the signer's COA +transaction(evmContractAddressHex: String, calldata: String, gasLimit: UInt64, value: UInt256) { + + let evmAddress: EVM.EVMAddress + let coa: auth(EVM.Call) &EVM.CadenceOwnedAccount + + prepare(signer: auth(BorrowValue) &Account) { + self.evmAddress = EVM.addressFromString(evmContractAddressHex) + + self.coa = signer.storage.borrow(from: /storage/evm) + ?? panic("Could not borrow COA from provided gateway address") + } + + execute { + let valueBalance = EVM.Balance(attoflow: value) + let callResult = self.coa.call( + to: self.evmAddress, + data: calldata.decodeHex(), + gasLimit: gasLimit, + value: valueBalance + ) + assert(callResult.status == EVM.Status.successful, message: "Call failed") + } +} +` diff --git a/packages/fcl-ethereum-provider/src/constants.ts b/packages/fcl-ethereum-provider/src/constants.ts index c9c05a4eb..9996971d4 100644 --- a/packages/fcl-ethereum-provider/src/constants.ts +++ b/packages/fcl-ethereum-provider/src/constants.ts @@ -19,6 +19,7 @@ export enum ContractType { } export enum EventType { + CADENCE_OWNED_ACCOUNT_CREATED = "CADENCE_OWNED_ACCOUNT_CREATED", TRANSACTION_EXECUTED = "TRANSACTION_EXECUTED", } @@ -27,6 +28,10 @@ export const EVENT_IDENTIFIERS = { [FlowNetwork.TESTNET]: "A.8c5303eaa26202d6.EVM.TransactionExecuted", [FlowNetwork.MAINNET]: "A.e467b9dd11fa00df.EVM.TransactionExecuted", }, + [EventType.CADENCE_OWNED_ACCOUNT_CREATED]: { + [FlowNetwork.TESTNET]: "A.8c5303eaa26202d6.EVM.CadenceOwnedAccountCreated", + [FlowNetwork.MAINNET]: "A.e467b9dd11fa00df.EVM.CadenceOwnedAccountCreated", + }, } export const FLOW_CONTRACTS = { diff --git a/packages/fcl-ethereum-provider/src/create-provider.ts b/packages/fcl-ethereum-provider/src/create-provider.ts index da1f783df..185f83668 100644 --- a/packages/fcl-ethereum-provider/src/create-provider.ts +++ b/packages/fcl-ethereum-provider/src/create-provider.ts @@ -43,7 +43,7 @@ export function createProvider(config: { ) const networkManager = new NetworkManager(config.config) - const accountManager = new AccountManager(config.user) + const accountManager = new AccountManager(config.user, networkManager) const gateway = new Gateway({ ...defaultRpcUrls, ...(config.rpcUrls || {}), diff --git a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.test.ts b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.test.ts index c849a9cd2..523138234 100644 --- a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.test.ts +++ b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.test.ts @@ -38,28 +38,29 @@ describe("ethRequestAccounts handler", () => { accountManagerMock = { authenticate: jest.fn(), getAccounts: jest.fn(), + getAndCreateAccounts: jest.fn(), updateCOAAddress: jest.fn(), subscribe: jest.fn(), } as unknown as jest.Mocked }) it("should call authenticate, updateCOAAddress, and return the manager's accounts", async () => { - accountManagerMock.getAccounts.mockResolvedValue(["0x1234..."]) + accountManagerMock.getAndCreateAccounts.mockResolvedValue(["0x1234..."]) const accounts = await ethRequestAccounts(accountManagerMock) expect(accountManagerMock.authenticate).toHaveBeenCalled() - expect(accountManagerMock.getAccounts).toHaveBeenCalled() + expect(accountManagerMock.getAndCreateAccounts).toHaveBeenCalled() expect(accounts).toEqual(["0x1234..."]) }) it("should handle empty accounts scenario", async () => { - accountManagerMock.getAccounts.mockResolvedValue([]) + accountManagerMock.getAndCreateAccounts.mockResolvedValue([]) const accounts = await ethRequestAccounts(accountManagerMock) expect(accountManagerMock.authenticate).toHaveBeenCalled() - expect(accountManagerMock.getAccounts).toHaveBeenCalled() + expect(accountManagerMock.getAndCreateAccounts).toHaveBeenCalled() expect(accounts).toEqual([]) }) }) diff --git a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.ts b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.ts index 54508fdaf..0e83bd4a4 100644 --- a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.ts +++ b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.ts @@ -6,8 +6,11 @@ export async function ethAccounts( return await accountManager.getAccounts() } -export async function ethRequestAccounts(accountManager: AccountManager) { +export async function ethRequestAccounts( + accountManager: AccountManager, + chainId: number +): Promise { await accountManager.authenticate() - return await accountManager.getAccounts() + return await accountManager.getAndCreateAccounts(chainId) } diff --git a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts index 31516a576..f1f621dac 100644 --- a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts +++ b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts @@ -32,7 +32,7 @@ export class RpcProcessor { case "eth_accounts": return ethAccounts(this.accountManager) case "eth_requestAccounts": - return ethRequestAccounts(this.accountManager) + return ethRequestAccounts(this.accountManager, chainId) case "eth_sendTransaction": return await ethSendTransaction(this.accountManager, params) case "eth_signTypedData": From d8d752322e561474cf61e4d44b5cd75c05c404a7 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> Date: Thu, 6 Feb 2025 15:16:21 -0800 Subject: [PATCH 23/56] Adhere to EIP-1193 / EIP-1474 Error Format (#2117) --- .../fcl-ethereum-provider/src/provider.ts | 13 +- .../src/rpc/rpc-processor.test.ts | 55 +++++++ .../src/rpc/rpc-processor.ts | 136 ++++++++++-------- .../fcl-ethereum-provider/src/util/errors.ts | 38 +++++ 4 files changed, 172 insertions(+), 70 deletions(-) create mode 100644 packages/fcl-ethereum-provider/src/util/errors.ts diff --git a/packages/fcl-ethereum-provider/src/provider.ts b/packages/fcl-ethereum-provider/src/provider.ts index ee6d9ed34..617b74f29 100644 --- a/packages/fcl-ethereum-provider/src/provider.ts +++ b/packages/fcl-ethereum-provider/src/provider.ts @@ -7,6 +7,7 @@ import { } from "./types/provider" import {RpcProcessor} from "./rpc/rpc-processor" import {EventDispatcher} from "./events/event-dispatcher" +import {ProviderError, ProviderErrorCode} from "./util/errors" import {AccountManager} from "./accounts/account-manager" export class FclEthereumProvider implements Eip1193Provider { @@ -21,15 +22,11 @@ export class FclEthereumProvider implements Eip1193Provider { method, params, }: ProviderRequest): Promise> { - try { - if (!method) { - throw new Error("Method is required") - } - const result = await this.rpcProcessor.handleRequest({method, params}) - return result - } catch (error) { - throw new Error(`Request failed: ${(error as Error).message}`) + if (!method) { + throw new Error("Method is required") } + const result = await this.rpcProcessor.handleRequest({method, params}) + return result } disconnect(): void { diff --git a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.test.ts b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.test.ts index eb3749c2e..0e0c3480a 100644 --- a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.test.ts +++ b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.test.ts @@ -67,4 +67,59 @@ describe("rpc processor", () => { chainId: 646, }) }) + + test("caught RpcError should be rethrown", async () => { + const gateway: jest.Mocked = new (Gateway as any)() + const accountManager: jest.Mocked = + new (AccountManager as any)() + const networkManager: jest.Mocked = + new (NetworkManager as any)() + const rpcProcessor = new RpcProcessor( + gateway, + accountManager, + networkManager + ) + + const error = new Error("test error") + ;(error as any).code = -32000 + jest.mocked(gateway).request.mockRejectedValue(error) + networkManager.getChainId.mockResolvedValue(747) + + await expect( + rpcProcessor.handleRequest({ + method: "eth_blockNumber", + params: [], + }) + ).rejects.toMatchObject({ + code: -32000, + message: "test error", + }) + }) + + test("caught generic error should be rethrown as an internal error", async () => { + const gateway: jest.Mocked = new (Gateway as any)() + const accountManager: jest.Mocked = + new (AccountManager as any)() + const networkManager: jest.Mocked = + new (NetworkManager as any)() + const rpcProcessor = new RpcProcessor( + gateway, + accountManager, + networkManager + ) + + jest.mocked(gateway).request.mockRejectedValue(new Error("test error")) + networkManager.getChainId.mockResolvedValue(747) + + const promise = rpcProcessor.handleRequest({ + method: "eth_blockNumber", + params: [], + }) + + await expect(promise).rejects.toMatchObject({ + code: -32603, + message: "Internal error", + cause: new Error("test error"), + }) + }) }) diff --git a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts index f1f621dac..ba50102b7 100644 --- a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts +++ b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts @@ -14,6 +14,7 @@ import { } from "../types/eth" import {signTypedData} from "./handlers/eth-signtypeddata" import {ethChainId} from "./handlers/eth-chain-id" +import {ProviderError, ProviderErrorCode} from "../util/errors" export class RpcProcessor { constructor( @@ -23,76 +24,87 @@ export class RpcProcessor { ) {} async handleRequest({method, params}: ProviderRequest): Promise { - const chainId = await this.networkManager.getChainId() - if (!chainId) { - throw new Error("No active chain") - } + try { + const chainId = await this.networkManager.getChainId() + if (!chainId) { + throw new Error("No active chain") + } - switch (method) { - case "eth_accounts": - return ethAccounts(this.accountManager) - case "eth_requestAccounts": - return ethRequestAccounts(this.accountManager, chainId) - case "eth_sendTransaction": - return await ethSendTransaction(this.accountManager, params) - case "eth_signTypedData": - case "eth_signTypedData_v3": - case "eth_signTypedData_v4": { - if (!params || typeof params !== "object") { - throw new Error(`${method} requires valid parameters.`) - } + switch (method) { + case "eth_accounts": + return ethAccounts(this.accountManager) + case "eth_requestAccounts": + return ethRequestAccounts(this.accountManager, chainId) + case "eth_sendTransaction": + return await ethSendTransaction(this.accountManager, params) + case "eth_signTypedData": + case "eth_signTypedData_v3": + case "eth_signTypedData_v4": { + if (!params || typeof params !== "object") { + throw new Error(`${method} requires valid parameters.`) + } - const {address, data} = params as {address?: unknown; data?: unknown} + const {address, data} = params as {address?: unknown; data?: unknown} - if ( - typeof address !== "string" || - typeof data !== "object" || - data === null - ) { - throw new Error( - `${method} requires 'address' (string) and a valid 'data' object.` - ) - } + if ( + typeof address !== "string" || + typeof data !== "object" || + data === null + ) { + throw new Error( + `${method} requires 'address' (string) and a valid 'data' object.` + ) + } - const validParams: SignTypedDataParams = { - address, - data: data as TypedData, - } + const validParams: SignTypedDataParams = { + address, + data: data as TypedData, + } - return await signTypedData(this.accountManager, validParams, method) - } - case "personal_sign": - return await personalSign( - this.accountManager, - params as PersonalSignParams - ) - case "wallet_addEthereumChain": - // Expect params to be an array with one chain configuration object. - if (!params || !Array.isArray(params) || !params[0]) { - throw new Error( - "wallet_addEthereumChain requires an array with a chain configuration object." - ) + return await signTypedData(this.accountManager, validParams, method) } - const chainConfig = params[0] as AddEthereumChainParams - - return await this.networkManager.addChain(chainConfig) - case "wallet_switchEthereumChain": - // Expect params to be an array with one object. - if (!params || !Array.isArray(params) || !params[0]) { - throw new Error( - "wallet_switchEthereumChain requires an array with a chain configuration object." + case "personal_sign": + return await personalSign( + this.accountManager, + params as PersonalSignParams ) - } - const switchParams = params[0] as SwitchEthereumChainParams - return await this.networkManager.switchChain(switchParams) - case "eth_chainId": - return ethChainId(this.networkManager) - default: - return await this.gateway.request({ - chainId, - method, - params, + case "wallet_addEthereumChain": + // Expect params to be an array with one chain configuration object. + if (!params || !Array.isArray(params) || !params[0]) { + throw new Error( + "wallet_addEthereumChain requires an array with a chain configuration object." + ) + } + const chainConfig = params[0] as AddEthereumChainParams + + return await this.networkManager.addChain(chainConfig) + case "wallet_switchEthereumChain": + // Expect params to be an array with one object. + if (!params || !Array.isArray(params) || !params[0]) { + throw new Error( + "wallet_switchEthereumChain requires an array with a chain configuration object." + ) + } + const switchParams = params[0] as SwitchEthereumChainParams + return await this.networkManager.switchChain(switchParams) + case "eth_chainId": + return ethChainId(this.networkManager) + default: + return await this.gateway.request({ + chainId, + method, + params, + }) + } + } catch (error: any) { + if (error?.code !== undefined) { + throw error + } else { + throw new ProviderError({ + code: ProviderErrorCode.InternalError, + cause: error, }) + } } } } diff --git a/packages/fcl-ethereum-provider/src/util/errors.ts b/packages/fcl-ethereum-provider/src/util/errors.ts new file mode 100644 index 000000000..42466a55f --- /dev/null +++ b/packages/fcl-ethereum-provider/src/util/errors.ts @@ -0,0 +1,38 @@ +export enum ProviderErrorCode { + // EIP-1193 error codes + UserRejectedRequest = 4001, + Unauthorized = 4100, + UnsupportedMethod = 4200, + Disconnected = 4900, + + // EIP-1474 / JSON-RPC error codes + ParseError = -32700, + InvalidRequest = -32600, + MethodNotFound = -32601, + InvalidParams = -32602, + InternalError = -32603, +} +export const ProviderErrorMessage: Record = { + // EIP-1193 error messages + [4001]: "User rejected request", + [4100]: "Unauthorized", + [4200]: "Unsupported method", + [4900]: "Disconnected", + // EIP-1474 / JSON-RPC error messages + [-32700]: "Parse error", + [-32600]: "Invalid request", + [-32601]: "Method not found", + [-32602]: "Invalid params", + [-32603]: "Internal error", +} + +export class ProviderError extends Error { + public code: ProviderErrorCode + public cause?: any + + constructor({code, cause}: {code: ProviderErrorCode; cause?: any}) { + super(ProviderErrorMessage[code]) + this.code = code + this.cause = cause + } +} From 5cead7232a95e413ec20648601c2dde23871d8f3 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> Date: Thu, 6 Feb 2025 15:46:07 -0800 Subject: [PATCH 24/56] Fix testnet chain ID (#2124) --- .../src/accounts/account-manager.test.ts | 10 +++++----- packages/fcl-ethereum-provider/src/constants.ts | 2 +- .../src/gateway/gateway.test.ts | 16 ++++++++-------- .../src/network/network-manager.test.ts | 2 +- .../src/rpc/rpc-processor.test.ts | 4 ++-- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts b/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts index a15147d8e..54f2a3167 100644 --- a/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts +++ b/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts @@ -127,7 +127,7 @@ describe("AccountManager", () => { await userMock.set!({addr: "0x1"} as CurrentUser) // Call getAndCreateAccounts. Since the COA already exists, it should just return it. - const accounts = await accountManager.getAndCreateAccounts(646) + const accounts = await accountManager.getAndCreateAccounts(545) expect(accounts).toEqual(["0x123"]) // Should not have created a new COA @@ -299,7 +299,7 @@ describe("send transaction", () => { test("send transaction testnet", async () => { // Set chainId to testnet - $mockChainId.next(646) + $mockChainId.next(545) const mockTxResult = { onceExecuted: jest.fn().mockResolvedValue({ @@ -328,7 +328,7 @@ describe("send transaction", () => { data: "0x1234", nonce: "0", gas: "0", - chainId: "646", + chainId: "545", } const result = await accountManager.sendTransaction(tx) @@ -388,11 +388,11 @@ describe("send transaction", () => { data: "0x1234", nonce: "0", gas: "0", - chainId: "646", + chainId: "545", } await expect(accountManager.sendTransaction(tx)).rejects.toThrow( - `Chain ID does not match the current network. Expected: 747, Received: 646` + `Chain ID does not match the current network. Expected: 747, Received: 545` ) expect(fcl.mutate).not.toHaveBeenCalled() diff --git a/packages/fcl-ethereum-provider/src/constants.ts b/packages/fcl-ethereum-provider/src/constants.ts index 9996971d4..5493c42a8 100644 --- a/packages/fcl-ethereum-provider/src/constants.ts +++ b/packages/fcl-ethereum-provider/src/constants.ts @@ -9,7 +9,7 @@ export const FLOW_CHAINS = { publicRpcUrl: "https://access.mainnet.nodes.onflow.org", }, [FlowNetwork.TESTNET]: { - eip155ChainId: 646, + eip155ChainId: 545, publicRpcUrl: "https://access.testnet.nodes.onflow.org", }, } diff --git a/packages/fcl-ethereum-provider/src/gateway/gateway.test.ts b/packages/fcl-ethereum-provider/src/gateway/gateway.test.ts index aa2bad31d..c4b3c0128 100644 --- a/packages/fcl-ethereum-provider/src/gateway/gateway.test.ts +++ b/packages/fcl-ethereum-provider/src/gateway/gateway.test.ts @@ -13,7 +13,7 @@ describe("gateway", () => { test("request should work for mainnet", async () => { const gateway = new Gateway({ 747: "https://example.com", - 646: "https://example.com/testnet", + 545: "https://example.com/testnet", }) jest.mocked(JsonRpcProvider).mockImplementation( @@ -51,7 +51,7 @@ describe("gateway", () => { test("request should work for testnet", async () => { const gateway = new Gateway({ 747: "https://example.com", - 646: "https://example.com/testnet", + 545: "https://example.com/testnet", }) jest.mocked(JsonRpcProvider).mockImplementation( @@ -66,7 +66,7 @@ describe("gateway", () => { const returnValue = await gateway.request({ method: "eth_accounts", params: [], - chainId: 646, + chainId: 545, }) // Check that the arguments are correct @@ -89,7 +89,7 @@ describe("gateway", () => { test("subsequent requests should use the same provider", async () => { const gateway = new Gateway({ 747: "https://example.com", - 646: "https://example.com/testnet", + 545: "https://example.com/testnet", }) jest.mocked(JsonRpcProvider).mockImplementation( @@ -104,13 +104,13 @@ describe("gateway", () => { await gateway.request({ method: "eth_accounts", params: [], - chainId: 646, + chainId: 545, }) await gateway.request({ method: "eth_accounts", params: [], - chainId: 646, + chainId: 545, }) // Verify that the testnet provider was used @@ -125,7 +125,7 @@ describe("gateway", () => { test("request should throw if chainId is not found", async () => { const gateway = new Gateway({ 747: "https://example.com", - 646: "https://example.com/testnet", + 545: "https://example.com/testnet", }) await expect( @@ -181,7 +181,7 @@ describe("gateway", () => { await gateway.request({ method: "eth_accounts", params: [], - chainId: 646, + chainId: 545, }) // Verify that the testnet provider was used diff --git a/packages/fcl-ethereum-provider/src/network/network-manager.test.ts b/packages/fcl-ethereum-provider/src/network/network-manager.test.ts index 081abc8b2..8ee9c8b66 100644 --- a/packages/fcl-ethereum-provider/src/network/network-manager.test.ts +++ b/packages/fcl-ethereum-provider/src/network/network-manager.test.ts @@ -36,7 +36,7 @@ describe("network manager", () => { const manager = new NetworkManager(config.mock) const chainId = await manager.getChainId() - expect(chainId).toBe(646) + expect(chainId).toBe(545) }) test("getChainId should throw error on unknown network", async () => { diff --git a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.test.ts b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.test.ts index 0e0c3480a..b292a609c 100644 --- a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.test.ts +++ b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.test.ts @@ -51,7 +51,7 @@ describe("rpc processor", () => { ) jest.mocked(gateway).request.mockResolvedValue("0x0") - networkManager.getChainId.mockResolvedValue(646) + networkManager.getChainId.mockResolvedValue(545) const response = await rpcProcessor.handleRequest({ method: "eth_blockNumber", @@ -64,7 +64,7 @@ describe("rpc processor", () => { expect(gateway.request).toHaveBeenCalledWith({ method: "eth_blockNumber", params: [], - chainId: 646, + chainId: 545, }) }) From b4b3e1b12f2748f28df972c9e1e83d8762edd712 Mon Sep 17 00:00:00 2001 From: Chase Fleming Date: Fri, 7 Feb 2025 14:28:39 -0800 Subject: [PATCH 25/56] Add UI for storage error (#2126) * Add UI for storage error * Use shadow dom --------- Co-authored-by: Chase Fleming <1666730+chasefleming@users.noreply.github.com> --- .../src/accounts/account-manager.test.ts | 46 ++++++++++- .../src/accounts/account-manager.ts | 24 +++++- .../src/notifications.ts | 80 +++++++++++++++++++ 3 files changed, 145 insertions(+), 5 deletions(-) create mode 100644 packages/fcl-ethereum-provider/src/notifications.ts diff --git a/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts b/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts index 54f2a3167..ed0633bc8 100644 --- a/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts +++ b/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts @@ -2,9 +2,11 @@ import {AccountManager} from "./account-manager" import {mockUser} from "../__mocks__/fcl" import * as fcl from "@onflow/fcl" import * as rlp from "@onflow/rlp" -import {CurrentUser} from "@onflow/typedefs" +import {CurrentUser, FvmErrorCode} from "@onflow/typedefs" import {NetworkManager} from "../network/network-manager" import {BehaviorSubject} from "../util/observable" +import {TransactionError} from "@onflow/fcl" +import * as notifications from "../notifications" jest.mock("@onflow/fcl", () => { const fcl = jest.requireActual("@onflow/fcl") @@ -14,6 +16,9 @@ jest.mock("@onflow/fcl", () => { tx: jest.fn(), mutate: jest.fn(), query: jest.fn(), + TransactionError: { + fromErrorMessage: jest.fn(), + }, } }) @@ -22,6 +27,11 @@ jest.mock("@onflow/rlp", () => ({ Buffer: jest.requireActual("@onflow/rlp").Buffer, })) +jest.mock("../notifications", () => ({ + // ...jest.requireActual("../notifications"), + displayErrorNotification: jest.fn(), +})) + const mockFcl = jest.mocked(fcl) const mockQuery = jest.mocked(fcl.query) @@ -163,6 +173,40 @@ describe("AccountManager", () => { expect(fcl.mutate).toHaveBeenCalled() }) + it("should display error notification and throw if STORAGE_CAPACITY_EXCEEDED error occurs", async () => { + const txResultError = { + onceExecuted: jest.fn().mockResolvedValue({ + statusCode: 0, + errorMessage: "Simulated error", + events: [], + }), + } as any as jest.Mocked> + + jest.mocked(fcl.tx).mockReturnValue(txResultError) + jest.mocked(fcl.mutate).mockResolvedValue("1111") + + const storageError = { + code: FvmErrorCode.STORAGE_CAPACITY_EXCEEDED, + message: "Simulated storage capacity exceeded", + type: "TRANSACTION_ERROR", + name: "TransactionError", + } as TransactionError + + jest.mocked(TransactionError.fromErrorMessage).mockReturnValue(storageError) + + const accountManager = new AccountManager(userMock.mock, networkManager) + + await userMock.set!({addr: "0x1"} as CurrentUser) + + await expect(accountManager.createCOA(747)).rejects.toThrow( + "Insufficient funds to cover storage costs." + ) + expect(notifications.displayErrorNotification).toHaveBeenCalledWith( + "Storage Error", + "Your wallet does not have enough funds to cover storage costs. Please add more funds." + ) + }) + it("should handle user changes correctly", async () => { mockQuery .mockResolvedValueOnce("0x123") // for user 0x1 diff --git a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts index 07aa81c2f..30fc74838 100644 --- a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts +++ b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts @@ -1,12 +1,10 @@ import * as fcl from "@onflow/fcl" import * as rlp from "@onflow/rlp" -import {CompositeSignature, CurrentUser} from "@onflow/typedefs" +import {CompositeSignature, CurrentUser, FvmErrorCode} from "@onflow/typedefs" import { - ContractType, EVENT_IDENTIFIERS, EventType, FLOW_CHAINS, - FLOW_CONTRACTS, FlowNetwork, } from "../constants" import {TransactionExecutedEvent} from "../types/events" @@ -25,8 +23,9 @@ import { } from "../util/observable" import {EthSignatureResponse} from "../types/eth" import {NetworkManager} from "../network/network-manager" -import {formatChainId, getContractAddress} from "../util/eth" import {createCOATx, getCOAScript, sendTransactionTx} from "../cadence" +import {TransactionError} from "@onflow/fcl" +import {displayErrorNotification} from "../notifications" export class AccountManager { private $addressStore = new BehaviorSubject<{ @@ -181,6 +180,23 @@ export class AccountManager { authz: this.user, }) + const txResult = await fcl.tx(txId).onceExecuted() + + if (txResult.statusCode == 0) { + const txErr = TransactionError.fromErrorMessage(txResult.errorMessage) + + if (txErr && txErr.code === FvmErrorCode.STORAGE_CAPACITY_EXCEEDED) { + displayErrorNotification( + "Storage Error", + "Your wallet does not have enough funds to cover storage costs. Please add more funds." + ) + + throw new Error("Insufficient funds to cover storage costs.") + } + + throw new Error(`Transaction failed: ${txErr.message}`) + } + const event = await this.waitForTxResult( txId, EVENT_IDENTIFIERS[EventType.CADENCE_OWNED_ACCOUNT_CREATED][flowNetwork], diff --git a/packages/fcl-ethereum-provider/src/notifications.ts b/packages/fcl-ethereum-provider/src/notifications.ts new file mode 100644 index 000000000..f6aae525f --- /dev/null +++ b/packages/fcl-ethereum-provider/src/notifications.ts @@ -0,0 +1,80 @@ +export function displayErrorNotification(title: string, message: string) { + const existing = document.getElementById("flow-error-notification") + if (existing) { + existing.remove() + } + + const container = document.createElement("div") + container.id = "flow-error-notification" + document.body.appendChild(container) + + const shadow = container.attachShadow({mode: "closed"}) + + const isDarkMode = + window.matchMedia && + window.matchMedia("(prefers-color-scheme: dark)").matches + const backgroundColor = isDarkMode ? "#2c2c2c" : "#ffffff" + const textColor = isDarkMode ? "#f0f0f0" : "#333333" + const borderColor = isDarkMode ? "#444444" : "#e0e0e0" + + const style = document.createElement("style") + style.textContent = ` + :host { + position: fixed; + bottom: 20px; + right: 20px; + z-index: 1000; + max-width: 320px; + overflow: hidden; + } + .notification { + background-color: ${backgroundColor}; + color: ${textColor}; + padding: 16px 24px; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + font-family: Arial, sans-serif; + border: 1px solid ${borderColor}; + opacity: 0; + transform: translateY(20px); + transition: opacity 0.3s ease, transform 0.3s ease; + } + .title { + font-weight: bold; + margin-bottom: 8px; + font-size: 16px; + } + .message { + font-size: 14px; + line-height: 1.4; + } + ` + shadow.appendChild(style) + + const notification = document.createElement("div") + notification.classList.add("notification") + + const titleElem = document.createElement("div") + titleElem.classList.add("title") + titleElem.innerText = title + notification.appendChild(titleElem) + + const messageElem = document.createElement("div") + messageElem.classList.add("message") + messageElem.innerText = message + notification.appendChild(messageElem) + + shadow.appendChild(notification) + + // Trigger the fade-in animation + requestAnimationFrame(() => { + notification.style.opacity = "1" + notification.style.transform = "translateY(0)" + }) + + setTimeout(() => { + notification.style.opacity = "0" + notification.style.transform = "translateY(20px)" + notification.addEventListener("transitionend", () => container.remove()) + }, 5000) +} From b81429b66e046b884e076b0845d8da0638f81467 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> Date: Fri, 7 Feb 2025 16:12:17 -0800 Subject: [PATCH 26/56] Add Boilerplate Wagmi & Rainbowkit Adapter (#2105) --- package-lock.json | 2512 ++++++++++++++++- .../fcl-bundle/src/build/get-input-options.js | 19 +- packages/fcl-ethereum-provider/package.json | 5 +- .../src/accounts/account-manager.ts | 20 +- packages/fcl-ethereum-provider/src/cadence.ts | 44 +- .../fcl-ethereum-provider/src/constants.ts | 4 +- .../src/create-provider.ts | 7 +- .../src/events/event-dispatcher.test.ts | 12 +- .../src/events/event-dispatcher.ts | 3 +- .../src/gateway/gateway.test.ts | 4 +- .../fcl-ethereum-provider/src/provider.ts | 4 +- .../src/rpc/handlers/eth-accounts.test.ts | 11 +- .../src/rpc/handlers/eth-accounts.ts | 8 +- .../src/rpc/handlers/eth-chain-id.test.ts | 8 +- .../src/rpc/handlers/eth-chain-id.ts | 12 +- .../src/rpc/rpc-processor.test.ts | 110 + .../src/rpc/rpc-processor.ts | 2 +- .../src/types/provider.ts | 1 + packages/fcl-rainbowkit-adapter/.babelrc | 3 + .../fcl-rainbowkit-adapter/.browserslistrc | 2 + .../fcl-rainbowkit-adapter/.eslintrc.json | 20 + packages/fcl-rainbowkit-adapter/.npmignore | 1 + packages/fcl-rainbowkit-adapter/CHANGELOG.md | 1 + packages/fcl-rainbowkit-adapter/README.md | 1 + packages/fcl-rainbowkit-adapter/package.json | 54 + packages/fcl-rainbowkit-adapter/src/index.ts | 32 + packages/fcl-rainbowkit-adapter/tsconfig.json | 9 + packages/fcl-wagmi-adapter/.babelrc | 3 + packages/fcl-wagmi-adapter/.browserslistrc | 2 + packages/fcl-wagmi-adapter/.eslintrc.json | 20 + packages/fcl-wagmi-adapter/.npmignore | 1 + packages/fcl-wagmi-adapter/CHANGELOG.md | 1 + packages/fcl-wagmi-adapter/README.md | 1 + packages/fcl-wagmi-adapter/package.json | 52 + packages/fcl-wagmi-adapter/src/index.ts | 215 ++ packages/fcl-wagmi-adapter/tsconfig.json | 9 + 36 files changed, 3071 insertions(+), 142 deletions(-) create mode 100644 packages/fcl-rainbowkit-adapter/.babelrc create mode 100644 packages/fcl-rainbowkit-adapter/.browserslistrc create mode 100644 packages/fcl-rainbowkit-adapter/.eslintrc.json create mode 100644 packages/fcl-rainbowkit-adapter/.npmignore create mode 100644 packages/fcl-rainbowkit-adapter/CHANGELOG.md create mode 100644 packages/fcl-rainbowkit-adapter/README.md create mode 100644 packages/fcl-rainbowkit-adapter/package.json create mode 100644 packages/fcl-rainbowkit-adapter/src/index.ts create mode 100644 packages/fcl-rainbowkit-adapter/tsconfig.json create mode 100644 packages/fcl-wagmi-adapter/.babelrc create mode 100644 packages/fcl-wagmi-adapter/.browserslistrc create mode 100644 packages/fcl-wagmi-adapter/.eslintrc.json create mode 100644 packages/fcl-wagmi-adapter/.npmignore create mode 100644 packages/fcl-wagmi-adapter/CHANGELOG.md create mode 100644 packages/fcl-wagmi-adapter/README.md create mode 100644 packages/fcl-wagmi-adapter/package.json create mode 100644 packages/fcl-wagmi-adapter/src/index.ts create mode 100644 packages/fcl-wagmi-adapter/tsconfig.json diff --git a/package-lock.json b/package-lock.json index 30b629384..c4ae5c935 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,6 +36,12 @@ "@nx/nx-win32-x64-msvc": "^17.3.2" } }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.0.tgz", + "integrity": "sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg==", + "license": "MIT" + }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -1913,7 +1919,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.25.7", + "version": "7.26.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.7.tgz", + "integrity": "sha512-AOPI3D+a8dXnja+iwsUqGRjr1BbZIe771sXdapOtYI531gSqpi92vXivKcq2asu/DFpdl1ceFAKZyRzK2PCVcQ==", "license": "MIT", "dependencies": { "regenerator-runtime": "^0.14.0" @@ -2356,6 +2364,26 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/@coinbase/wallet-sdk": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@coinbase/wallet-sdk/-/wallet-sdk-4.2.3.tgz", + "integrity": "sha512-BcyHZ/Ec84z0emORzqdXDv4P0oV+tV3a0OirfA8Ko1JGBIAVvB+hzLvZzCDvnuZx7MTK+Dd8Y9Tjlo446BpCIg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@noble/hashes": "^1.4.0", + "clsx": "^1.2.1", + "eventemitter3": "^5.0.1", + "preact": "^10.24.2" + } + }, + "node_modules/@coinbase/wallet-sdk/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT", + "peer": true + }, "node_modules/@discoveryjs/json-ext": { "version": "0.5.7", "dev": true, @@ -2364,6 +2392,21 @@ "node": ">=10.0.0" } }, + "node_modules/@ecies/ciphers": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@ecies/ciphers/-/ciphers-0.2.2.tgz", + "integrity": "sha512-ylfGR7PyTd+Rm2PqQowG08BCKA22QuX8NzrL+LxAAvazN10DMwdJ2fWwAzRj05FI/M8vNFGm3cv9Wq/GFWCBLg==", + "license": "MIT", + "peer": true, + "engines": { + "bun": ">=1", + "deno": ">=2", + "node": ">=16" + }, + "peerDependencies": { + "@noble/ciphers": "^1.0.0" + } + }, "node_modules/@emnapi/core": { "version": "1.3.1", "dev": true, @@ -2404,6 +2447,13 @@ "dev": true, "license": "0BSD" }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT", + "peer": true + }, "node_modules/@es-joy/jsdoccomment": { "version": "0.41.0", "dev": true, @@ -2530,6 +2580,61 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@ethereumjs/common": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-3.2.0.tgz", + "integrity": "sha512-pksvzI0VyLgmuEF2FA/JR/4/y6hcPq8OUail3/AvycBaW1d5VSauOZzqGvJ3RTmR4MU35lWE8KseKOsEhrFRBA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ethereumjs/util": "^8.1.0", + "crc-32": "^1.2.0" + } + }, + "node_modules/@ethereumjs/rlp": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-4.0.1.tgz", + "integrity": "sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw==", + "license": "MPL-2.0", + "peer": true, + "bin": { + "rlp": "bin/rlp" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@ethereumjs/tx": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-4.2.0.tgz", + "integrity": "sha512-1nc6VO4jtFd172BbSnTnDQVr9IYBFl1y4xPzZdtkrkKIncBCkdbgfdRV+MiTkJYAtTxvV12GRZLqBFT1PNK6Yw==", + "license": "MPL-2.0", + "peer": true, + "dependencies": { + "@ethereumjs/common": "^3.2.0", + "@ethereumjs/rlp": "^4.0.1", + "@ethereumjs/util": "^8.1.0", + "ethereum-cryptography": "^2.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@ethereumjs/util": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@ethereumjs/util/-/util-8.1.0.tgz", + "integrity": "sha512-zQ0IqbdX8FZ9aw11vP+dZkKDkS+kgIvQPHnSAXzP9pLu+Rfu3D3XEeLbicvoXJTYnhZiPmsZUxgdzXwNKxRPbA==", + "license": "MPL-2.0", + "peer": true, + "dependencies": { + "@ethereumjs/rlp": "^4.0.1", + "ethereum-cryptography": "^2.0.0", + "micro-ftch": "^0.3.1" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/@ethersproject/abstract-provider": { "version": "5.7.0", "funding": [ @@ -5669,6 +5774,377 @@ "node": ">=6 <7 || >=8" } }, + "node_modules/@metamask/eth-json-rpc-provider": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@metamask/eth-json-rpc-provider/-/eth-json-rpc-provider-1.0.1.tgz", + "integrity": "sha512-whiUMPlAOrVGmX8aKYVPvlKyG4CpQXiNNyt74vE1xb5sPvmx5oA7B/kOi/JdBvhGQq97U1/AVdXEdk2zkP8qyA==", + "peer": true, + "dependencies": { + "@metamask/json-rpc-engine": "^7.0.0", + "@metamask/safe-event-emitter": "^3.0.0", + "@metamask/utils": "^5.0.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@metamask/eth-json-rpc-provider/node_modules/@metamask/json-rpc-engine": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/@metamask/json-rpc-engine/-/json-rpc-engine-7.3.3.tgz", + "integrity": "sha512-dwZPq8wx9yV3IX2caLi9q9xZBw2XeIoYqdyihDDDpuHVCEiqadJLwqM3zy+uwf6F1QYQ65A8aOMQg1Uw7LMLNg==", + "license": "ISC", + "peer": true, + "dependencies": { + "@metamask/rpc-errors": "^6.2.1", + "@metamask/safe-event-emitter": "^3.0.0", + "@metamask/utils": "^8.3.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@metamask/eth-json-rpc-provider/node_modules/@metamask/json-rpc-engine/node_modules/@metamask/utils": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@metamask/utils/-/utils-8.5.0.tgz", + "integrity": "sha512-I6bkduevXb72TIM9q2LRO63JSsF9EXduh3sBr9oybNX2hNNpr/j1tEjXrsG0Uabm4MJ1xkGAQEMwifvKZIkyxQ==", + "license": "ISC", + "peer": true, + "dependencies": { + "@ethereumjs/tx": "^4.2.0", + "@metamask/superstruct": "^3.0.0", + "@noble/hashes": "^1.3.1", + "@scure/base": "^1.1.3", + "@types/debug": "^4.1.7", + "debug": "^4.3.4", + "pony-cause": "^2.1.10", + "semver": "^7.5.4", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@metamask/eth-json-rpc-provider/node_modules/@metamask/utils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@metamask/utils/-/utils-5.0.2.tgz", + "integrity": "sha512-yfmE79bRQtnMzarnKfX7AEJBwFTxvTyw3nBQlu/5rmGXrjAeAMltoGxO62TFurxrQAFMNa/fEjIHNvungZp0+g==", + "license": "ISC", + "peer": true, + "dependencies": { + "@ethereumjs/tx": "^4.1.2", + "@types/debug": "^4.1.7", + "debug": "^4.3.4", + "semver": "^7.3.8", + "superstruct": "^1.0.3" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@metamask/eth-json-rpc-provider/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@metamask/json-rpc-engine": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@metamask/json-rpc-engine/-/json-rpc-engine-8.0.2.tgz", + "integrity": "sha512-IoQPmql8q7ABLruW7i4EYVHWUbF74yrp63bRuXV5Zf9BQwcn5H9Ww1eLtROYvI1bUXwOiHZ6qT5CWTrDc/t/AA==", + "license": "ISC", + "peer": true, + "dependencies": { + "@metamask/rpc-errors": "^6.2.1", + "@metamask/safe-event-emitter": "^3.0.0", + "@metamask/utils": "^8.3.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@metamask/json-rpc-middleware-stream": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@metamask/json-rpc-middleware-stream/-/json-rpc-middleware-stream-7.0.2.tgz", + "integrity": "sha512-yUdzsJK04Ev98Ck4D7lmRNQ8FPioXYhEUZOMS01LXW8qTvPGiRVXmVltj2p4wrLkh0vW7u6nv0mNl5xzC5Qmfg==", + "license": "ISC", + "peer": true, + "dependencies": { + "@metamask/json-rpc-engine": "^8.0.2", + "@metamask/safe-event-emitter": "^3.0.0", + "@metamask/utils": "^8.3.0", + "readable-stream": "^3.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@metamask/object-multiplex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@metamask/object-multiplex/-/object-multiplex-2.1.0.tgz", + "integrity": "sha512-4vKIiv0DQxljcXwfpnbsXcfa5glMj5Zg9mqn4xpIWqkv6uJ2ma5/GtUfLFSxhlxnR8asRMv8dDmWya1Tc1sDFA==", + "license": "ISC", + "peer": true, + "dependencies": { + "once": "^1.4.0", + "readable-stream": "^3.6.2" + }, + "engines": { + "node": "^16.20 || ^18.16 || >=20" + } + }, + "node_modules/@metamask/onboarding": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@metamask/onboarding/-/onboarding-1.0.1.tgz", + "integrity": "sha512-FqHhAsCI+Vacx2qa5mAFcWNSrTcVGMNjzxVgaX8ECSny/BJ9/vgXP9V7WF/8vb9DltPeQkxr+Fnfmm6GHfmdTQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "bowser": "^2.9.0" + } + }, + "node_modules/@metamask/providers": { + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/@metamask/providers/-/providers-16.1.0.tgz", + "integrity": "sha512-znVCvux30+3SaUwcUGaSf+pUckzT5ukPRpcBmy+muBLC0yaWnBcvDqGfcsw6CBIenUdFrVoAFa8B6jsuCY/a+g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@metamask/json-rpc-engine": "^8.0.1", + "@metamask/json-rpc-middleware-stream": "^7.0.1", + "@metamask/object-multiplex": "^2.0.0", + "@metamask/rpc-errors": "^6.2.1", + "@metamask/safe-event-emitter": "^3.1.1", + "@metamask/utils": "^8.3.0", + "detect-browser": "^5.2.0", + "extension-port-stream": "^3.0.0", + "fast-deep-equal": "^3.1.3", + "is-stream": "^2.0.0", + "readable-stream": "^3.6.2", + "webextension-polyfill": "^0.10.0" + }, + "engines": { + "node": "^18.18 || >=20" + } + }, + "node_modules/@metamask/providers/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@metamask/rpc-errors": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@metamask/rpc-errors/-/rpc-errors-6.4.0.tgz", + "integrity": "sha512-1ugFO1UoirU2esS3juZanS/Fo8C8XYocCuBpfZI5N7ECtoG+zu0wF+uWZASik6CkO6w9n/Iebt4iI4pT0vptpg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@metamask/utils": "^9.0.0", + "fast-safe-stringify": "^2.0.6" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@metamask/rpc-errors/node_modules/@metamask/utils": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@metamask/utils/-/utils-9.3.0.tgz", + "integrity": "sha512-w8CVbdkDrVXFJbfBSlDfafDR6BAkpDmv1bC1UJVCoVny5tW2RKAdn9i68Xf7asYT4TnUhl/hN4zfUiKQq9II4g==", + "license": "ISC", + "peer": true, + "dependencies": { + "@ethereumjs/tx": "^4.2.0", + "@metamask/superstruct": "^3.1.0", + "@noble/hashes": "^1.3.1", + "@scure/base": "^1.1.3", + "@types/debug": "^4.1.7", + "debug": "^4.3.4", + "pony-cause": "^2.1.10", + "semver": "^7.5.4", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@metamask/rpc-errors/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@metamask/safe-event-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@metamask/safe-event-emitter/-/safe-event-emitter-3.1.2.tgz", + "integrity": "sha512-5yb2gMI1BDm0JybZezeoX/3XhPDOtTbcFvpTXM9kxsoZjPZFh4XciqRbpD6N86HYZqWDhEaKUDuOyR0sQHEjMA==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@metamask/sdk": { + "version": "0.31.5", + "resolved": "https://registry.npmjs.org/@metamask/sdk/-/sdk-0.31.5.tgz", + "integrity": "sha512-i7wteqO/fU2JWQrMZz+addHokYThHYznp4nYXviv+QysdxGVgAYvcW/PBA+wpeP3veX7QGfNqMPgSsZbBrASYw==", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.26.0", + "@metamask/onboarding": "^1.0.1", + "@metamask/providers": "16.1.0", + "@metamask/sdk-communication-layer": "0.31.0", + "@metamask/sdk-install-modal-web": "0.31.5", + "@paulmillr/qr": "^0.2.1", + "bowser": "^2.9.0", + "cross-fetch": "^4.0.0", + "debug": "^4.3.4", + "eciesjs": "^0.4.11", + "eth-rpc-errors": "^4.0.3", + "eventemitter2": "^6.4.9", + "obj-multiplex": "^1.0.0", + "pump": "^3.0.0", + "readable-stream": "^3.6.2", + "socket.io-client": "^4.5.1", + "tslib": "^2.6.0", + "util": "^0.12.4", + "uuid": "^8.3.2" + } + }, + "node_modules/@metamask/sdk-install-modal-web": { + "version": "0.31.5", + "resolved": "https://registry.npmjs.org/@metamask/sdk-install-modal-web/-/sdk-install-modal-web-0.31.5.tgz", + "integrity": "sha512-ZfrVkPAabfH4AIxcTlxQN5oyyzzVXFTLZrm1/BJ+X632d9MiyAVHNtiqa9EZpZYkZGk2icmDVP+xCpvJmVOVpQ==", + "peer": true, + "dependencies": { + "@paulmillr/qr": "^0.2.1" + } + }, + "node_modules/@metamask/sdk/node_modules/@metamask/sdk-communication-layer": { + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/@metamask/sdk-communication-layer/-/sdk-communication-layer-0.31.0.tgz", + "integrity": "sha512-V9CxdzabDPjQVgmKGHsyU3SYt4Af27g+4DbGCx0fLoHqN/i1RBDZqs/LYbJX3ykJCANzE+llz/MolMCMrzM2RA==", + "peer": true, + "dependencies": { + "bufferutil": "^4.0.8", + "date-fns": "^2.29.3", + "debug": "^4.3.4", + "utf-8-validate": "^5.0.2", + "uuid": "^8.3.2" + }, + "peerDependencies": { + "cross-fetch": "^4.0.0", + "eciesjs": "*", + "eventemitter2": "^6.4.9", + "readable-stream": "^3.6.2", + "socket.io-client": "^4.5.1" + } + }, + "node_modules/@metamask/sdk/node_modules/cross-fetch": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz", + "integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==", + "license": "MIT", + "peer": true, + "dependencies": { + "node-fetch": "^2.7.0" + } + }, + "node_modules/@metamask/sdk/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "peer": true + }, + "node_modules/@metamask/sdk/node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "license": "MIT", + "peer": true, + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/@metamask/sdk/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "peer": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@metamask/superstruct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@metamask/superstruct/-/superstruct-3.1.0.tgz", + "integrity": "sha512-N08M56HdOgBfRKkrgCMZvQppkZGcArEop3kixNEtVbJKm6P9Cfg0YkI6X0s1g78sNrj2fWUwvJADdZuzJgFttA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@metamask/utils": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/@metamask/utils/-/utils-8.5.0.tgz", + "integrity": "sha512-I6bkduevXb72TIM9q2LRO63JSsF9EXduh3sBr9oybNX2hNNpr/j1tEjXrsG0Uabm4MJ1xkGAQEMwifvKZIkyxQ==", + "license": "ISC", + "peer": true, + "dependencies": { + "@ethereumjs/tx": "^4.2.0", + "@metamask/superstruct": "^3.0.0", + "@noble/hashes": "^1.3.1", + "@scure/base": "^1.1.3", + "@types/debug": "^4.1.7", + "debug": "^4.3.4", + "pony-cause": "^2.1.10", + "semver": "^7.5.4", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@metamask/utils/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@motionone/animation": { "version": "10.18.0", "license": "MIT", @@ -5775,8 +6251,36 @@ "@tybys/wasm-util": "^0.9.0" } }, - "node_modules/@noble/hashes": { - "version": "1.7.1", + "node_modules/@noble/ciphers": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.2.1.tgz", + "integrity": "sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA==", + "license": "MIT", + "peer": true, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.1.tgz", + "integrity": "sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.7.1" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.7.1", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.1.tgz", "integrity": "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==", "license": "MIT", @@ -6735,10 +7239,18 @@ "resolved": "packages/fcl-ethereum-provider", "link": true }, + "node_modules/@onflow/fcl-rainbowkit-adapter": { + "resolved": "packages/fcl-rainbowkit-adapter", + "link": true + }, "node_modules/@onflow/fcl-react-native": { "resolved": "packages/fcl-react-native", "link": true }, + "node_modules/@onflow/fcl-wagmi-adapter": { + "resolved": "packages/fcl-wagmi-adapter", + "link": true + }, "node_modules/@onflow/fcl-wc": { "resolved": "packages/fcl-wc", "link": true @@ -6884,6 +7396,16 @@ "inBundle": true, "license": "MIT" }, + "node_modules/@paulmillr/qr": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@paulmillr/qr/-/qr-0.2.1.tgz", + "integrity": "sha512-IHnV6A+zxU7XwmKFinmYjUcwlyK9+xkG3/s9KcQhI9BjQKycrJ1JRO+FbNYPwZiPKW3je/DR0k7w8/gLa5eaxQ==", + "license": "(MIT OR Apache-2.0)", + "peer": true, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "license": "MIT", @@ -9027,6 +9549,74 @@ "win32" ] }, + "node_modules/@safe-global/safe-apps-provider": { + "version": "0.18.5", + "resolved": "https://registry.npmjs.org/@safe-global/safe-apps-provider/-/safe-apps-provider-0.18.5.tgz", + "integrity": "sha512-9v9wjBi3TwLsEJ3C2ujYoexp3pFJ0omDLH/GX91e2QB+uwCKTBYyhxFSrTQ9qzoyQd+bfsk4gjOGW87QcJhf7g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@safe-global/safe-apps-sdk": "^9.1.0", + "events": "^3.3.0" + } + }, + "node_modules/@safe-global/safe-apps-sdk": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@safe-global/safe-apps-sdk/-/safe-apps-sdk-9.1.0.tgz", + "integrity": "sha512-N5p/ulfnnA2Pi2M3YeWjULeWbjo7ei22JwU/IXnhoHzKq3pYCN6ynL9mJBOlvDVv892EgLPCWCOwQk/uBT2v0Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@safe-global/safe-gateway-typescript-sdk": "^3.5.3", + "viem": "^2.1.1" + } + }, + "node_modules/@safe-global/safe-gateway-typescript-sdk": { + "version": "3.22.9", + "resolved": "https://registry.npmjs.org/@safe-global/safe-gateway-typescript-sdk/-/safe-gateway-typescript-sdk-3.22.9.tgz", + "integrity": "sha512-7ojVK/crhOaGowEO8uYWaopZzcr5rR76emgllGIfjCLR70aY4PbASpi9Pbs+7jIRzPDBBkM0RBo+zYx5UduX8Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/@scure/base": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.4.tgz", + "integrity": "sha512-5Yy9czTO47mqz+/J8GM6GIId4umdCk1wc1q8rKERQulIoc8VP9pzDcghv10Tl2E7R96ZUx/PhND3ESYUQX8NuQ==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.6.2.tgz", + "integrity": "sha512-t96EPDMbtGgtb7onKKqxRLfE5g05k7uHnHRM2xdE6BP/ZmxaLtPek4J4KfVn/90IQNrU1IOAqMgiDtUdtbe3nw==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.8.1", + "@noble/hashes": "~1.7.1", + "@scure/base": "~1.2.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.5.4.tgz", + "integrity": "sha512-TFM4ni0vKvCfBpohoh+/lY05i9gRbSwXWngAsF4CABQxoaOHijxuaZ2R6cStDQ5CHtHO9aGJTr4ksVJASRRyMA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.7.1", + "@scure/base": "~1.2.4" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@segment/loosely-validate-event": { "version": "2.0.0", "peer": true, @@ -9150,6 +9740,13 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT", + "peer": true + }, "node_modules/@stablelib/aead": { "version": "1.0.1", "license": "MIT" @@ -9278,6 +9875,34 @@ "@stablelib/wipe": "^1.0.1" } }, + "node_modules/@tanstack/query-core": { + "version": "5.66.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.66.0.tgz", + "integrity": "sha512-J+JeBtthiKxrpzUu7rfIPDzhscXF2p5zE/hVdrqkACBP8Yu0M96mwJ5m/8cPPYQE9aRNvXztXHlNwIh4FEeMZw==", + "license": "MIT", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.66.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.66.0.tgz", + "integrity": "sha512-z3sYixFQJe8hndFnXgWu7C79ctL+pI0KAelYyW+khaNJ1m22lWrhJU2QrsTcRKMuVPtoZvfBYrTStIdKo+x0Xw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@tanstack/query-core": "5.66.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, "node_modules/@tootallnate/once": { "version": "2.0.0", "dev": true, @@ -9378,6 +10003,16 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.6", "license": "MIT" @@ -9442,6 +10077,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT", + "peer": true + }, "node_modules/@types/node": { "version": "18.19.57", "license": "MIT", @@ -9749,6 +10391,122 @@ "graphql": "^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0" } }, + "node_modules/@vanilla-extract/css": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/@vanilla-extract/css/-/css-1.15.5.tgz", + "integrity": "sha512-N1nQebRWnXvlcmu9fXKVUs145EVwmWtMD95bpiEKtvehHDpUhmO1l2bauS7FGYKbi3dU1IurJbGpQhBclTr1ng==", + "license": "MIT", + "peer": true, + "dependencies": { + "@emotion/hash": "^0.9.0", + "@vanilla-extract/private": "^1.0.6", + "css-what": "^6.1.0", + "cssesc": "^3.0.0", + "csstype": "^3.0.7", + "dedent": "^1.5.3", + "deep-object-diff": "^1.1.9", + "deepmerge": "^4.2.2", + "lru-cache": "^10.4.3", + "media-query-parser": "^2.0.2", + "modern-ahocorasick": "^1.0.0", + "picocolors": "^1.0.0" + } + }, + "node_modules/@vanilla-extract/css/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC", + "peer": true + }, + "node_modules/@vanilla-extract/dynamic": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@vanilla-extract/dynamic/-/dynamic-2.1.2.tgz", + "integrity": "sha512-9BGMciD8rO1hdSPIAh1ntsG4LPD3IYKhywR7VOmmz9OO4Lx1hlwkSg3E6X07ujFx7YuBfx0GDQnApG9ESHvB2A==", + "license": "MIT", + "peer": true, + "dependencies": { + "@vanilla-extract/private": "^1.0.6" + } + }, + "node_modules/@vanilla-extract/private": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@vanilla-extract/private/-/private-1.0.6.tgz", + "integrity": "sha512-ytsG/JLweEjw7DBuZ/0JCN4WAQgM9erfSTdS1NQY778hFQSZ6cfCDEZZ0sgVm4k54uNz6ImKB33AYvSR//fjxw==", + "license": "MIT", + "peer": true + }, + "node_modules/@vanilla-extract/sprinkles": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@vanilla-extract/sprinkles/-/sprinkles-1.6.3.tgz", + "integrity": "sha512-oCHlQeYOBIJIA2yWy2GnY5wE2A7hGHDyJplJo4lb+KEIBcJWRnDJDg8ywDwQS5VfWJrBBO3drzYZPFpWQjAMiQ==", + "license": "MIT", + "peer": true, + "peerDependencies": { + "@vanilla-extract/css": "^1.0.0" + } + }, + "node_modules/@wagmi/connectors": { + "version": "5.7.5", + "resolved": "https://registry.npmjs.org/@wagmi/connectors/-/connectors-5.7.5.tgz", + "integrity": "sha512-btqHHUSTzg4BZe9at/7SnRPv4cz8O3pisbeZBh0qxKz7PVm+9vRxY0bSala3xQPDcS0PRTB30Vn/+lM73GCjbw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@coinbase/wallet-sdk": "4.2.3", + "@metamask/sdk": "0.31.5", + "@safe-global/safe-apps-provider": "0.18.5", + "@safe-global/safe-apps-sdk": "9.1.0", + "@walletconnect/ethereum-provider": "2.17.0", + "cbw-sdk": "npm:@coinbase/wallet-sdk@3.9.3" + }, + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "@wagmi/core": "2.16.3", + "typescript": ">=5.0.4", + "viem": "2.x" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@wagmi/core": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/@wagmi/core/-/core-2.16.3.tgz", + "integrity": "sha512-SVovoWHaQ2AIkmGf+ucNijT6AHXcTMffFcLmcFF6++y21x+ge7Gkh3UoJiU91SDDv8n08eTQ9jbyia3GEgU5jQ==", + "license": "MIT", + "dependencies": { + "eventemitter3": "5.0.1", + "mipd": "0.0.7", + "zustand": "5.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "@tanstack/query-core": ">=5.0.0", + "typescript": ">=5.0.4", + "viem": "2.x" + }, + "peerDependenciesMeta": { + "@tanstack/query-core": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@wagmi/core/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, "node_modules/@walletconnect/core": { "version": "2.17.1", "license": "Apache-2.0", @@ -9782,56 +10540,190 @@ "tslib": "1.14.1" } }, - "node_modules/@walletconnect/events": { - "version": "1.0.1", - "license": "MIT", + "node_modules/@walletconnect/ethereum-provider": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@walletconnect/ethereum-provider/-/ethereum-provider-2.17.0.tgz", + "integrity": "sha512-b+KTAXOb6JjoxkwpgYQQKPUcTwENGmdEdZoIDLeRicUmZTn/IQKfkMoC2frClB4YxkyoVMtj1oMV2JAax+yu9A==", + "license": "Apache-2.0", + "peer": true, "dependencies": { - "keyvaluestorage-interface": "^1.0.0", - "tslib": "1.14.1" + "@walletconnect/jsonrpc-http-connection": "1.0.8", + "@walletconnect/jsonrpc-provider": "1.0.14", + "@walletconnect/jsonrpc-types": "1.0.4", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/modal": "2.7.0", + "@walletconnect/sign-client": "2.17.0", + "@walletconnect/types": "2.17.0", + "@walletconnect/universal-provider": "2.17.0", + "@walletconnect/utils": "2.17.0", + "events": "3.3.0" } }, - "node_modules/@walletconnect/heartbeat": { - "version": "1.2.2", - "license": "MIT", + "node_modules/@walletconnect/ethereum-provider/node_modules/@walletconnect/core": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@walletconnect/core/-/core-2.17.0.tgz", + "integrity": "sha512-On+uSaCfWdsMIQsECwWHZBmUXfrnqmv6B8SXRRuTJgd8tUpEvBkLQH4X7XkSm3zW6ozEkQTCagZ2ox2YPn3kbw==", + "license": "Apache-2.0", + "peer": true, "dependencies": { - "@walletconnect/events": "^1.0.1", - "@walletconnect/time": "^1.0.2", - "events": "^3.3.0" + "@walletconnect/heartbeat": "1.2.2", + "@walletconnect/jsonrpc-provider": "1.0.14", + "@walletconnect/jsonrpc-types": "1.0.4", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/jsonrpc-ws-connection": "1.0.14", + "@walletconnect/keyvaluestorage": "1.1.1", + "@walletconnect/logger": "2.1.2", + "@walletconnect/relay-api": "1.0.11", + "@walletconnect/relay-auth": "1.0.4", + "@walletconnect/safe-json": "1.0.2", + "@walletconnect/time": "1.0.2", + "@walletconnect/types": "2.17.0", + "@walletconnect/utils": "2.17.0", + "events": "3.3.0", + "lodash.isequal": "4.5.0", + "uint8arrays": "3.1.0" + }, + "engines": { + "node": ">=18" } }, - "node_modules/@walletconnect/jsonrpc-http-connection": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-http-connection/-/jsonrpc-http-connection-1.0.8.tgz", - "integrity": "sha512-+B7cRuaxijLeFDJUq5hAzNyef3e3tBDIxyaCNmFtjwnod5AGis3RToNqzFU33vpVcxFhofkpE7Cx+5MYejbMGw==", - "license": "MIT", + "node_modules/@walletconnect/ethereum-provider/node_modules/@walletconnect/sign-client": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@walletconnect/sign-client/-/sign-client-2.17.0.tgz", + "integrity": "sha512-sErYwvSSHQolNXni47L3Bm10ptJc1s1YoJvJd34s5E9h9+d3rj7PrhbiW9X82deN+Dm5oA8X9tC4xty1yIBrVg==", + "license": "Apache-2.0", + "peer": true, "dependencies": { - "@walletconnect/jsonrpc-utils": "^1.0.6", - "@walletconnect/safe-json": "^1.0.1", - "cross-fetch": "^3.1.4", - "events": "^3.3.0" + "@walletconnect/core": "2.17.0", + "@walletconnect/events": "1.0.1", + "@walletconnect/heartbeat": "1.2.2", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/logger": "2.1.2", + "@walletconnect/time": "1.0.2", + "@walletconnect/types": "2.17.0", + "@walletconnect/utils": "2.17.0", + "events": "3.3.0" } }, - "node_modules/@walletconnect/jsonrpc-provider": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-provider/-/jsonrpc-provider-1.0.14.tgz", - "integrity": "sha512-rtsNY1XqHvWj0EtITNeuf8PHMvlCLiS3EjQL+WOkxEOA4KPxsohFnBDeyPYiNm4ZvkQdLnece36opYidmtbmow==", - "license": "MIT", + "node_modules/@walletconnect/ethereum-provider/node_modules/@walletconnect/types": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-2.17.0.tgz", + "integrity": "sha512-i1pn9URpvt9bcjRDkabuAmpA9K7mzyKoLJlbsAujRVX7pfaG7wur7u9Jz0bk1HxvuABL5LHNncTnVKSXKQ5jZA==", + "license": "Apache-2.0", + "peer": true, "dependencies": { - "@walletconnect/jsonrpc-utils": "^1.0.8", - "@walletconnect/safe-json": "^1.0.2", - "events": "^3.3.0" + "@walletconnect/events": "1.0.1", + "@walletconnect/heartbeat": "1.2.2", + "@walletconnect/jsonrpc-types": "1.0.4", + "@walletconnect/keyvaluestorage": "1.1.1", + "@walletconnect/logger": "2.1.2", + "events": "3.3.0" } }, - "node_modules/@walletconnect/jsonrpc-types": { - "version": "1.0.4", - "license": "MIT", + "node_modules/@walletconnect/ethereum-provider/node_modules/@walletconnect/utils": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-2.17.0.tgz", + "integrity": "sha512-1aeQvjwsXy4Yh9G6g2eGmXrEl+BzkNjHRdCrGdMYqFTFa8ROEJfTGsSH3pLsNDlOY94CoBUvJvM55q/PMoN/FQ==", + "license": "Apache-2.0", + "peer": true, "dependencies": { - "events": "^3.3.0", - "keyvaluestorage-interface": "^1.0.0" + "@stablelib/chacha20poly1305": "1.0.1", + "@stablelib/hkdf": "1.0.1", + "@stablelib/random": "1.0.2", + "@stablelib/sha256": "1.0.1", + "@stablelib/x25519": "1.0.3", + "@walletconnect/relay-api": "1.0.11", + "@walletconnect/relay-auth": "1.0.4", + "@walletconnect/safe-json": "1.0.2", + "@walletconnect/time": "1.0.2", + "@walletconnect/types": "2.17.0", + "@walletconnect/window-getters": "1.0.1", + "@walletconnect/window-metadata": "1.0.1", + "detect-browser": "5.3.0", + "elliptic": "^6.5.7", + "query-string": "7.1.3", + "uint8arrays": "3.1.0" } }, - "node_modules/@walletconnect/jsonrpc-utils": { - "version": "1.0.8", + "node_modules/@walletconnect/ethereum-provider/node_modules/query-string": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", + "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==", + "license": "MIT", + "peer": true, + "dependencies": { + "decode-uri-component": "^0.2.2", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@walletconnect/ethereum-provider/node_modules/strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@walletconnect/events": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "keyvaluestorage-interface": "^1.0.0", + "tslib": "1.14.1" + } + }, + "node_modules/@walletconnect/heartbeat": { + "version": "1.2.2", + "license": "MIT", + "dependencies": { + "@walletconnect/events": "^1.0.1", + "@walletconnect/time": "^1.0.2", + "events": "^3.3.0" + } + }, + "node_modules/@walletconnect/jsonrpc-http-connection": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-http-connection/-/jsonrpc-http-connection-1.0.8.tgz", + "integrity": "sha512-+B7cRuaxijLeFDJUq5hAzNyef3e3tBDIxyaCNmFtjwnod5AGis3RToNqzFU33vpVcxFhofkpE7Cx+5MYejbMGw==", + "license": "MIT", + "dependencies": { + "@walletconnect/jsonrpc-utils": "^1.0.6", + "@walletconnect/safe-json": "^1.0.1", + "cross-fetch": "^3.1.4", + "events": "^3.3.0" + } + }, + "node_modules/@walletconnect/jsonrpc-provider": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-provider/-/jsonrpc-provider-1.0.14.tgz", + "integrity": "sha512-rtsNY1XqHvWj0EtITNeuf8PHMvlCLiS3EjQL+WOkxEOA4KPxsohFnBDeyPYiNm4ZvkQdLnece36opYidmtbmow==", + "license": "MIT", + "dependencies": { + "@walletconnect/jsonrpc-utils": "^1.0.8", + "@walletconnect/safe-json": "^1.0.2", + "events": "^3.3.0" + } + }, + "node_modules/@walletconnect/jsonrpc-types": { + "version": "1.0.4", + "license": "MIT", + "dependencies": { + "events": "^3.3.0", + "keyvaluestorage-interface": "^1.0.0" + } + }, + "node_modules/@walletconnect/jsonrpc-utils": { + "version": "1.0.8", "license": "MIT", "dependencies": { "@walletconnect/environment": "^1.0.1", @@ -9978,6 +10870,139 @@ "events": "3.3.0" } }, + "node_modules/@walletconnect/universal-provider": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@walletconnect/universal-provider/-/universal-provider-2.17.0.tgz", + "integrity": "sha512-d3V5Be7AqLrvzcdMZSBS8DmGDRdqnyLk1DWmRKAGgR6ieUWykhhUKlvfeoZtvJrIXrY7rUGYpH1X41UtFkW5Pw==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@walletconnect/jsonrpc-http-connection": "1.0.8", + "@walletconnect/jsonrpc-provider": "1.0.14", + "@walletconnect/jsonrpc-types": "1.0.4", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/logger": "2.1.2", + "@walletconnect/sign-client": "2.17.0", + "@walletconnect/types": "2.17.0", + "@walletconnect/utils": "2.17.0", + "events": "3.3.0" + } + }, + "node_modules/@walletconnect/universal-provider/node_modules/@walletconnect/core": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@walletconnect/core/-/core-2.17.0.tgz", + "integrity": "sha512-On+uSaCfWdsMIQsECwWHZBmUXfrnqmv6B8SXRRuTJgd8tUpEvBkLQH4X7XkSm3zW6ozEkQTCagZ2ox2YPn3kbw==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@walletconnect/heartbeat": "1.2.2", + "@walletconnect/jsonrpc-provider": "1.0.14", + "@walletconnect/jsonrpc-types": "1.0.4", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/jsonrpc-ws-connection": "1.0.14", + "@walletconnect/keyvaluestorage": "1.1.1", + "@walletconnect/logger": "2.1.2", + "@walletconnect/relay-api": "1.0.11", + "@walletconnect/relay-auth": "1.0.4", + "@walletconnect/safe-json": "1.0.2", + "@walletconnect/time": "1.0.2", + "@walletconnect/types": "2.17.0", + "@walletconnect/utils": "2.17.0", + "events": "3.3.0", + "lodash.isequal": "4.5.0", + "uint8arrays": "3.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@walletconnect/universal-provider/node_modules/@walletconnect/sign-client": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@walletconnect/sign-client/-/sign-client-2.17.0.tgz", + "integrity": "sha512-sErYwvSSHQolNXni47L3Bm10ptJc1s1YoJvJd34s5E9h9+d3rj7PrhbiW9X82deN+Dm5oA8X9tC4xty1yIBrVg==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@walletconnect/core": "2.17.0", + "@walletconnect/events": "1.0.1", + "@walletconnect/heartbeat": "1.2.2", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/logger": "2.1.2", + "@walletconnect/time": "1.0.2", + "@walletconnect/types": "2.17.0", + "@walletconnect/utils": "2.17.0", + "events": "3.3.0" + } + }, + "node_modules/@walletconnect/universal-provider/node_modules/@walletconnect/types": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-2.17.0.tgz", + "integrity": "sha512-i1pn9URpvt9bcjRDkabuAmpA9K7mzyKoLJlbsAujRVX7pfaG7wur7u9Jz0bk1HxvuABL5LHNncTnVKSXKQ5jZA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@walletconnect/events": "1.0.1", + "@walletconnect/heartbeat": "1.2.2", + "@walletconnect/jsonrpc-types": "1.0.4", + "@walletconnect/keyvaluestorage": "1.1.1", + "@walletconnect/logger": "2.1.2", + "events": "3.3.0" + } + }, + "node_modules/@walletconnect/universal-provider/node_modules/@walletconnect/utils": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-2.17.0.tgz", + "integrity": "sha512-1aeQvjwsXy4Yh9G6g2eGmXrEl+BzkNjHRdCrGdMYqFTFa8ROEJfTGsSH3pLsNDlOY94CoBUvJvM55q/PMoN/FQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@stablelib/chacha20poly1305": "1.0.1", + "@stablelib/hkdf": "1.0.1", + "@stablelib/random": "1.0.2", + "@stablelib/sha256": "1.0.1", + "@stablelib/x25519": "1.0.3", + "@walletconnect/relay-api": "1.0.11", + "@walletconnect/relay-auth": "1.0.4", + "@walletconnect/safe-json": "1.0.2", + "@walletconnect/time": "1.0.2", + "@walletconnect/types": "2.17.0", + "@walletconnect/window-getters": "1.0.1", + "@walletconnect/window-metadata": "1.0.1", + "detect-browser": "5.3.0", + "elliptic": "^6.5.7", + "query-string": "7.1.3", + "uint8arrays": "3.1.0" + } + }, + "node_modules/@walletconnect/universal-provider/node_modules/query-string": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", + "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==", + "license": "MIT", + "peer": true, + "dependencies": { + "decode-uri-component": "^0.2.2", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@walletconnect/universal-provider/node_modules/strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4" + } + }, "node_modules/@walletconnect/utils": { "version": "2.17.1", "license": "Apache-2.0", @@ -10283,6 +11308,27 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/abitype": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.8.tgz", + "integrity": "sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3 >=3.22.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, "node_modules/abort-controller": { "version": "3.0.0", "license": "MIT", @@ -10639,6 +11685,23 @@ "license": "MIT", "peer": true }, + "node_modules/async-mutex": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.2.6.tgz", + "integrity": "sha512-Hs4R+4SPgamu6rSGW8C7cV9gaWUKEHykfzCCvIRuaVv636Ju10ZdeUbvb4TBEW0INuq2DHZqXbK4Nd3yG4RaRw==", + "license": "MIT", + "peer": true, + "dependencies": { + "tslib": "^2.0.0" + } + }, + "node_modules/async-mutex/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "peer": true + }, "node_modules/asynckit": { "version": "0.4.0", "license": "MIT" @@ -11324,6 +12387,13 @@ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" }, + "node_modules/bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", + "license": "MIT", + "peer": true + }, "node_modules/bplist-creator": { "version": "0.0.7", "license": "MIT", @@ -11461,6 +12531,20 @@ "version": "1.1.2", "license": "MIT" }, + "node_modules/bufferutil": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.9.tgz", + "integrity": "sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==", + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/builtin-modules": { "version": "3.3.0", "dev": true, @@ -11567,6 +12651,37 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "license": "MIT", + "peer": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caller-callsite": { "version": "2.0.0", "license": "MIT", @@ -11665,6 +12780,39 @@ ], "license": "CC-BY-4.0" }, + "node_modules/cbw-sdk": { + "name": "@coinbase/wallet-sdk", + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/@coinbase/wallet-sdk/-/wallet-sdk-3.9.3.tgz", + "integrity": "sha512-N/A2DRIf0Y3PHc1XAMvbBUu4zisna6qAdqABMZwBMNEfWrXpAwx16pZGkYCLGE+Rvv1edbcB2LYDRnACNcmCiw==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "bn.js": "^5.2.1", + "buffer": "^6.0.3", + "clsx": "^1.2.1", + "eth-block-tracker": "^7.1.0", + "eth-json-rpc-filters": "^6.0.0", + "eventemitter3": "^5.0.1", + "keccak": "^3.0.3", + "preact": "^10.16.0", + "sha.js": "^2.4.11" + } + }, + "node_modules/cbw-sdk/node_modules/bn.js": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", + "license": "MIT", + "peer": true + }, + "node_modules/cbw-sdk/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT", + "peer": true + }, "node_modules/chalk": { "version": "2.4.2", "license": "MIT", @@ -12008,6 +13156,16 @@ "node": ">=6" } }, + "node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/cmd-shim": { "version": "6.0.3", "dev": true, @@ -12486,6 +13644,19 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "license": "Apache-2.0", + "peer": true, + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/create-jest": { "version": "29.7.0", "dev": true, @@ -12794,6 +13965,13 @@ "dev": true, "license": "MIT" }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "license": "MIT", + "peer": true + }, "node_modules/dag-map": { "version": "1.0.2", "license": "MIT", @@ -12873,6 +14051,23 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/date-fns": { + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", + "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.21.0" + }, + "engines": { + "node": ">=0.11" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/date-fns" + } + }, "node_modules/dateformat": { "version": "3.0.3", "dev": true, @@ -12945,7 +14140,6 @@ }, "node_modules/dedent": { "version": "1.5.3", - "dev": true, "license": "MIT", "peerDependencies": { "babel-plugin-macros": "^3.1.0" @@ -12969,6 +14163,13 @@ "dev": true, "license": "MIT" }, + "node_modules/deep-object-diff": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/deep-object-diff/-/deep-object-diff-1.1.9.tgz", + "integrity": "sha512-Rn+RuwkmkDwCi2/oXOFS9Gsr5lJZu/yTGpK7wAaAIE75CC+LCGEZHpY6VQJa/RoJcrmaA/docWJZvYohlNkWPA==", + "license": "MIT", + "peer": true + }, "node_modules/deepmerge": { "version": "4.3.1", "license": "MIT", @@ -13161,6 +14362,13 @@ "node": ">=8" } }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT", + "peer": true + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -13316,6 +14524,21 @@ "url": "https://dotenvx.com" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "peer": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/duplexer": { "version": "0.1.2", "dev": true, @@ -13335,6 +14558,24 @@ "version": "0.2.0", "license": "MIT" }, + "node_modules/eciesjs": { + "version": "0.4.13", + "resolved": "https://registry.npmjs.org/eciesjs/-/eciesjs-0.4.13.tgz", + "integrity": "sha512-zBdtR4K+wbj10bWPpIOF9DW+eFYQu8miU5ypunh0t4Bvt83ZPlEWgT5Dq/0G6uwEXumZKjfb5BZxYUZQ2Hzn/Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ecies/ciphers": "^0.2.2", + "@noble/ciphers": "^1.0.0", + "@noble/curves": "^1.6.0", + "@noble/hashes": "^1.5.0" + }, + "engines": { + "bun": ">=1", + "deno": ">=2", + "node": ">=16" + } + }, "node_modules/ee-first": { "version": "1.1.1", "license": "MIT", @@ -13424,6 +14665,52 @@ "once": "^1.4.0" } }, + "node_modules/engine.io-client": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz", + "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", + "license": "MIT", + "peer": true, + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-client/node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/enhanced-resolve": { "version": "5.17.1", "dev": true, @@ -13582,12 +14869,11 @@ } }, "node_modules/es-define-property": { - "version": "1.0.0", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "license": "MIT", "peer": true, - "dependencies": { - "get-intrinsic": "^1.2.4" - }, "engines": { "node": ">= 0.4" } @@ -14077,6 +15363,179 @@ "node": ">= 0.6" } }, + "node_modules/eth-block-tracker": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/eth-block-tracker/-/eth-block-tracker-7.1.0.tgz", + "integrity": "sha512-8YdplnuE1IK4xfqpf4iU7oBxnOYAc35934o083G8ao+8WM8QQtt/mVlAY6yIAdY1eMeLqg4Z//PZjJGmWGPMRg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@metamask/eth-json-rpc-provider": "^1.0.0", + "@metamask/safe-event-emitter": "^3.0.0", + "@metamask/utils": "^5.0.1", + "json-rpc-random-id": "^1.0.1", + "pify": "^3.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/eth-block-tracker/node_modules/@metamask/utils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@metamask/utils/-/utils-5.0.2.tgz", + "integrity": "sha512-yfmE79bRQtnMzarnKfX7AEJBwFTxvTyw3nBQlu/5rmGXrjAeAMltoGxO62TFurxrQAFMNa/fEjIHNvungZp0+g==", + "license": "ISC", + "peer": true, + "dependencies": { + "@ethereumjs/tx": "^4.1.2", + "@types/debug": "^4.1.7", + "debug": "^4.3.4", + "semver": "^7.3.8", + "superstruct": "^1.0.3" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/eth-block-tracker/node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eth-block-tracker/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/eth-json-rpc-filters": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/eth-json-rpc-filters/-/eth-json-rpc-filters-6.0.1.tgz", + "integrity": "sha512-ITJTvqoCw6OVMLs7pI8f4gG92n/St6x80ACtHodeS+IXmO0w+t1T5OOzfSt7KLSMLRkVUoexV7tztLgDxg+iig==", + "license": "ISC", + "peer": true, + "dependencies": { + "@metamask/safe-event-emitter": "^3.0.0", + "async-mutex": "^0.2.6", + "eth-query": "^2.1.2", + "json-rpc-engine": "^6.1.0", + "pify": "^5.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/eth-query": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/eth-query/-/eth-query-2.1.2.tgz", + "integrity": "sha512-srES0ZcvwkR/wd5OQBRA1bIJMww1skfGS0s8wlwK3/oNP4+wnds60krvu5R1QbpRQjMmpG5OMIWro5s7gvDPsA==", + "license": "ISC", + "peer": true, + "dependencies": { + "json-rpc-random-id": "^1.0.0", + "xtend": "^4.0.1" + } + }, + "node_modules/eth-rpc-errors": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eth-rpc-errors/-/eth-rpc-errors-4.0.3.tgz", + "integrity": "sha512-Z3ymjopaoft7JDoxZcEb3pwdGh7yiYMhOwm2doUt6ASXlMavpNlK6Cre0+IMl2VSGyEU9rkiperQhp5iRxn5Pg==", + "license": "MIT", + "peer": true, + "dependencies": { + "fast-safe-stringify": "^2.0.6" + } + }, + "node_modules/ethereum-cryptography": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz", + "integrity": "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@noble/curves": "1.4.2", + "@noble/hashes": "1.4.0", + "@scure/bip32": "1.4.0", + "@scure/bip39": "1.3.0" + } + }, + "node_modules/ethereum-cryptography/node_modules/@noble/curves": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", + "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ethereum-cryptography/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ethereum-cryptography/node_modules/@scure/base": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", + "integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ethereum-cryptography/node_modules/@scure/bip32": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.4.0.tgz", + "integrity": "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@noble/curves": "~1.4.0", + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ethereum-cryptography/node_modules/@scure/bip39": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.3.0.tgz", + "integrity": "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/event-target-shim": { "version": "5.0.1", "license": "MIT", @@ -14084,6 +15543,13 @@ "node": ">=6" } }, + "node_modules/eventemitter2": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", + "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==", + "license": "MIT", + "peer": true + }, "node_modules/eventemitter3": { "version": "4.0.7", "license": "MIT" @@ -14787,6 +16253,20 @@ "integrity": "sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==", "dev": true }, + "node_modules/extension-port-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/extension-port-stream/-/extension-port-stream-3.0.0.tgz", + "integrity": "sha512-an2S5quJMiy5bnZKEf6AkfH/7r8CzHvhchU40gxN+OM6HPhe7Z9T1FUychcf2M9PpPOO0Hf7BAEfJkw2TDIBDw==", + "license": "ISC", + "peer": true, + "dependencies": { + "readable-stream": "^3.6.2 || ^4.4.2", + "webextension-polyfill": ">=0.10.0 <1.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/external-editor": { "version": "3.1.0", "dev": true, @@ -14802,7 +16282,6 @@ }, "node_modules/fast-deep-equal": { "version": "3.1.3", - "dev": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -14846,6 +16325,13 @@ "node": ">=6" } }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "license": "MIT", + "peer": true + }, "node_modules/fast-xml-parser": { "version": "4.5.0", "funding": [ @@ -15380,15 +16866,22 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", + "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", "license": "MIT", "peer": true, "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -15397,6 +16890,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/get-package-type": { "version": "0.1.0", "dev": true, @@ -15496,6 +16999,20 @@ "version": "3.1.2", "license": "MIT" }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "peer": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stdin": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-9.0.0.tgz", @@ -15725,11 +17242,13 @@ "license": "(BSD-3-Clause AND Apache-2.0)" }, "node_modules/gopd": { - "version": "1.0.1", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", "peer": true, - "dependencies": { - "get-intrinsic": "^1.1.3" + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -15860,7 +17379,9 @@ } }, "node_modules/has-symbols": { - "version": "1.0.3", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "license": "MIT", "peer": true, "engines": { @@ -16504,6 +18025,23 @@ "url": "https://github.com/sponsors/brc-dd" } }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-array-buffer": { "version": "3.0.4", "license": "MIT", @@ -16685,6 +18223,25 @@ "node": ">=6" } }, + "node_modules/is-generator-function": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-glob": { "version": "4.0.3", "license": "MIT", @@ -16856,12 +18413,16 @@ } }, "node_modules/is-regex": { - "version": "1.1.4", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "license": "MIT", "peer": true, "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -17062,6 +18623,21 @@ "ws": "*" } }, + "node_modules/isows": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.6.tgz", + "integrity": "sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "dev": true, @@ -19218,6 +20794,34 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/json-rpc-engine": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/json-rpc-engine/-/json-rpc-engine-6.1.0.tgz", + "integrity": "sha512-NEdLrtrq1jUZyfjkr9OCz9EzCNhnRyWtt1PAnvnhwy6e8XETS0Dtc+ZNCO2gvuAoKsIn2+vCSowXTYE4CkgnAQ==", + "license": "ISC", + "peer": true, + "dependencies": { + "@metamask/safe-event-emitter": "^2.0.0", + "eth-rpc-errors": "^4.0.2" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/json-rpc-engine/node_modules/@metamask/safe-event-emitter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@metamask/safe-event-emitter/-/safe-event-emitter-2.0.0.tgz", + "integrity": "sha512-/kSXhY692qiV1MXu6EeOZvg5nECLclxNXcKCxJ3cXQgYuRymRHpdx/t7JXfsK+JLjwA1e1c1/SBrlQYpusC29Q==", + "license": "ISC", + "peer": true + }, + "node_modules/json-rpc-random-id": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-rpc-random-id/-/json-rpc-random-id-1.0.1.tgz", + "integrity": "sha512-RJ9YYNCkhVDBuP4zN5BBtYAzEl03yq/jIIsyif0JY9qyJuQQZNeDK7anAPKKlyEtLSj2s8h6hNh2F8zO5q7ScA==", + "license": "ISC", + "peer": true + }, "node_modules/json-schema-deref-sync": { "version": "0.13.0", "license": "MIT", @@ -19324,6 +20928,29 @@ "dev": true, "license": "MIT" }, + "node_modules/keccak": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.4.tgz", + "integrity": "sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q==", + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "dependencies": { + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/keccak/node_modules/node-addon-api": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", + "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==", + "license": "MIT", + "peer": true + }, "node_modules/keyv": { "version": "4.5.4", "dev": true, @@ -20388,6 +22015,16 @@ "license": "Apache-2.0", "peer": true }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/md5": { "version": "2.3.0", "license": "BSD-3-Clause", @@ -20422,6 +22059,16 @@ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" }, + "node_modules/media-query-parser": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/media-query-parser/-/media-query-parser-2.0.2.tgz", + "integrity": "sha512-1N4qp+jE0pL5Xv4uEcwVUhIkwdUO3S/9gML90nqKA7v7FcOS5vUtatfzok9S9U1EJU8dHWlcv95WLnKmmxZI9w==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.12.5" + } + }, "node_modules/memoize-one": { "version": "5.2.1", "license": "MIT", @@ -21420,6 +23067,13 @@ } } }, + "node_modules/micro-ftch": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/micro-ftch/-/micro-ftch-0.3.1.tgz", + "integrity": "sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg==", + "license": "MIT", + "peer": true + }, "node_modules/micromatch": { "version": "4.0.8", "license": "MIT", @@ -21675,6 +23329,26 @@ "version": "4.0.0", "license": "ISC" }, + "node_modules/mipd": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mipd/-/mipd-0.0.7.tgz", + "integrity": "sha512-aAPZPNDQ3uMTdKbuO2YmAw2TxLHO0moa4YKAyETM/DTj5FloZo+a+8tU+iv4GmW+sOxKLSRwcSFuczk+Cpt6fg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wagmi-dev" + } + ], + "license": "MIT", + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/mkdirp": { "version": "0.5.6", "license": "MIT", @@ -21696,6 +23370,13 @@ "ufo": "^1.5.4" } }, + "node_modules/modern-ahocorasick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/modern-ahocorasick/-/modern-ahocorasick-1.1.0.tgz", + "integrity": "sha512-sEKPVl2rM+MNVkGQt3ChdmD8YsigmXdn5NifZn6jiwn9LRJpWm8F3guhaqrJT/JOat6pwpbXEk6kv+b9DMIjsQ==", + "license": "MIT", + "peer": true + }, "node_modules/modify-values": { "version": "1.0.1", "dev": true, @@ -21949,6 +23630,18 @@ "node": "^16.14.0 || >=18.0.0" } }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "peer": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/node-gyp/node_modules/glob": { "version": "10.4.5", "dev": true, @@ -22633,6 +24326,51 @@ "node": ">=16" } }, + "node_modules/obj-multiplex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/obj-multiplex/-/obj-multiplex-1.0.0.tgz", + "integrity": "sha512-0GNJAOsHoBHeNTvl5Vt6IWnpUEcc3uSRxzBri7EDyIcMgYvnY2JL2qdeV5zTMjWQX5OHcD5amcW2HFfDh0gjIA==", + "license": "ISC", + "peer": true, + "dependencies": { + "end-of-stream": "^1.4.0", + "once": "^1.4.0", + "readable-stream": "^2.3.3" + } + }, + "node_modules/obj-multiplex/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT", + "peer": true + }, + "node_modules/obj-multiplex/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "peer": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/obj-multiplex/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "peer": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "license": "MIT", @@ -22878,6 +24616,41 @@ "integrity": "sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==", "dev": true }, + "node_modules/ox": { + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.6.7.tgz", + "integrity": "sha512-17Gk/eFsFRAZ80p5eKqv89a57uXjd3NgIf1CaXojATPBuujVc/fQSVhBeAU9JCRB+k7J50WQAyWTxK19T9GgbA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.10.1", + "@noble/curves": "^1.6.0", + "@noble/hashes": "^1.5.0", + "@scure/bip32": "^1.5.0", + "@scure/bip39": "^1.4.0", + "abitype": "^1.0.6", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/ox/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, "node_modules/p-filter": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz", @@ -23374,6 +25147,16 @@ "node": ">=4.0.0" } }, + "node_modules/pony-cause": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/pony-cause/-/pony-cause-2.1.11.tgz", + "integrity": "sha512-M7LhCsdNbNgiLYiP4WjsfLUuFmCfnjdF6jKe2R9NKl4WFN+HZPGHJZ9lnLP7f9ZnKe3U9nuWD0szirmj+migUg==", + "license": "0BSD", + "peer": true, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/possible-typed-array-names": { "version": "1.0.0", "license": "MIT", @@ -24967,6 +26750,69 @@ "node": ">=0.10.0" } }, + "node_modules/react-remove-scroll": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.2.tgz", + "integrity": "sha512-KmONPx5fnlXYJQqC62Q+lwIeAk64ws/cUw6omIumRzMRPqgnYqhSSti99nbj0Ry13bv7dF+BKn7NB+OqkdZGTw==", + "license": "MIT", + "peer": true, + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "peer": true + }, + "node_modules/react-remove-scroll/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "peer": true + }, "node_modules/react-shallow-renderer": { "version": "16.15.0", "license": "MIT", @@ -24979,6 +26825,36 @@ "react": "^16.0.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "peer": true + }, "node_modules/read": { "version": "3.0.1", "dev": true, @@ -25890,13 +27766,15 @@ "integrity": "sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w==" }, "node_modules/safe-regex-test": { - "version": "1.0.3", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", "license": "MIT", "peer": true, "dependencies": { - "call-bind": "^1.0.6", + "call-bound": "^1.0.2", "es-errors": "^1.3.0", - "is-regex": "^1.1.4" + "is-regex": "^1.2.1" }, "engines": { "node": ">= 0.4" @@ -26198,6 +28076,20 @@ "license": "ISC", "peer": true }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "license": "(MIT AND BSD-3-Clause)", + "peer": true, + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, "node_modules/sha3": { "version": "2.1.4", "license": "MIT", @@ -26351,6 +28243,36 @@ "version": "1.5.0", "license": "MIT" }, + "node_modules/socket.io-client": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "license": "MIT", + "peer": true, + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/socks": { "version": "2.8.3", "dev": true, @@ -26922,6 +28844,16 @@ "license": "MIT", "peer": true }, + "node_modules/superstruct": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-1.0.4.tgz", + "integrity": "sha512-7JpaAoX2NGyoFlI9NBh66BQXGONc+uE+MRS5i2iOBKuS4e+ccgMDjATgZldkah+33DakBxDHiss9kvUcGAO8UQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/supports-color": { "version": "5.5.0", "license": "MIT", @@ -28221,23 +30153,98 @@ "license": "MIT", "peer": true }, - "node_modules/use-sync-external-store": { - "version": "1.2.2", + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", "license": "MIT", "peer": true, + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/util": { - "version": "0.10.4", - "dev": true, + "node_modules/use-callback-ref/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "peer": true + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", "license": "MIT", + "peer": true, "dependencies": { - "inherits": "2.0.3" - } - }, - "node_modules/util-deprecate": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "peer": true + }, + "node_modules/use-sync-external-store": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", + "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==", + "license": "MIT", + "peer": true, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/utf-8-validate": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", + "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/util": { + "version": "0.10.4", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "2.0.3" + } + }, + "node_modules/util-deprecate": { "version": "1.0.2", "license": "MIT" }, @@ -28346,6 +30353,36 @@ "node": ">= 0.8" } }, + "node_modules/viem": { + "version": "2.22.21", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.22.21.tgz", + "integrity": "sha512-CujapStF+F3VP+bKBQOGFk5YHyJKZOY2TGvD1e04CAm8VrtLo3sfTydYW2Rri6LMktqp6ilGB9GvSiZczxvOBQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@noble/curves": "1.8.1", + "@noble/hashes": "1.7.1", + "@scure/bip32": "1.6.2", + "@scure/bip39": "1.5.4", + "abitype": "1.0.8", + "isows": "1.0.6", + "ox": "0.6.7", + "ws": "8.18.0" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/vlq": { "version": "1.0.1", "license": "MIT", @@ -28362,6 +30399,32 @@ "node": ">=14" } }, + "node_modules/wagmi": { + "version": "2.14.9", + "resolved": "https://registry.npmjs.org/wagmi/-/wagmi-2.14.9.tgz", + "integrity": "sha512-nDJ5hwPaiVpn/8Bi82m5K4BCqDiOSnOV976p/jKXt0svQABGdAxUxej0UgDRoVlrp+NutmejN+SyQKmhV477/A==", + "license": "MIT", + "peer": true, + "dependencies": { + "@wagmi/connectors": "5.7.5", + "@wagmi/core": "2.16.3", + "use-sync-external-store": "1.4.0" + }, + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "@tanstack/react-query": ">=5.0.0", + "react": ">=18", + "typescript": ">=5.0.4", + "viem": "2.x" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/walk-up-path": { "version": "3.0.1", "dev": true, @@ -28393,6 +30456,13 @@ "defaults": "^1.0.3" } }, + "node_modules/webextension-polyfill": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/webextension-polyfill/-/webextension-polyfill-0.10.0.tgz", + "integrity": "sha512-c5s35LgVa5tFaHhrZDnr3FpQpjj1BB+RXhLTYUxGqBVN460HkbM8TBtEqdXWbpTKfzwCcjAZVF7zXCYSKtcp9g==", + "license": "MPL-2.0", + "peer": true + }, "node_modules/webidl-conversions": { "version": "7.0.0", "dev": true, @@ -28967,6 +31037,15 @@ "dev": true, "license": "MIT" }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "peer": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/xtend": { "version": "4.0.2", "license": "MIT", @@ -29047,6 +31126,35 @@ "zod": "^3.18.0" } }, + "node_modules/zustand": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.0.tgz", + "integrity": "sha512-LE+VcmbartOPM+auOjCCLQOsQ05zUTp8RkgwRzefUk+2jISdMMFnxvyTjA4YNWr5ZGXYbVsEMZosttuxUBkojQ==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + }, "packages/config": { "name": "@onflow/config", "version": "1.5.1", @@ -29222,7 +31330,7 @@ "@babel/runtime": "^7.25.7", "@ethersproject/bytes": "^5.7.0", "@ethersproject/hash": "^5.7.0", - "@onflow/fcl": "1.13.4", + "@noble/hashes": "^1.7.1", "@onflow/rlp": "^1.2.3", "@walletconnect/jsonrpc-http-connection": "^1.0.8", "@walletconnect/jsonrpc-provider": "^1.0.14" @@ -29237,6 +31345,242 @@ "eslint": "^8.57.1", "eslint-plugin-jsdoc": "^46.10.1", "jest": "^29.7.0" + }, + "peerDependencies": { + "@onflow/fcl": "^1.13.4" + } + }, + "packages/fcl-rainbowkit-adapter": { + "name": "@onflow/fcl-rainbowkit-adapter", + "version": "0.0.0", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.25.7", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@onflow/fcl-ethereum-provider": "^0.0.0", + "@onflow/fcl-wagmi-adapter": "^0.0.0", + "@onflow/rlp": "^1.2.3", + "@wagmi/core": "^2.16.3", + "@walletconnect/jsonrpc-http-connection": "^1.0.8", + "@walletconnect/jsonrpc-provider": "^1.0.14", + "viem": "^2.22.21" + }, + "devDependencies": { + "@babel/preset-typescript": "^7.25.7", + "@onflow/fcl-bundle": "1.6.0", + "@onflow/typedefs": "^1.4.0", + "@types/jest": "^29.5.13", + "@typescript-eslint/eslint-plugin": "^6.21.0", + "@typescript-eslint/parser": "^6.21.0", + "eslint": "^8.57.1", + "eslint-plugin-jsdoc": "^46.10.1", + "jest": "^29.7.0" + }, + "peerDependencies": { + "@onflow/fcl": "^1.13.4", + "@rainbow-me/rainbowkit": "^2.2.3" + } + }, + "packages/fcl-rainbowkit-adapter/node_modules/@rainbow-me/rainbowkit": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@rainbow-me/rainbowkit/-/rainbowkit-2.2.3.tgz", + "integrity": "sha512-kXZ+zmKSPZhZdNHey+4TwOIW4p2vIRv5B9+5FoTJv1xktru1RykfAAQY5z+nLUdvc0l6M5hiYH4X88Jl1foXGQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@vanilla-extract/css": "1.15.5", + "@vanilla-extract/dynamic": "2.1.2", + "@vanilla-extract/sprinkles": "1.6.3", + "clsx": "2.1.1", + "qrcode": "1.5.4", + "react-remove-scroll": "2.6.2", + "ua-parser-js": "^1.0.37" + }, + "engines": { + "node": ">=12.4" + }, + "peerDependencies": { + "@tanstack/react-query": ">=5.0.0", + "react": ">=18", + "react-dom": ">=18", + "viem": "2.x", + "wagmi": "^2.9.0" + } + }, + "packages/fcl-rainbowkit-adapter/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "packages/fcl-rainbowkit-adapter/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "license": "ISC", + "peer": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "packages/fcl-rainbowkit-adapter/node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "packages/fcl-rainbowkit-adapter/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "packages/fcl-rainbowkit-adapter/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT", + "peer": true + }, + "packages/fcl-rainbowkit-adapter/node_modules/pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.13.0" + } + }, + "packages/fcl-rainbowkit-adapter/node_modules/qrcode": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", + "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", + "license": "MIT", + "peer": true, + "dependencies": { + "dijkstrajs": "^1.0.1", + "pngjs": "^5.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "qrcode": "bin/qrcode" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "packages/fcl-rainbowkit-adapter/node_modules/react": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", + "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "packages/fcl-rainbowkit-adapter/node_modules/react-dom": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", + "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.25.0" + }, + "peerDependencies": { + "react": "^19.0.0" + } + }, + "packages/fcl-rainbowkit-adapter/node_modules/scheduler": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", + "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", + "license": "MIT", + "peer": true + }, + "packages/fcl-rainbowkit-adapter/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "packages/fcl-rainbowkit-adapter/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "license": "ISC", + "peer": true + }, + "packages/fcl-rainbowkit-adapter/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "license": "MIT", + "peer": true, + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "packages/fcl-rainbowkit-adapter/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "license": "ISC", + "peer": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" } }, "packages/fcl-react-native": { @@ -29299,6 +31643,36 @@ "node": ">=4.2.0" } }, + "packages/fcl-wagmi-adapter": { + "name": "@onflow/fcl-wagmi-adapter", + "version": "0.0.0", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.25.7", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@onflow/fcl-ethereum-provider": "^0.0.0", + "@onflow/rlp": "^1.2.3", + "@walletconnect/jsonrpc-http-connection": "^1.0.8", + "@walletconnect/jsonrpc-provider": "^1.0.14", + "viem": "^2.22.21" + }, + "devDependencies": { + "@babel/preset-typescript": "^7.25.7", + "@onflow/fcl-bundle": "1.6.0", + "@onflow/typedefs": "^1.4.0", + "@types/jest": "^29.5.13", + "@typescript-eslint/eslint-plugin": "^6.21.0", + "@typescript-eslint/parser": "^6.21.0", + "eslint": "^8.57.1", + "eslint-plugin-jsdoc": "^46.10.1", + "jest": "^29.7.0" + }, + "peerDependencies": { + "@onflow/fcl": "^1.13.4", + "@wagmi/core": "^2.16.3" + } + }, "packages/fcl-wc": { "name": "@onflow/fcl-wc", "version": "5.5.4", diff --git a/packages/fcl-bundle/src/build/get-input-options.js b/packages/fcl-bundle/src/build/get-input-options.js index 936e06066..861a034c6 100644 --- a/packages/fcl-bundle/src/build/get-input-options.js +++ b/packages/fcl-bundle/src/build/get-input-options.js @@ -44,17 +44,14 @@ module.exports = function getInputOptions(package, build) { .concat(Object.keys(package.dependencies || {})) let testExternal = id => { - return ( - build.type !== "umd" && - (/@babel\/runtime/g.test(id) || - external.reduce((state, ext) => { - return ( - state || - (ext instanceof RegExp && ext.test(id)) || - (typeof ext === "string" && ext === id) - ) - }, false)) - ) + if (build.type !== "umd" && /@babel\/runtime/g.test(id)) return true + + for (let ext of external) { + if (ext instanceof RegExp && ext.test(id)) return true + if (typeof ext === "string" && id.startsWith(ext)) return true + } + + return false } // exclude peer dependencies diff --git a/packages/fcl-ethereum-provider/package.json b/packages/fcl-ethereum-provider/package.json index 9907ac63d..3cd7ea871 100644 --- a/packages/fcl-ethereum-provider/package.json +++ b/packages/fcl-ethereum-provider/package.json @@ -39,9 +39,12 @@ "@babel/runtime": "^7.25.7", "@ethersproject/bytes": "^5.7.0", "@ethersproject/hash": "^5.7.0", - "@onflow/fcl": "1.13.4", + "@noble/hashes": "^1.7.1", "@onflow/rlp": "^1.2.3", "@walletconnect/jsonrpc-http-connection": "^1.0.8", "@walletconnect/jsonrpc-provider": "^1.0.14" + }, + "peerDependencies": { + "@onflow/fcl": "^1.13.4" } } diff --git a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts index 30fc74838..3b327ff66 100644 --- a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts +++ b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts @@ -1,6 +1,11 @@ import * as fcl from "@onflow/fcl" import * as rlp from "@onflow/rlp" -import {CompositeSignature, CurrentUser, FvmErrorCode} from "@onflow/typedefs" +import { + CompositeSignature, + CurrentUser, + Service, + FvmErrorCode, +} from "@onflow/typedefs" import { EVENT_IDENTIFIERS, EventType, @@ -40,7 +45,8 @@ export class AccountManager { constructor( private user: typeof fcl.currentUser, - private networkManager: NetworkManager + private networkManager: NetworkManager, + private service?: Service ) { // Create an observable from the user const $user = new Observable(subscriber => { @@ -87,12 +93,16 @@ export class AccountManager { .subscribe(this.$addressStore) } - public async authenticate(): Promise { - return await this.user.authenticate() + public async authenticate(): Promise { + await this.user.authenticate({service: this.service}) + return this.getAccounts().then(accounts => { + return accounts + }) } public async unauthenticate(): Promise { await this.user.unauthenticate() + await new Promise(resolve => setTimeout(resolve, 0)) } private async waitForTxResult( @@ -130,7 +140,7 @@ export class AccountManager { if (error) { throw error } - return address + return address || null } public async getAccounts(): Promise { diff --git a/packages/fcl-ethereum-provider/src/cadence.ts b/packages/fcl-ethereum-provider/src/cadence.ts index fc821101f..22ad17f99 100644 --- a/packages/fcl-ethereum-provider/src/cadence.ts +++ b/packages/fcl-ethereum-provider/src/cadence.ts @@ -1,22 +1,23 @@ import {getContractAddress} from "./util/eth" import {ContractType} from "./constants" -export const getCOAScript = (chainId: number) => ` -import EVM from ${getContractAddress(ContractType.EVM, chainId)} - -access(all) -fun main(address: Address): String? { - if let coa = getAuthAccount(address) - .storage - .borrow<&EVM.CadenceOwnedAccount>(from: /storage/evm) { - return coa.address().toString() - } - return nil -} -` - -export const createCOATx = (chainId: number) => ` -import EVM from ${getContractAddress(ContractType.EVM, chainId)} +export const getCOAScript = ( + chainId: number +) => `import EVM from ${getContractAddress(ContractType.EVM, chainId)} + +/// Returns the hex encoded address of the COA in the given Flow address +/// +access(all) fun main(flowAddress: Address): String? { + return getAuthAccount(flowAddress) + .storage.borrow<&EVM.CadenceOwnedAccount>(from: /storage/evm) + ?.address() + ?.toString() + ?? nil +}` + +export const createCOATx = ( + chainId: number +) => `import EVM from ${getContractAddress(ContractType.EVM, chainId)} transaction() { prepare(signer: auth(SaveValue, IssueStorageCapabilityController, PublishCapability) &Account) { @@ -29,11 +30,11 @@ transaction() { let cap = signer.capabilities.storage.issue<&EVM.CadenceOwnedAccount>(storagePath) signer.capabilities.publish(cap, at: publicPath) } -} -` +}` -export const sendTransactionTx = (chainId: number) => ` -import EVM from ${getContractAddress(ContractType.EVM, chainId)} +export const sendTransactionTx = ( + chainId: number +) => `import EVM from ${getContractAddress(ContractType.EVM, chainId)} /// Executes the calldata from the signer's COA transaction(evmContractAddressHex: String, calldata: String, gasLimit: UInt64, value: UInt256) { @@ -58,5 +59,4 @@ transaction(evmContractAddressHex: String, calldata: String, gasLimit: UInt64, v ) assert(callResult.status == EVM.Status.successful, message: "Call failed") } -} -` +}` diff --git a/packages/fcl-ethereum-provider/src/constants.ts b/packages/fcl-ethereum-provider/src/constants.ts index 5493c42a8..a0b530b1a 100644 --- a/packages/fcl-ethereum-provider/src/constants.ts +++ b/packages/fcl-ethereum-provider/src/constants.ts @@ -6,11 +6,11 @@ export enum FlowNetwork { export const FLOW_CHAINS = { [FlowNetwork.MAINNET]: { eip155ChainId: 747, - publicRpcUrl: "https://access.mainnet.nodes.onflow.org", + publicRpcUrl: "https://mainnet.evm.nodes.onflow.org", }, [FlowNetwork.TESTNET]: { eip155ChainId: 545, - publicRpcUrl: "https://access.testnet.nodes.onflow.org", + publicRpcUrl: "https://testnet.evm.nodes.onflow.org", }, } diff --git a/packages/fcl-ethereum-provider/src/create-provider.ts b/packages/fcl-ethereum-provider/src/create-provider.ts index 185f83668..1b1fd3ea4 100644 --- a/packages/fcl-ethereum-provider/src/create-provider.ts +++ b/packages/fcl-ethereum-provider/src/create-provider.ts @@ -8,6 +8,7 @@ import {AccountManager} from "./accounts/account-manager" import {FLOW_CHAINS} from "./constants" import {Gateway} from "./gateway/gateway" import {NetworkManager} from "./network/network-manager" +import {Subject} from "./util/observable" /** * Create a new FCL Ethereum provider @@ -43,7 +44,11 @@ export function createProvider(config: { ) const networkManager = new NetworkManager(config.config) - const accountManager = new AccountManager(config.user, networkManager) + const accountManager = new AccountManager( + config.user, + networkManager, + config.service + ) const gateway = new Gateway({ ...defaultRpcUrls, ...(config.rpcUrls || {}), diff --git a/packages/fcl-ethereum-provider/src/events/event-dispatcher.test.ts b/packages/fcl-ethereum-provider/src/events/event-dispatcher.test.ts index 7f338e749..ef041313e 100644 --- a/packages/fcl-ethereum-provider/src/events/event-dispatcher.test.ts +++ b/packages/fcl-ethereum-provider/src/events/event-dispatcher.test.ts @@ -8,8 +8,8 @@ jest.mock("../network/network-manager") describe("event dispatcher", () => { let networkManager: jest.Mocked - let accountManager: jest.Mocked let $mockChainId: Subject + let accountManager: jest.Mocked beforeEach(() => { jest.clearAllMocks() @@ -38,7 +38,7 @@ describe("event dispatcher", () => { expect(accountManager.subscribe).toHaveBeenCalledWith(expect.any(Function)) // Simulate account change from account manager - subs.forEach(sub => sub(["0x1234"])) + subs.forEach(sub => sub(["1234"])) expect(listener).toHaveBeenCalled() expect(listener).toHaveBeenCalledTimes(1) @@ -47,7 +47,7 @@ describe("event dispatcher", () => { eventDispatcher.off("accountsChanged", listener) // Simulate account change from account manager - subs.forEach(sub => sub(["0x5678"])) + subs.forEach(sub => sub(["5678"])) expect(listener).toHaveBeenCalledTimes(1) }) @@ -68,7 +68,7 @@ describe("event dispatcher", () => { expect(accountManager.subscribe).toHaveBeenCalledWith(expect.any(Function)) // Simulate account change from account manager - mockMgrSubCb!(["0x1234"]) + mockMgrSubCb!(["1234"]) expect(listener).toHaveBeenCalled() expect(listener).toHaveBeenCalledTimes(1) @@ -91,8 +91,8 @@ describe("event dispatcher", () => { expect(accountManager.subscribe).toHaveBeenCalledWith(expect.any(Function)) // Simulate account change from account manager - mockMgrSubCb!(["0x1234"]) - mockMgrSubCb!(["0x5678"]) + mockMgrSubCb!(["1234"]) + mockMgrSubCb!(["5678"]) expect(listener).toHaveBeenCalled() expect(listener).toHaveBeenCalledTimes(2) diff --git a/packages/fcl-ethereum-provider/src/events/event-dispatcher.ts b/packages/fcl-ethereum-provider/src/events/event-dispatcher.ts index 081b36c67..559fe4d6a 100644 --- a/packages/fcl-ethereum-provider/src/events/event-dispatcher.ts +++ b/packages/fcl-ethereum-provider/src/events/event-dispatcher.ts @@ -10,6 +10,7 @@ import { takeFirst, } from "../util/observable" import {formatChainId} from "../util/eth" +import {withPrefix} from "@onflow/fcl" export class EventDispatcher { private $emitters: { @@ -27,7 +28,7 @@ export class EventDispatcher { // Emit changes to the accounts as an accountsChanged event accountsChanged: new Observable(subscriber => { return accountManager.subscribe(accounts => { - subscriber.next(accounts) + subscriber.next(accounts.map(x => withPrefix(x))) }) }), // Emit changes to the chainId as a chainChanged event diff --git a/packages/fcl-ethereum-provider/src/gateway/gateway.test.ts b/packages/fcl-ethereum-provider/src/gateway/gateway.test.ts index c4b3c0128..174b72f48 100644 --- a/packages/fcl-ethereum-provider/src/gateway/gateway.test.ts +++ b/packages/fcl-ethereum-provider/src/gateway/gateway.test.ts @@ -162,7 +162,7 @@ describe("gateway", () => { jest.mocked(HttpConnection).mock.instances[0] ) expect(HttpConnection).toHaveBeenCalledWith( - "https://access.mainnet.nodes.onflow.org" + "https://mainnet.evm.nodes.onflow.org" ) }) @@ -191,7 +191,7 @@ describe("gateway", () => { jest.mocked(HttpConnection).mock.instances[0] ) expect(HttpConnection).toHaveBeenCalledWith( - "https://access.testnet.nodes.onflow.org" + "https://testnet.evm.nodes.onflow.org" ) }) }) diff --git a/packages/fcl-ethereum-provider/src/provider.ts b/packages/fcl-ethereum-provider/src/provider.ts index 617b74f29..400bd0ade 100644 --- a/packages/fcl-ethereum-provider/src/provider.ts +++ b/packages/fcl-ethereum-provider/src/provider.ts @@ -29,8 +29,8 @@ export class FclEthereumProvider implements Eip1193Provider { return result } - disconnect(): void { - this.acountManager.unauthenticate() + async disconnect(): Promise { + await this.acountManager.unauthenticate() } // Listen to events diff --git a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.test.ts b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.test.ts index 523138234..ccc04b7a9 100644 --- a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.test.ts +++ b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.test.ts @@ -47,20 +47,21 @@ describe("ethRequestAccounts handler", () => { it("should call authenticate, updateCOAAddress, and return the manager's accounts", async () => { accountManagerMock.getAndCreateAccounts.mockResolvedValue(["0x1234..."]) - const accounts = await ethRequestAccounts(accountManagerMock) + const accounts = await ethRequestAccounts(accountManagerMock, 747) - expect(accountManagerMock.authenticate).toHaveBeenCalled() - expect(accountManagerMock.getAndCreateAccounts).toHaveBeenCalled() expect(accounts).toEqual(["0x1234..."]) + + expect(accountManagerMock.authenticate).toHaveBeenCalled() + expect(accountManagerMock.getAndCreateAccounts).toHaveBeenCalledWith(747) }) it("should handle empty accounts scenario", async () => { accountManagerMock.getAndCreateAccounts.mockResolvedValue([]) - const accounts = await ethRequestAccounts(accountManagerMock) + const accounts = await ethRequestAccounts(accountManagerMock, 747) expect(accountManagerMock.authenticate).toHaveBeenCalled() - expect(accountManagerMock.getAndCreateAccounts).toHaveBeenCalled() + expect(accountManagerMock.getAndCreateAccounts).toHaveBeenCalledWith(747) expect(accounts).toEqual([]) }) }) diff --git a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.ts b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.ts index 0e83bd4a4..21c6c01fb 100644 --- a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.ts +++ b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-accounts.ts @@ -1,9 +1,11 @@ +import {withPrefix} from "@onflow/fcl" import {AccountManager} from "../../accounts/account-manager" export async function ethAccounts( accountManager: AccountManager ): Promise { - return await accountManager.getAccounts() + const accounts = await accountManager.getAccounts() + return accounts.map(x => withPrefix(x)) } export async function ethRequestAccounts( @@ -11,6 +13,6 @@ export async function ethRequestAccounts( chainId: number ): Promise { await accountManager.authenticate() - - return await accountManager.getAndCreateAccounts(chainId) + const accounts = await accountManager.getAndCreateAccounts(chainId) + return accounts.map(x => withPrefix(x)) } diff --git a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-chain-id.test.ts b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-chain-id.test.ts index 94d6e578f..212f4cbfc 100644 --- a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-chain-id.test.ts +++ b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-chain-id.test.ts @@ -1,4 +1,4 @@ -import {formatChainId} from "../../util/eth" +import {NetworkManager} from "../../network/network-manager" import {ethChainId} from "./eth-chain-id" jest.mock("../../util/eth", () => ({ @@ -13,7 +13,7 @@ describe("eth_chainId handler", () => { } networkManagerMock.getChainId.mockResolvedValue(747) - const chainId = await ethChainId(networkManagerMock as any)() + const chainId = await ethChainId(networkManagerMock as any) expect(chainId).toEqual("0x747") expect(networkManagerMock.getChainId).toHaveBeenCalled() @@ -22,10 +22,10 @@ describe("eth_chainId handler", () => { test("should throw an error if no chain id is available", async () => { const networkManagerMock = { getChainId: jest.fn(), - } + } as unknown as jest.Mocked networkManagerMock.getChainId.mockResolvedValue(null) - await expect(ethChainId(networkManagerMock as any)()).rejects.toThrow( + await expect(ethChainId(networkManagerMock)).rejects.toThrow( "No active chain" ) expect(networkManagerMock.getChainId).toHaveBeenCalled() diff --git a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-chain-id.ts b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-chain-id.ts index 9246bcfe3..4614af03e 100644 --- a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-chain-id.ts +++ b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-chain-id.ts @@ -1,12 +1,10 @@ import {NetworkManager} from "../../network/network-manager" import {formatChainId} from "../../util/eth" -export function ethChainId(networkManager: NetworkManager) { - return async function () { - const chainId = await networkManager.getChainId() - if (!chainId) { - throw new Error("No active chain") - } - return formatChainId(chainId) +export async function ethChainId(networkManager: NetworkManager) { + const chainId = await networkManager.getChainId() + if (!chainId) { + throw new Error("No active chain") } + return formatChainId(chainId) } diff --git a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.test.ts b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.test.ts index b292a609c..36d17b3dc 100644 --- a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.test.ts +++ b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.test.ts @@ -122,4 +122,114 @@ describe("rpc processor", () => { cause: new Error("test error"), }) }) + + test("caught RpcError should be rethrown", async () => { + const gateway: jest.Mocked = new (Gateway as any)() + const accountManager: jest.Mocked = + new (AccountManager as any)() + const networkManager: jest.Mocked = + new (NetworkManager as any)() + const rpcProcessor = new RpcProcessor( + gateway, + accountManager, + networkManager + ) + + const error = new Error("test error") + ;(error as any).code = -32000 + jest.mocked(gateway).request.mockRejectedValue(error) + networkManager.getChainId.mockResolvedValue(747) + + await expect( + rpcProcessor.handleRequest({ + method: "eth_blockNumber", + params: [], + }) + ).rejects.toMatchObject({ + code: -32000, + message: "test error", + }) + }) + + test("caught generic error should be rethrown as an internal error", async () => { + const gateway: jest.Mocked = new (Gateway as any)() + const accountManager: jest.Mocked = + new (AccountManager as any)() + const networkManager: jest.Mocked = + new (NetworkManager as any)() + const rpcProcessor = new RpcProcessor( + gateway, + accountManager, + networkManager + ) + + jest.mocked(gateway).request.mockRejectedValue(new Error("test error")) + networkManager.getChainId.mockResolvedValue(747) + + const promise = rpcProcessor.handleRequest({ + method: "eth_blockNumber", + params: [], + }) + + await expect(promise).rejects.toMatchObject({ + code: -32603, + message: "Internal error", + cause: new Error("test error"), + }) + }) + + test("caught RpcError should be rethrown", async () => { + const gateway: jest.Mocked = new (Gateway as any)() + const accountManager: jest.Mocked = + new (AccountManager as any)() + const networkManager: jest.Mocked = + new (NetworkManager as any)() + const rpcProcessor = new RpcProcessor( + gateway, + accountManager, + networkManager + ) + + const error = new Error("test error") + ;(error as any).code = -32000 + jest.mocked(gateway).request.mockRejectedValue(error) + networkManager.getChainId.mockResolvedValue(747) + + await expect( + rpcProcessor.handleRequest({ + method: "eth_blockNumber", + params: [], + }) + ).rejects.toMatchObject({ + code: -32000, + message: "test error", + }) + }) + + test("caught generic error should be rethrown as an internal error", async () => { + const gateway: jest.Mocked = new (Gateway as any)() + const accountManager: jest.Mocked = + new (AccountManager as any)() + const networkManager: jest.Mocked = + new (NetworkManager as any)() + const rpcProcessor = new RpcProcessor( + gateway, + accountManager, + networkManager + ) + + jest.mocked(gateway).request.mockRejectedValue(new Error("test error")) + networkManager.getChainId.mockResolvedValue(747) + + const promise = rpcProcessor.handleRequest({ + method: "eth_blockNumber", + params: [], + }) + + await expect(promise).rejects.toMatchObject({ + code: -32603, + message: "Internal error", + cause: new Error("test error"), + }) + }) }) diff --git a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts index ba50102b7..c153c8c43 100644 --- a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts +++ b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts @@ -88,7 +88,7 @@ export class RpcProcessor { const switchParams = params[0] as SwitchEthereumChainParams return await this.networkManager.switchChain(switchParams) case "eth_chainId": - return ethChainId(this.networkManager) + return await ethChainId(this.networkManager) default: return await this.gateway.request({ chainId, diff --git a/packages/fcl-ethereum-provider/src/types/provider.ts b/packages/fcl-ethereum-provider/src/types/provider.ts index e49dfbd2e..f2ae42a93 100644 --- a/packages/fcl-ethereum-provider/src/types/provider.ts +++ b/packages/fcl-ethereum-provider/src/types/provider.ts @@ -28,4 +28,5 @@ export interface Eip1193Provider { event: E, listener: EventCallback ): void + disconnect(): Promise } diff --git a/packages/fcl-rainbowkit-adapter/.babelrc b/packages/fcl-rainbowkit-adapter/.babelrc new file mode 100644 index 000000000..d766c90b2 --- /dev/null +++ b/packages/fcl-rainbowkit-adapter/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@babel/preset-env"], "@babel/preset-typescript"] +} diff --git a/packages/fcl-rainbowkit-adapter/.browserslistrc b/packages/fcl-rainbowkit-adapter/.browserslistrc new file mode 100644 index 000000000..6114ba78d --- /dev/null +++ b/packages/fcl-rainbowkit-adapter/.browserslistrc @@ -0,0 +1,2 @@ +defaults and supports es6-module +maintained node versions diff --git a/packages/fcl-rainbowkit-adapter/.eslintrc.json b/packages/fcl-rainbowkit-adapter/.eslintrc.json new file mode 100644 index 000000000..e80c057f5 --- /dev/null +++ b/packages/fcl-rainbowkit-adapter/.eslintrc.json @@ -0,0 +1,20 @@ +{ + "env": { + "browser": true, + "es2021": true, + "jest": true, + "node": true + }, + "extends": [ + "plugin:jsdoc/recommended-typescript", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" + ], + "plugins": ["jsdoc", "@typescript-eslint"], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "ignorePatterns": ["**/dist/"] +} diff --git a/packages/fcl-rainbowkit-adapter/.npmignore b/packages/fcl-rainbowkit-adapter/.npmignore new file mode 100644 index 000000000..8eba6c8dd --- /dev/null +++ b/packages/fcl-rainbowkit-adapter/.npmignore @@ -0,0 +1 @@ +src/ diff --git a/packages/fcl-rainbowkit-adapter/CHANGELOG.md b/packages/fcl-rainbowkit-adapter/CHANGELOG.md new file mode 100644 index 000000000..fdb6897bf --- /dev/null +++ b/packages/fcl-rainbowkit-adapter/CHANGELOG.md @@ -0,0 +1 @@ +# @onflow/fcl-ethereum-provider diff --git a/packages/fcl-rainbowkit-adapter/README.md b/packages/fcl-rainbowkit-adapter/README.md new file mode 100644 index 000000000..ad868842a --- /dev/null +++ b/packages/fcl-rainbowkit-adapter/README.md @@ -0,0 +1 @@ +# @onflow/fcl-rainbowkit-adapter diff --git a/packages/fcl-rainbowkit-adapter/package.json b/packages/fcl-rainbowkit-adapter/package.json new file mode 100644 index 000000000..8998b5a3a --- /dev/null +++ b/packages/fcl-rainbowkit-adapter/package.json @@ -0,0 +1,54 @@ +{ + "name": "@onflow/fcl-rainbowkit-adapter", + "version": "0.0.0", + "description": "Rainbowkit adapter for FCL-compatible wallets", + "license": "Apache-2.0", + "author": "Dapper Labs ", + "homepage": "https://onflow.org", + "repository": { + "type": "git", + "url": "git+ssh://git@github.com/onflow/flow-js-sdk.git" + }, + "bugs": { + "url": "https://github.com/onflow/flow-js-sdk/issues" + }, + "devDependencies": { + "@babel/preset-typescript": "^7.25.7", + "@onflow/fcl-bundle": "1.6.0", + "@onflow/typedefs": "^1.4.0", + "@types/jest": "^29.5.13", + "@typescript-eslint/eslint-plugin": "^6.21.0", + "@typescript-eslint/parser": "^6.21.0", + "eslint": "^8.57.1", + "eslint-plugin-jsdoc": "^46.10.1", + "jest": "^29.7.0" + }, + "source": "src/index.ts", + "main": "dist/index.js", + "module": "dist/index.module.js", + "unpkg": "dist/index.umd.js", + "types": "types/index.d.ts", + "scripts": { + "prepublishOnly": "npm test && npm run build", + "test": "jest", + "build": "fcl-bundle", + "test:watch": "jest --watch", + "start": "fcl-bundle --watch" + }, + "dependencies": { + "@babel/runtime": "^7.25.7", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@onflow/fcl-ethereum-provider": "^0.0.0", + "@onflow/fcl-wagmi-adapter": "^0.0.0", + "@onflow/rlp": "^1.2.3", + "@wagmi/core": "^2.16.3", + "@walletconnect/jsonrpc-http-connection": "^1.0.8", + "@walletconnect/jsonrpc-provider": "^1.0.14", + "viem": "^2.22.21" + }, + "peerDependencies": { + "@onflow/fcl": "^1.13.4", + "@rainbow-me/rainbowkit": "^2.2.3" + } +} diff --git a/packages/fcl-rainbowkit-adapter/src/index.ts b/packages/fcl-rainbowkit-adapter/src/index.ts new file mode 100644 index 000000000..00ad8b1bc --- /dev/null +++ b/packages/fcl-rainbowkit-adapter/src/index.ts @@ -0,0 +1,32 @@ +import {fclWagmiAdapter} from "@onflow/fcl-wagmi-adapter" +import {Wallet} from "@rainbow-me/rainbowkit" +import {createConnector} from "@wagmi/core" + +type FclConnectorOptions = Parameters[0] + +type DefaultWalletOptions = { + projectId: string +} + +const FALLBACK_ICON = + "https://assets.website-files.com/5f6294c0c7a8cdd643b1c820/5f6294c0c7a8cda55cb1c936_Flow_Wordmark.svg" + +export const createFclConnector = (options: FclConnectorOptions) => { + const uid = options.service?.uid + const name = options.service?.provider?.name + const iconUrl = options.service?.provider?.icon! + + return ({projectId}: DefaultWalletOptions): Wallet => ({ + id: uid ? `fcl-${uid}` : "fcl", + name: name || "Cadence Wallet", + iconUrl: iconUrl || FALLBACK_ICON, + iconBackground: "#FFFFFF", + createConnector: walletDetails => { + const connector = fclWagmiAdapter(options) + return createConnector(config => ({ + ...connector(config), + ...walletDetails, + })) + }, + }) +} diff --git a/packages/fcl-rainbowkit-adapter/tsconfig.json b/packages/fcl-rainbowkit-adapter/tsconfig.json new file mode 100644 index 000000000..617d9a8ae --- /dev/null +++ b/packages/fcl-rainbowkit-adapter/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig", + // Change this to match your project + "include": ["src/**/*"], + "compilerOptions": { + "declarationDir": "types", + "rootDir": "src" + } +} diff --git a/packages/fcl-wagmi-adapter/.babelrc b/packages/fcl-wagmi-adapter/.babelrc new file mode 100644 index 000000000..d766c90b2 --- /dev/null +++ b/packages/fcl-wagmi-adapter/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": [["@babel/preset-env"], "@babel/preset-typescript"] +} diff --git a/packages/fcl-wagmi-adapter/.browserslistrc b/packages/fcl-wagmi-adapter/.browserslistrc new file mode 100644 index 000000000..6114ba78d --- /dev/null +++ b/packages/fcl-wagmi-adapter/.browserslistrc @@ -0,0 +1,2 @@ +defaults and supports es6-module +maintained node versions diff --git a/packages/fcl-wagmi-adapter/.eslintrc.json b/packages/fcl-wagmi-adapter/.eslintrc.json new file mode 100644 index 000000000..e80c057f5 --- /dev/null +++ b/packages/fcl-wagmi-adapter/.eslintrc.json @@ -0,0 +1,20 @@ +{ + "env": { + "browser": true, + "es2021": true, + "jest": true, + "node": true + }, + "extends": [ + "plugin:jsdoc/recommended-typescript", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" + ], + "plugins": ["jsdoc", "@typescript-eslint"], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "ignorePatterns": ["**/dist/"] +} diff --git a/packages/fcl-wagmi-adapter/.npmignore b/packages/fcl-wagmi-adapter/.npmignore new file mode 100644 index 000000000..8eba6c8dd --- /dev/null +++ b/packages/fcl-wagmi-adapter/.npmignore @@ -0,0 +1 @@ +src/ diff --git a/packages/fcl-wagmi-adapter/CHANGELOG.md b/packages/fcl-wagmi-adapter/CHANGELOG.md new file mode 100644 index 000000000..fdb6897bf --- /dev/null +++ b/packages/fcl-wagmi-adapter/CHANGELOG.md @@ -0,0 +1 @@ +# @onflow/fcl-ethereum-provider diff --git a/packages/fcl-wagmi-adapter/README.md b/packages/fcl-wagmi-adapter/README.md new file mode 100644 index 000000000..7e305a98b --- /dev/null +++ b/packages/fcl-wagmi-adapter/README.md @@ -0,0 +1 @@ +# @onflow/fcl-wagmi-adapter \ No newline at end of file diff --git a/packages/fcl-wagmi-adapter/package.json b/packages/fcl-wagmi-adapter/package.json new file mode 100644 index 000000000..27dcc5ce2 --- /dev/null +++ b/packages/fcl-wagmi-adapter/package.json @@ -0,0 +1,52 @@ +{ + "name": "@onflow/fcl-wagmi-adapter", + "version": "0.0.0", + "description": "Wagmi adapter for FCL-compatible wallets", + "license": "Apache-2.0", + "author": "Dapper Labs ", + "homepage": "https://onflow.org", + "repository": { + "type": "git", + "url": "git+ssh://git@github.com/onflow/flow-js-sdk.git" + }, + "bugs": { + "url": "https://github.com/onflow/flow-js-sdk/issues" + }, + "devDependencies": { + "@babel/preset-typescript": "^7.25.7", + "@onflow/fcl-bundle": "1.6.0", + "@onflow/typedefs": "^1.4.0", + "@types/jest": "^29.5.13", + "@typescript-eslint/eslint-plugin": "^6.21.0", + "@typescript-eslint/parser": "^6.21.0", + "eslint": "^8.57.1", + "eslint-plugin-jsdoc": "^46.10.1", + "jest": "^29.7.0" + }, + "source": "src/index.ts", + "main": "dist/index.js", + "module": "dist/index.module.js", + "unpkg": "dist/index.umd.js", + "types": "types/index.d.ts", + "scripts": { + "prepublishOnly": "npm test && npm run build", + "test": "jest", + "build": "fcl-bundle", + "test:watch": "jest --watch", + "start": "fcl-bundle --watch" + }, + "dependencies": { + "@babel/runtime": "^7.25.7", + "@ethersproject/bytes": "^5.7.0", + "@ethersproject/hash": "^5.7.0", + "@onflow/fcl-ethereum-provider": "^0.0.0", + "@onflow/rlp": "^1.2.3", + "@walletconnect/jsonrpc-http-connection": "^1.0.8", + "@walletconnect/jsonrpc-provider": "^1.0.14", + "viem": "^2.22.21" + }, + "peerDependencies": { + "@onflow/fcl": "^1.13.4", + "@wagmi/core": "^2.16.3" + } +} diff --git a/packages/fcl-wagmi-adapter/src/index.ts b/packages/fcl-wagmi-adapter/src/index.ts new file mode 100644 index 000000000..615c512ad --- /dev/null +++ b/packages/fcl-wagmi-adapter/src/index.ts @@ -0,0 +1,215 @@ +import { + ChainNotConfiguredError, + type Connector, + createConnector, +} from "@wagmi/core" +import { + type Address, + type ProviderConnectInfo, + ProviderDisconnectedError, + SwitchChainError, + getAddress, + numberToHex, +} from "viem" +import {createProvider} from "@onflow/fcl-ethereum-provider" + +type FclWagmiAdapterParams = Parameters[0] + +export function fclWagmiAdapter(params: FclWagmiAdapterParams) { + type Provider = ReturnType + type Properties = { + onConnect(connectInfo: ProviderConnectInfo): void + onDisplayUri(uri: string): void + } + let provider: Provider | undefined + + let accountsChanged: Connector["onAccountsChanged"] | undefined + let chainChanged: Connector["onChainChanged"] | undefined + let connect: Connector["onConnect"] | undefined + let disconnect: (({reason}: {reason: string}) => void) | undefined + + // Parse and validate service parameters + const id = params.service?.uid || "fcl" + const name = params.service?.provider?.name || "Cadence Wallet" + + // TODO: we need to surface this through FCL service configuration + const rdns = (params.service?.provider as any)?.rdns + + return createConnector(config => ({ + id: id, + name: name, + type: "fcl-wagmi-adapter", + rdns: rdns, + async setup() { + const provider = await this.getProvider() + + if (connect) provider.removeListener("connect", connect) + connect = this.onConnect.bind(this) + provider.on("connect", connect) + + // We shouldn't need to listen for `'accountsChanged'` here since the `'connect'` event should suffice (and wallet shouldn't be connected yet). + // Some wallets, like MetaMask, do not implement the `'connect'` event and overload `'accountsChanged'` instead. + if (!accountsChanged) { + accountsChanged = this.onAccountsChanged.bind(this) + provider.on("accountsChanged", accountsChanged) + } + }, + async connect({isReconnecting}: any = {}) { + const provider = await this.getProvider() + + let accounts: readonly Address[] + if (isReconnecting) { + accounts = await this.getAccounts() + } else { + accounts = ( + (await provider.request({ + method: "eth_requestAccounts", + })) as string[] + ).map(x => getAddress(x)) + } + + // Manage EIP-1193 event listeners + // https://eips.ethereum.org/EIPS/eip-1193#events + if (connect) provider.removeListener("connect", connect) + connect = this.onConnect.bind(this) + provider.on("connect", connect) + + if (accountsChanged) + provider.removeListener("accountsChanged", accountsChanged) + accountsChanged = this.onAccountsChanged.bind(this) + provider.on("accountsChanged", accountsChanged) + + if (chainChanged) provider.removeListener("chainChanged", chainChanged) + chainChanged = this.onChainChanged.bind(this) + provider.on("chainChanged", chainChanged) + + if (disconnect) provider.removeListener("disconnect", disconnect) + disconnect = (({reason}) => { + throw new ProviderDisconnectedError(new Error(reason)) + }) as ({reason}: {reason: string}) => void + provider.on("disconnect", disconnect) + + return {accounts, chainId: await this.getChainId()} + }, + async disconnect() { + const provider = await this.getProvider() + + // Manage EIP-1193 event listeners + if (chainChanged) provider.removeListener("chainChanged", chainChanged) + chainChanged = undefined + + if (disconnect) provider.removeListener("disconnect", disconnect) + disconnect = undefined + + if (connect) provider.removeListener("connect", connect) + connect = this.onConnect.bind(this) + provider.on("connect", connect) + + await provider.disconnect() + }, + async getAccounts() { + const provider = await this.getProvider() + const accounts = (await provider.request({ + method: "eth_accounts", + })) as string[] + return accounts.map(x => getAddress(x)) + }, + async getChainId() { + const provider = await this.getProvider() + const chainId = await provider.request({method: "eth_chainId"}) + console.log("CHAIN ID", chainId) + return Number(chainId) + }, + async getProvider() { + return provider ?? (provider = createProvider(params)) + }, + async isAuthorized() { + // TODO: There may be an issue here if a user without a COA refreshes the page + // We should instead be checking whether FCL itself is authorized + const accounts = await this.getAccounts() + return accounts.length > 0 + }, + async switchChain({addEthereumChainParameter, chainId}: any) { + console.log("HEY") + const provider = await this.getProvider() + + const chain = config.chains.find(x => x.id === chainId) + if (!chain) throw new SwitchChainError(new ChainNotConfiguredError()) + + try { + await provider.request({ + method: "wallet_switchEthereumChain", + params: [{chainId: numberToHex(chainId)}], + }) + + return chain + } catch (err) { + // TODO: Error handling + throw new SwitchChainError(err as Error) + } + }, + onAccountsChanged(accounts) { + if (accounts.length === 0) this.onDisconnect() + else + config.emitter.emit("change", { + accounts: accounts.map((x: any) => getAddress(x)), + }) + }, + onChainChanged(chain) { + const chainId = Number(chain) + config.emitter.emit("change", {chainId}) + }, + async onConnect(connectInfo) { + const accounts = await this.getAccounts() + + // TODO: What to do if accounts is empty? not sure this is accurate + if (accounts.length === 0) return + + const chainId = Number(connectInfo.chainId) + config.emitter.emit("connect", {accounts, chainId}) + + const provider = await this.getProvider() + + if (connect) provider.removeListener("connect", connect) + connect = undefined + + if (accountsChanged) + provider.removeListener("accountsChanged", accountsChanged) + accountsChanged = this.onAccountsChanged.bind(this) + provider.on("accountsChanged", accountsChanged) + + if (chainChanged) provider.removeListener("chainChanged", chainChanged) + chainChanged = this.onChainChanged.bind(this) + provider.on("chainChanged", chainChanged) + + if (disconnect) provider.removeListener("disconnect", disconnect) + disconnect = (({reason}) => { + throw new ProviderDisconnectedError(new Error(reason)) + }) as ({reason}: {reason: string}) => void + provider.on("disconnect", disconnect) + }, + // TODO: waht to do with error? + async onDisconnect(error) { + const provider = await this.getProvider() + + config.emitter.emit("disconnect") + + // Manage EIP-1193 event listeners + if (chainChanged) { + provider.removeListener("chainChanged", chainChanged) + chainChanged = undefined + } + if (disconnect) { + provider.removeListener("disconnect", disconnect) + disconnect = undefined + } + if (!connect) { + connect = this.onConnect.bind(this) + provider.on("connect", connect) + } + }, + onDisplayUri(uri: string) { + config.emitter.emit("message", {type: "display_uri", data: uri}) + }, + })) +} diff --git a/packages/fcl-wagmi-adapter/tsconfig.json b/packages/fcl-wagmi-adapter/tsconfig.json new file mode 100644 index 000000000..617d9a8ae --- /dev/null +++ b/packages/fcl-wagmi-adapter/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig", + // Change this to match your project + "include": ["src/**/*"], + "compilerOptions": { + "declarationDir": "types", + "rootDir": "src" + } +} From b9e9510b0f114d391e4592d71c9ccb803cc620b1 Mon Sep 17 00:00:00 2001 From: Chase Fleming Date: Tue, 11 Feb 2025 16:02:08 -0800 Subject: [PATCH 27/56] Precalulate tx hash (#2130) * wip * Add nonce script * Add get Nonce * Remove * Change * Run prettier * Switch to bigint * Remove and add test * Remove * Update tests * Run prettier * Fix test * Move tests * Run prettier * Update packages/fcl-ethereum-provider/src/accounts/sign-message.test.ts Co-authored-by: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> --------- Co-authored-by: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Co-authored-by: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> --- .../src/accounts/account-manager.test.ts | 171 +++++------------- .../src/accounts/account-manager.ts | 87 +++++++-- .../src/accounts/sign-message.test.ts | 98 ++++++++++ packages/fcl-ethereum-provider/src/cadence.ts | 12 ++ 4 files changed, 229 insertions(+), 139 deletions(-) create mode 100644 packages/fcl-ethereum-provider/src/accounts/sign-message.test.ts diff --git a/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts b/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts index ed0633bc8..fc1bd14bc 100644 --- a/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts +++ b/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts @@ -1,7 +1,6 @@ import {AccountManager} from "./account-manager" import {mockUser} from "../__mocks__/fcl" import * as fcl from "@onflow/fcl" -import * as rlp from "@onflow/rlp" import {CurrentUser, FvmErrorCode} from "@onflow/typedefs" import {NetworkManager} from "../network/network-manager" import {BehaviorSubject} from "../util/observable" @@ -22,13 +21,7 @@ jest.mock("@onflow/fcl", () => { } }) -jest.mock("@onflow/rlp", () => ({ - encode: jest.fn(), - Buffer: jest.requireActual("@onflow/rlp").Buffer, -})) - jest.mock("../notifications", () => ({ - // ...jest.requireActual("../notifications"), displayErrorNotification: jest.fn(), })) @@ -280,7 +273,7 @@ describe("AccountManager", () => { }) }) -describe("send transaction", () => { +describe("sendTransaction", () => { let networkManager: jest.Mocked let $mockChainId: BehaviorSubject @@ -294,6 +287,47 @@ describe("send transaction", () => { jest.resetAllMocks() }) + test("sendTransaction returns a pre-calculated hash (mainnet)", async () => { + const mockTxResult = { + onceExecuted: jest.fn().mockResolvedValue({ + events: [ + { + type: "A.e467b9dd11fa00df.EVM.TransactionExecuted", + data: {hash: ["12", "34"]}, + }, + ], + }), + } as any as jest.Mocked> + + jest.mocked(fcl.tx).mockReturnValue(mockTxResult) + jest.mocked(fcl.mutate).mockResolvedValue("1111") + jest + .mocked(fcl.query) + .mockResolvedValueOnce("0x1234") + .mockResolvedValueOnce("0x0") + + const user = mockUser({addr: "0x1234"} as CurrentUser).mock + const accountManager = new AccountManager(user, networkManager) + + // Numbers maxed out to test edge cases + const txInput = { + to: "0xffffffffffffffffffffffffffffffffffffffff", + from: "0x1234", + value: + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + data: "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + gas: "0xffffffffffffffff", + chainId: "747", + } + + const result = await accountManager.sendTransaction(txInput) + + expect(result).toEqual( + "0xc4a532f9ed47b2092206a768b3ad3d32dfd80ed1f3b10690b81fdedc24685de7" + ) + expect(fcl.mutate).toHaveBeenCalled() + }) + test("send transaction mainnet", async () => { const mockTxResult = { onceExecuted: jest.fn().mockResolvedValue({ @@ -309,7 +343,6 @@ describe("send transaction", () => { } as any as jest.Mocked> jest.mocked(fcl.tx).mockReturnValue(mockTxResult) - jest.mocked(fcl.mutate).mockResolvedValue("1111") jest.mocked(fcl.query).mockResolvedValue("0x1234") const user = mockUser({addr: "0x4444"} as CurrentUser).mock @@ -327,7 +360,9 @@ describe("send transaction", () => { const result = await accountManager.sendTransaction(tx) - expect(result).toEqual("1852") + expect(result).toEqual( + "0xb7f94fa964193ab940ed6e24bdc72b4a59eb4e69546d8f423b8e52835dbf1d18" + ) expect(fcl.mutate).toHaveBeenCalled() expect(mockFcl.mutate.mock.calls[0][0]).toMatchObject({ cadence: expect.any(String), @@ -335,10 +370,6 @@ describe("send transaction", () => { authz: user, limit: 9999, }) - - expect(mockFcl.tx).toHaveBeenCalledWith("1111") - expect(mockFcl.tx).toHaveBeenCalledTimes(1) - expect(mockTxResult.onceExecuted).toHaveBeenCalledTimes(1) }) test("send transaction testnet", async () => { @@ -377,7 +408,7 @@ describe("send transaction", () => { const result = await accountManager.sendTransaction(tx) - expect(result).toEqual("1852") + expect(result).not.toBeNull() expect(fcl.mutate).toHaveBeenCalled() expect(mockFcl.mutate.mock.calls[0][0]).toMatchObject({ cadence: expect.any(String), @@ -385,39 +416,6 @@ describe("send transaction", () => { authz: user.mock, limit: 9999, }) - - expect(mockFcl.tx).toHaveBeenCalledWith("1111") - expect(mockFcl.tx).toHaveBeenCalledTimes(1) - expect(mockTxResult.onceExecuted).toHaveBeenCalledTimes(1) - }) - - test("throws error if no executed event not found", async () => { - const mockTxResult = { - onceExecuted: jest.fn().mockResolvedValue({ - events: [], - }), - } as any as jest.Mocked> - - jest.mocked(fcl.tx).mockReturnValue(mockTxResult) - jest.mocked(fcl.mutate).mockResolvedValue("1111") - jest.mocked(fcl.query).mockResolvedValue("0x1234") - - const user = mockUser({addr: "0x4444"} as CurrentUser) - const accountManager = new AccountManager(user.mock, networkManager) - - const tx = { - to: "0x1234", - from: "0x1234", - value: "0", - data: "0x1234", - nonce: "0", - gas: "0", - chainId: "747", - } - - await expect(accountManager.sendTransaction(tx)).rejects.toThrow( - "EVM transaction hash not found" - ) }) test("throws error if from address does not match user address", async () => { @@ -442,80 +440,3 @@ describe("send transaction", () => { expect(fcl.mutate).not.toHaveBeenCalled() }) }) - -describe("signMessage", () => { - let networkManager: jest.Mocked - let accountManager: AccountManager - let user: ReturnType["mock"] - let updateUser: ReturnType["set"] - - beforeEach(() => { - jest.resetAllMocks() - ;({mock: user, set: updateUser} = mockUser({addr: "0x123"} as CurrentUser)) - jest.mocked(fcl.query).mockResolvedValue("0xCOA1") - const $mockChainId = new BehaviorSubject(747) - networkManager = { - $chainId: $mockChainId, - getChainId: () => $mockChainId.getValue(), - } as any as jest.Mocked - accountManager = new AccountManager(user, networkManager) - }) - - it("should throw an error if the COA address is not available", async () => { - await updateUser({addr: undefined} as CurrentUser) - accountManager["coaAddress"] = null - - await expect( - accountManager.signMessage("Test message", "0x1234") - ).rejects.toThrow( - "COA address is not available. User might not be authenticated." - ) - }) - - it("should throw an error if the signer address does not match the COA address", async () => { - await expect( - accountManager.signMessage("Test message", "0xDIFFERENT") - ).rejects.toThrow("Signer address does not match authenticated COA address") - }) - - it("should successfully sign a message and return an RLP-encoded proof", async () => { - const mockSignature = "0xabcdef1234567890" - const mockRlpEncoded = "f86a808683abcdef682f73746f726167652f65766d" - - user.signUserMessage.mockResolvedValue([ - {addr: "0xCOA1", keyId: 0, signature: mockSignature} as any, - ]) - - jest.mocked(rlp.encode).mockReturnValue(Buffer.from(mockRlpEncoded, "hex")) - - const proof = await accountManager.signMessage("Test message", "0xCOA1") - - expect(proof).toBe(`0x${mockRlpEncoded}`) - - expect(user.signUserMessage).toHaveBeenCalledWith("Test message") - - expect(rlp.encode).toHaveBeenCalledWith([ - [0], - expect.any(Buffer), - "/public/evm", - [mockSignature], - ]) - }) - - it("should throw an error if signUserMessage returns an empty array", async () => { - user.signUserMessage.mockResolvedValue([]) - await expect( - accountManager.signMessage("Test message", "0xCOA1") - ).rejects.toThrow("Failed to sign message") - }) - - it("should throw an error if signUserMessage fails", async () => { - user.signUserMessage = jest - .fn() - .mockRejectedValue(new Error("Signing failed")) - - await expect( - accountManager.signMessage("Test message", "0xCOA1") - ).rejects.toThrow("Signing failed") - }) -}) diff --git a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts index 3b327ff66..5797e123e 100644 --- a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts +++ b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts @@ -28,9 +28,27 @@ import { } from "../util/observable" import {EthSignatureResponse} from "../types/eth" import {NetworkManager} from "../network/network-manager" -import {createCOATx, getCOAScript, sendTransactionTx} from "../cadence" +import { + createCOATx, + getCOAScript, + getNonceScript, + sendTransactionTx, +} from "../cadence" import {TransactionError} from "@onflow/fcl" import {displayErrorNotification} from "../notifications" +import {keccak_256} from "@noble/hashes/sha3" +import {bytesToHex, hexToBytes} from "@noble/hashes/utils" + +// Helper function to convert a number or bigint to a Uint8Array (minimal byte representation) +function numberToUint8Array(value: number | bigint): Uint8Array { + const big = typeof value === "bigint" ? value : BigInt(value) + if (big === BigInt(0)) return new Uint8Array([]) + let hex = big.toString(16) + if (hex.length % 2 !== 0) { + hex = "0" + hex + } + return hexToBytes(hex) +} export class AccountManager { private $addressStore = new BehaviorSubject<{ @@ -235,6 +253,24 @@ export class AccountManager { }) } + /** + * Fetch the current nonce from the EVM contract via a Cadence script. + */ + private async getNonce(evmAddress: string): Promise { + const chainId = await this.networkManager.getChainId() + + if (!chainId) { + throw new Error("No active chain") + } + + const nonce = await fcl.query({ + cadence: getNonceScript(chainId), + args: (arg, t) => [arg(evmAddress, t.String)], + }) + + return nonce.toString() + } + async sendTransaction({ to, from, @@ -276,7 +312,41 @@ export class AccountManager { ) } - const txId = await fcl.mutate({ + // ----- Pre-calculate the transaction hash ----- + const evmAddress = expectedCOAAddress!.toLowerCase().replace(/^0x/, "") + const nonceStr = await this.getNonce(evmAddress) + const nonce = parseInt(nonceStr, 10) + + const gasLimit = gas.startsWith("0x") ? BigInt(gas) : BigInt(gas) + + const valueHex = value.replace(/^0x/, "") + const txValue = BigInt("0x" + valueHex) + + const dataHex = data.replace(/^0x/, "") + + const gasPrice = BigInt(0) + const directCallTxType = BigInt(255) + const contractCallSubType = BigInt(5) + + // Build the transaction fields array, converting numbers/bigints using numberToUint8Array + const txArray = [ + numberToUint8Array(nonce), + numberToUint8Array(gasPrice), + numberToUint8Array(gasLimit), + hexToBytes(to.replace(/^0x/, "")), + numberToUint8Array(txValue), + hexToBytes(dataHex), + numberToUint8Array(directCallTxType), + numberToUint8Array(BigInt("0x" + evmAddress)), + numberToUint8Array(contractCallSubType), + ] + + const encodedTx = rlp.encode(txArray) + const digest = keccak_256(encodedTx) + const preCalculatedTxHash = "0x" + bytesToHex(digest) + // ----- End pre-calculation ----- + + await fcl.mutate({ cadence: sendTransactionTx(parsedChainId), limit: 9999, args: (arg: typeof fcl.arg, t: typeof fcl.t) => [ @@ -288,18 +358,7 @@ export class AccountManager { authz: this.user, }) - const event = await this.waitForTxResult( - txId, - EVENT_IDENTIFIERS[EventType.TRANSACTION_EXECUTED][flowNetwork], - "EVM transaction hash not found" - ) - - const eventData: TransactionExecutedEvent = event.data - const evmTxHash = eventData.hash - .map(h => parseInt(h, 16).toString().padStart(2, "0")) - .join("") - - return evmTxHash + return preCalculatedTxHash } public async signMessage( diff --git a/packages/fcl-ethereum-provider/src/accounts/sign-message.test.ts b/packages/fcl-ethereum-provider/src/accounts/sign-message.test.ts new file mode 100644 index 000000000..c49d591a5 --- /dev/null +++ b/packages/fcl-ethereum-provider/src/accounts/sign-message.test.ts @@ -0,0 +1,98 @@ +import {NetworkManager} from "../network/network-manager" +import {AccountManager} from "./account-manager" +import {mockUser} from "../__mocks__/fcl" +import {CurrentUser} from "@onflow/typedefs" +import * as fcl from "@onflow/fcl" +import {BehaviorSubject} from "../util/observable" +import * as rlp from "@onflow/rlp" + +jest.mock("@onflow/fcl", () => { + const fcl = jest.requireActual("@onflow/fcl") + return { + withPrefix: fcl.withPrefix, + sansPrefix: fcl.sansPrefix, + mutate: jest.fn(), + query: jest.fn(), + } +}) + +jest.mock("@onflow/rlp", () => ({ + encode: jest.fn(), + Buffer: jest.requireActual("@onflow/rlp").Buffer, +})) + +describe("signMessage", () => { + let networkManager: jest.Mocked + let accountManager: AccountManager + let user: ReturnType["mock"] + let updateUser: ReturnType["set"] + + beforeEach(() => { + jest.resetAllMocks() + ;({mock: user, set: updateUser} = mockUser({addr: "0x123"} as CurrentUser)) + jest.mocked(fcl.query).mockResolvedValue("0xCOA1") + const $mockChainId = new BehaviorSubject(747) + networkManager = { + $chainId: $mockChainId, + getChainId: () => $mockChainId.getValue(), + } as any as jest.Mocked + accountManager = new AccountManager(user, networkManager) + }) + + it("should throw an error if the COA address is not available", async () => { + await updateUser({addr: undefined} as CurrentUser) + + await expect( + accountManager.signMessage("Test message", "0x1234") + ).rejects.toThrow( + "COA address is not available. User might not be authenticated." + ) + }) + + it("should throw an error if the signer address does not match the COA address", async () => { + await expect( + accountManager.signMessage("Test message", "0xDIFFERENT") + ).rejects.toThrow("Signer address does not match authenticated COA address") + }) + + it("should successfully sign a message and return an RLP-encoded proof", async () => { + const mockSignature = "0xabcdef1234567890" + const mockRlpEncoded = "f86a808683abcdef682f73746f726167652f65766d" + + user.signUserMessage.mockResolvedValue([ + {addr: "0xCOA1", keyId: 0, signature: mockSignature} as any, + ]) + + jest.mocked(rlp.encode).mockReturnValue(Buffer.from(mockRlpEncoded, "hex")) + + const proof = await accountManager.signMessage("Test message", "0xCOA1") + + expect(proof).toBe(`0x${mockRlpEncoded}`) + + expect(user.signUserMessage).toHaveBeenCalledWith("Test message") + + expect(rlp.encode).toHaveBeenCalledWith([ + [0], + expect.any(Buffer), + "/public/evm", + [mockSignature], + ]) + }) + + it("should throw an error if signUserMessage returns an empty array", async () => { + user.signUserMessage.mockResolvedValue([]) + await expect( + accountManager.signMessage("Test message", "0xCOA1") + ).rejects.toThrow("Failed to sign message") + }) + + it("should throw an error if signUserMessage fails", async () => { + user.signUserMessage = jest + .fn() + .mockRejectedValue(new Error("Signing failed")) + + await expect( + accountManager.signMessage("Test message", "0xCOA1") + ).rejects.toThrow("Signing failed") + }) +}) diff --git a/packages/fcl-ethereum-provider/src/cadence.ts b/packages/fcl-ethereum-provider/src/cadence.ts index 22ad17f99..f55c229ed 100644 --- a/packages/fcl-ethereum-provider/src/cadence.ts +++ b/packages/fcl-ethereum-provider/src/cadence.ts @@ -60,3 +60,15 @@ transaction(evmContractAddressHex: String, calldata: String, gasLimit: UInt64, v assert(callResult.status == EVM.Status.successful, message: "Call failed") } }` + +export const getNonceScript = ( + chainId: number +) => `import EVM from ${getContractAddress(ContractType.EVM, chainId)} + +access(all) +fun main(evmAddress: String): UInt64 { + let addr = EVM.EVMAddress(bytes: evmAddress.decodeHex().toConstantSized<[UInt8; 20]>()) + let nonce = addr.nonce() + return nonce +} +` From 070f216f7671341344fbbda20a9d0604a8169dbc Mon Sep 17 00:00:00 2001 From: Chase Fleming Date: Wed, 12 Feb 2025 13:51:40 -0800 Subject: [PATCH 28/56] Clean up prefix handling (#2132) * Clean prefix handling * Run prettier --------- Co-authored-by: Chase Fleming <1666730+chasefleming@users.noreply.github.com> --- .../src/accounts/account-manager.ts | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts index 5797e123e..83f83da8f 100644 --- a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts +++ b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts @@ -306,23 +306,26 @@ export class AccountManager { // Check if the from address matches the authenticated COA address const expectedCOAAddress = await this.getCOAAddress() - if (from.toLowerCase() !== expectedCOAAddress?.toLowerCase()) { + if ( + fcl.sansPrefix(from).toLowerCase() !== + fcl.sansPrefix(expectedCOAAddress)?.toLowerCase() + ) { throw new Error( `From address does not match authenticated user address.\nUser: ${expectedCOAAddress}\nFrom: ${from}` ) } // ----- Pre-calculate the transaction hash ----- - const evmAddress = expectedCOAAddress!.toLowerCase().replace(/^0x/, "") + const evmAddress = fcl.sansPrefix(expectedCOAAddress!).toLowerCase() const nonceStr = await this.getNonce(evmAddress) const nonce = parseInt(nonceStr, 10) - const gasLimit = gas.startsWith("0x") ? BigInt(gas) : BigInt(gas) + const gasLimit = BigInt(gas) - const valueHex = value.replace(/^0x/, "") + const valueHex = fcl.sansPrefix(value) const txValue = BigInt("0x" + valueHex) - const dataHex = data.replace(/^0x/, "") + const dataHex = fcl.sansPrefix(data) const gasPrice = BigInt(0) const directCallTxType = BigInt(255) @@ -333,17 +336,17 @@ export class AccountManager { numberToUint8Array(nonce), numberToUint8Array(gasPrice), numberToUint8Array(gasLimit), - hexToBytes(to.replace(/^0x/, "")), + hexToBytes(fcl.sansPrefix(to)), numberToUint8Array(txValue), hexToBytes(dataHex), numberToUint8Array(directCallTxType), - numberToUint8Array(BigInt("0x" + evmAddress)), + numberToUint8Array(BigInt(fcl.withPrefix(evmAddress))), numberToUint8Array(contractCallSubType), ] const encodedTx = rlp.encode(txArray) const digest = keccak_256(encodedTx) - const preCalculatedTxHash = "0x" + bytesToHex(digest) + const preCalculatedTxHash = fcl.withPrefix(bytesToHex(digest)) // ----- End pre-calculation ----- await fcl.mutate({ @@ -372,7 +375,10 @@ export class AccountManager { ) } - if (from.toLowerCase() !== coaAddress.toLowerCase()) { + if ( + fcl.sansPrefix(from).toLowerCase() !== + fcl.sansPrefix(coaAddress).toLowerCase() + ) { throw new Error("Signer address does not match authenticated COA address") } @@ -387,7 +393,7 @@ export class AccountManager { const keyIndices = response.map(sig => sig.keyId) const signatures = response.map(sig => sig.signature) - const addressHexArray = Buffer.from(from.replace(/^0x/, ""), "hex") + const addressHexArray = Buffer.from(fcl.sansPrefix(from), "hex") const capabilityPath = "/public/evm" @@ -395,9 +401,7 @@ export class AccountManager { .encode([keyIndices, addressHexArray, capabilityPath, signatures]) .toString("hex") - return rlpEncodedProof.startsWith("0x") - ? rlpEncodedProof - : `0x${rlpEncodedProof}` // Return 0x-prefix for Ethereum compatibility + return fcl.withPrefix(rlpEncodedProof) } catch (error) { throw error } From 4057abd1559c2e42887b9c04902cb9beb20afa73 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> Date: Wed, 12 Feb 2025 15:13:04 -0800 Subject: [PATCH 29/56] Fix eth_sendTransaction (#2134) --- .../src/accounts/account-manager.ts | 18 ++++++------ packages/fcl-ethereum-provider/src/cadence.ts | 2 +- .../fcl-ethereum-provider/src/constants.ts | 4 +++ .../rpc/handlers/eth-send-transaction.test.ts | 28 ++++++++++++++----- .../src/rpc/handlers/eth-send-transaction.ts | 8 +++++- .../src/rpc/rpc-processor.ts | 6 +++- 6 files changed, 47 insertions(+), 19 deletions(-) diff --git a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts index 83f83da8f..39a1c73f1 100644 --- a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts +++ b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts @@ -7,6 +7,7 @@ import { FvmErrorCode, } from "@onflow/typedefs" import { + DEFAULT_EVM_GAS_LIMIT, EVENT_IDENTIFIERS, EventType, FLOW_CHAINS, @@ -113,9 +114,7 @@ export class AccountManager { public async authenticate(): Promise { await this.user.authenticate({service: this.service}) - return this.getAccounts().then(accounts => { - return accounts - }) + return this.getAccounts() } public async unauthenticate(): Promise { @@ -307,8 +306,9 @@ export class AccountManager { // Check if the from address matches the authenticated COA address const expectedCOAAddress = await this.getCOAAddress() if ( - fcl.sansPrefix(from).toLowerCase() !== - fcl.sansPrefix(expectedCOAAddress)?.toLowerCase() + fcl.sansPrefix(from.toLowerCase()) !== + fcl.sansPrefix(expectedCOAAddress?.toLowerCase() || null) && + !!expectedCOAAddress ) { throw new Error( `From address does not match authenticated user address.\nUser: ${expectedCOAAddress}\nFrom: ${from}` @@ -353,10 +353,10 @@ export class AccountManager { cadence: sendTransactionTx(parsedChainId), limit: 9999, args: (arg: typeof fcl.arg, t: typeof fcl.t) => [ - arg(to, t.String), - arg(data, t.String), - arg(gas, t.UInt64), - arg(value, t.UInt256), + arg(fcl.sansPrefix(to), t.String), + arg(fcl.sansPrefix(data ?? ""), t.String), + arg(BigInt(gas ?? DEFAULT_EVM_GAS_LIMIT).toString(), t.UInt64), + arg(BigInt(value ?? 0).toString(), t.UInt), ], authz: this.user, }) diff --git a/packages/fcl-ethereum-provider/src/cadence.ts b/packages/fcl-ethereum-provider/src/cadence.ts index f55c229ed..8120547af 100644 --- a/packages/fcl-ethereum-provider/src/cadence.ts +++ b/packages/fcl-ethereum-provider/src/cadence.ts @@ -37,7 +37,7 @@ export const sendTransactionTx = ( ) => `import EVM from ${getContractAddress(ContractType.EVM, chainId)} /// Executes the calldata from the signer's COA -transaction(evmContractAddressHex: String, calldata: String, gasLimit: UInt64, value: UInt256) { +transaction(evmContractAddressHex: String, calldata: String, gasLimit: UInt64, value: UInt) { let evmAddress: EVM.EVMAddress let coa: auth(EVM.Call) &EVM.CadenceOwnedAccount diff --git a/packages/fcl-ethereum-provider/src/constants.ts b/packages/fcl-ethereum-provider/src/constants.ts index a0b530b1a..fa16c8215 100644 --- a/packages/fcl-ethereum-provider/src/constants.ts +++ b/packages/fcl-ethereum-provider/src/constants.ts @@ -58,3 +58,7 @@ export interface TransactionExecutedEvent { } export const ACCESS_NODE_API_KEY = "accessNode.api" + +// TODO: This is a fixed value matching what is used by Flow Wallet right now. +// We should investigate whether eth_estimateGas can be used (& should be used) to get a more accurate value. +export const DEFAULT_EVM_GAS_LIMIT = BigInt("0x76c0") diff --git a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-send-transaction.test.ts b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-send-transaction.test.ts index 14ad5ae28..6c2d4bcc1 100644 --- a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-send-transaction.test.ts +++ b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-send-transaction.test.ts @@ -1,24 +1,38 @@ import {AccountManager} from "../../accounts/account-manager" +import {NetworkManager} from "../../network/network-manager" import {ethSendTransaction} from "./eth-send-transaction" jest.mock("../../accounts/account-manager") +jest.mock("../../network/network-manager") describe("eth_sendTransaction handler", () => { test("should call the AccountManager to send a transaction", async () => { const mockAccountManager = new (AccountManager as any)() mockAccountManager.sendTransaction.mockResolvedValue("0x123456") - const params = { - from: "0x1234", - to: "0x5678", - value: "0x100", - } + const mockNetworkManager = new (NetworkManager as any)() + mockNetworkManager.getChainId.mockResolvedValue(1) - const evmTxHash = await ethSendTransaction(mockAccountManager, params) + const params = [ + { + from: "0x1234", + to: "0x5678", + value: "0x100", + }, + ] + + const evmTxHash = await ethSendTransaction( + mockAccountManager, + mockNetworkManager, + params + ) expect(mockAccountManager.sendTransaction).toHaveBeenCalled() expect(mockAccountManager.sendTransaction).toHaveBeenCalledTimes(1) - expect(mockAccountManager.sendTransaction).toHaveBeenCalledWith(params) + expect(mockAccountManager.sendTransaction).toHaveBeenCalledWith({ + ...params[0], + chainId: 1, + }) expect(evmTxHash).toEqual("0x123456") }) }) diff --git a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-send-transaction.ts b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-send-transaction.ts index b8c015eb4..24528c258 100644 --- a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-send-transaction.ts +++ b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-send-transaction.ts @@ -1,8 +1,14 @@ import {AccountManager} from "../../accounts/account-manager" +import {NetworkManager} from "../../network/network-manager" export async function ethSendTransaction( accountManager: AccountManager, + networkManager: NetworkManager, params: any ) { - return await accountManager.sendTransaction(params) + return await accountManager.sendTransaction({ + ...params[0], + // We pass the chainId to avoid race conditions where the chainId changes + chainId: await networkManager.getChainId(), + }) } diff --git a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts index c153c8c43..4cdfac456 100644 --- a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts +++ b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts @@ -36,7 +36,11 @@ export class RpcProcessor { case "eth_requestAccounts": return ethRequestAccounts(this.accountManager, chainId) case "eth_sendTransaction": - return await ethSendTransaction(this.accountManager, params) + return await ethSendTransaction( + this.accountManager, + this.networkManager, + params + ) case "eth_signTypedData": case "eth_signTypedData_v3": case "eth_signTypedData_v4": { From 184cee5dd50a942258b5f599edaa8abc66fd0e3d Mon Sep 17 00:00:00 2001 From: Chase Fleming Date: Wed, 12 Feb 2025 15:32:31 -0800 Subject: [PATCH 30/56] Misc cleanup and refactoring (#2136) * Refactor evm import * Add type * Refactor * Use type * Cleanup * Remove * Run prettier * Remove space * Run prettier --------- Co-authored-by: Chase Fleming <1666730+chasefleming@users.noreply.github.com> --- .../src/accounts/account-manager.ts | 31 +++++-------------- packages/fcl-ethereum-provider/src/cadence.ts | 24 +++++--------- .../src/types/account.ts | 5 +++ .../fcl-ethereum-provider/src/util/chain.ts | 7 +++++ 4 files changed, 27 insertions(+), 40 deletions(-) create mode 100644 packages/fcl-ethereum-provider/src/types/account.ts create mode 100644 packages/fcl-ethereum-provider/src/util/chain.ts diff --git a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts index 39a1c73f1..46951ca1a 100644 --- a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts +++ b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts @@ -6,14 +6,7 @@ import { Service, FvmErrorCode, } from "@onflow/typedefs" -import { - DEFAULT_EVM_GAS_LIMIT, - EVENT_IDENTIFIERS, - EventType, - FLOW_CHAINS, - FlowNetwork, -} from "../constants" -import {TransactionExecutedEvent} from "../types/events" +import {DEFAULT_EVM_GAS_LIMIT, EVENT_IDENTIFIERS, EventType} from "../constants" import { BehaviorSubject, concat, @@ -39,6 +32,8 @@ import {TransactionError} from "@onflow/fcl" import {displayErrorNotification} from "../notifications" import {keccak_256} from "@noble/hashes/sha3" import {bytesToHex, hexToBytes} from "@noble/hashes/utils" +import {AddressStoreState} from "../types/account" +import {getFlowNetwork} from "../util/chain" // Helper function to convert a number or bigint to a Uint8Array (minimal byte representation) function numberToUint8Array(value: number | bigint): Uint8Array { @@ -52,11 +47,7 @@ function numberToUint8Array(value: number | bigint): Uint8Array { } export class AccountManager { - private $addressStore = new BehaviorSubject<{ - isLoading: boolean - address: string | null - error: Error | null - }>({ + private $addressStore = new BehaviorSubject({ isLoading: true, address: null, error: null, @@ -85,11 +76,7 @@ export class AccountManager { distinctUntilChanged(), switchMap(addr => concat( - of({isLoading: true} as { - isLoading: boolean - address: string | null - error: Error | null - }), + of({isLoading: true} as AddressStoreState), from( (async () => { try { @@ -185,9 +172,7 @@ export class AccountManager { public async createCOA(chainId: number): Promise { // Find the Flow network based on the chain ID - const flowNetwork = Object.entries(FLOW_CHAINS).find( - ([, chain]) => chain.eip155ChainId === chainId - )?.[0] as FlowNetwork | undefined + const flowNetwork = getFlowNetwork(chainId) if (!flowNetwork) { throw new Error("Flow network not found for chain ID") @@ -287,9 +272,7 @@ export class AccountManager { }) { // Find the Flow network based on the chain ID const parsedChainId = parseInt(chainId) - const flowNetwork = Object.entries(FLOW_CHAINS).find( - ([, chain]) => chain.eip155ChainId === parsedChainId - )?.[0] as FlowNetwork | undefined + const flowNetwork = getFlowNetwork(parsedChainId) if (!flowNetwork) { throw new Error("Flow network not found for chain ID") diff --git a/packages/fcl-ethereum-provider/src/cadence.ts b/packages/fcl-ethereum-provider/src/cadence.ts index 8120547af..c638e4d95 100644 --- a/packages/fcl-ethereum-provider/src/cadence.ts +++ b/packages/fcl-ethereum-provider/src/cadence.ts @@ -1,12 +1,12 @@ import {getContractAddress} from "./util/eth" import {ContractType} from "./constants" -export const getCOAScript = ( - chainId: number -) => `import EVM from ${getContractAddress(ContractType.EVM, chainId)} +const evmImport = (chainId: number) => + `import EVM from ${getContractAddress(ContractType.EVM, chainId)}` + +export const getCOAScript = (chainId: number) => `${evmImport(chainId)} /// Returns the hex encoded address of the COA in the given Flow address -/// access(all) fun main(flowAddress: Address): String? { return getAuthAccount(flowAddress) .storage.borrow<&EVM.CadenceOwnedAccount>(from: /storage/evm) @@ -15,9 +15,7 @@ access(all) fun main(flowAddress: Address): String? { ?? nil }` -export const createCOATx = ( - chainId: number -) => `import EVM from ${getContractAddress(ContractType.EVM, chainId)} +export const createCOATx = (chainId: number) => `${evmImport(chainId)} transaction() { prepare(signer: auth(SaveValue, IssueStorageCapabilityController, PublishCapability) &Account) { @@ -32,9 +30,7 @@ transaction() { } }` -export const sendTransactionTx = ( - chainId: number -) => `import EVM from ${getContractAddress(ContractType.EVM, chainId)} +export const sendTransactionTx = (chainId: number) => `${evmImport(chainId)} /// Executes the calldata from the signer's COA transaction(evmContractAddressHex: String, calldata: String, gasLimit: UInt64, value: UInt) { @@ -44,7 +40,6 @@ transaction(evmContractAddressHex: String, calldata: String, gasLimit: UInt64, v prepare(signer: auth(BorrowValue) &Account) { self.evmAddress = EVM.addressFromString(evmContractAddressHex) - self.coa = signer.storage.borrow(from: /storage/evm) ?? panic("Could not borrow COA from provided gateway address") } @@ -61,14 +56,11 @@ transaction(evmContractAddressHex: String, calldata: String, gasLimit: UInt64, v } }` -export const getNonceScript = ( - chainId: number -) => `import EVM from ${getContractAddress(ContractType.EVM, chainId)} +export const getNonceScript = (chainId: number) => `${evmImport(chainId)} access(all) fun main(evmAddress: String): UInt64 { let addr = EVM.EVMAddress(bytes: evmAddress.decodeHex().toConstantSized<[UInt8; 20]>()) let nonce = addr.nonce() return nonce -} -` +}` diff --git a/packages/fcl-ethereum-provider/src/types/account.ts b/packages/fcl-ethereum-provider/src/types/account.ts new file mode 100644 index 000000000..269027c3e --- /dev/null +++ b/packages/fcl-ethereum-provider/src/types/account.ts @@ -0,0 +1,5 @@ +export interface AddressStoreState { + isLoading: boolean + address: string | null + error: Error | null +} diff --git a/packages/fcl-ethereum-provider/src/util/chain.ts b/packages/fcl-ethereum-provider/src/util/chain.ts new file mode 100644 index 000000000..2cc32b53a --- /dev/null +++ b/packages/fcl-ethereum-provider/src/util/chain.ts @@ -0,0 +1,7 @@ +import {FLOW_CHAINS, FlowNetwork} from "../constants" + +export function getFlowNetwork(chainId: number): FlowNetwork | undefined { + return Object.entries(FLOW_CHAINS).find( + ([, chain]) => chain.eip155ChainId === chainId + )?.[0] as FlowNetwork | undefined +} From bf3b6197b2b39eebf5095be8efcc5a32a4e8c6c1 Mon Sep 17 00:00:00 2001 From: Chase Fleming Date: Wed, 12 Feb 2025 16:26:12 -0800 Subject: [PATCH 31/56] Refactoring: move to helpers (#2139) Co-authored-by: Chase Fleming <1666730+chasefleming@users.noreply.github.com> --- .../src/accounts/account-manager.ts | 71 +++++++++++-------- 1 file changed, 40 insertions(+), 31 deletions(-) diff --git a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts index 46951ca1a..03eb4e5fb 100644 --- a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts +++ b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts @@ -6,7 +6,12 @@ import { Service, FvmErrorCode, } from "@onflow/typedefs" -import {DEFAULT_EVM_GAS_LIMIT, EVENT_IDENTIFIERS, EventType} from "../constants" +import { + DEFAULT_EVM_GAS_LIMIT, + EVENT_IDENTIFIERS, + EventType, + FlowNetwork, +} from "../constants" import { BehaviorSubject, concat, @@ -58,7 +63,13 @@ export class AccountManager { private networkManager: NetworkManager, private service?: Service ) { - // Create an observable from the user + this.initializeUserSubscription() + } + + /** + * Subscribes to the current user observable and updates the address store. + */ + private initializeUserSubscription() { const $user = new Observable(subscriber => { return this.user.subscribe((currentUser: CurrentUser, error?: Error) => { if (error) { @@ -69,7 +80,6 @@ export class AccountManager { }) as Subscription }) - // Bind the address store to the user observable $user .pipe( map(snapshot => snapshot.addr || null), @@ -171,20 +181,8 @@ export class AccountManager { } public async createCOA(chainId: number): Promise { - // Find the Flow network based on the chain ID - const flowNetwork = getFlowNetwork(chainId) - - if (!flowNetwork) { - throw new Error("Flow network not found for chain ID") - } - - // Validate the chain ID - const currentChainId = await this.networkManager.getChainId() - if (chainId !== currentChainId) { - throw new Error( - `Chain ID does not match the current network. Expected: ${currentChainId}, Received: ${chainId}` - ) - } + const flowNetwork = this.getFlowNetworkOrThrow(chainId) + await this.validateChainId(chainId) const txId = await fcl.mutate({ cadence: createCOATx(chainId), @@ -270,21 +268,9 @@ export class AccountManager { gas: string chainId: string }) { - // Find the Flow network based on the chain ID const parsedChainId = parseInt(chainId) - const flowNetwork = getFlowNetwork(parsedChainId) - - if (!flowNetwork) { - throw new Error("Flow network not found for chain ID") - } - - // Validate the chain ID - const currentChainId = await this.networkManager.getChainId() - if (parsedChainId !== currentChainId) { - throw new Error( - `Chain ID does not match the current network. Expected: ${currentChainId}, Received: ${parsedChainId}` - ) - } + this.getFlowNetworkOrThrow(parsedChainId) + await this.validateChainId(parsedChainId) // Check if the from address matches the authenticated COA address const expectedCOAAddress = await this.getCOAAddress() @@ -389,4 +375,27 @@ export class AccountManager { throw error } } + + /** + * Validates that the provided chain ID matches the current network. + */ + private async validateChainId(chainId: number): Promise { + const currentChainId = await this.networkManager.getChainId() + if (chainId !== currentChainId) { + throw new Error( + `Chain ID does not match the current network. Expected: ${currentChainId}, Received: ${chainId}` + ) + } + } + + /** + * Gets the Flow network based on the chain ID or throws an error. + */ + private getFlowNetworkOrThrow(chainId: number): FlowNetwork { + const flowNetwork = getFlowNetwork(chainId) + if (!flowNetwork) { + throw new Error("Flow network not found for chain ID") + } + return flowNetwork + } } From 1468edaf517afe205b7884810c86f986b75f9577 Mon Sep 17 00:00:00 2001 From: Chase Fleming Date: Wed, 12 Feb 2025 16:39:47 -0800 Subject: [PATCH 32/56] Move tx hash method to util (#2140) Co-authored-by: Chase Fleming <1666730+chasefleming@users.noreply.github.com> --- .../src/accounts/account-manager.ts | 50 ++++------------- .../src/util/transaction.ts | 53 +++++++++++++++++++ 2 files changed, 62 insertions(+), 41 deletions(-) create mode 100644 packages/fcl-ethereum-provider/src/util/transaction.ts diff --git a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts index 03eb4e5fb..529200631 100644 --- a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts +++ b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts @@ -35,21 +35,9 @@ import { } from "../cadence" import {TransactionError} from "@onflow/fcl" import {displayErrorNotification} from "../notifications" -import {keccak_256} from "@noble/hashes/sha3" -import {bytesToHex, hexToBytes} from "@noble/hashes/utils" import {AddressStoreState} from "../types/account" import {getFlowNetwork} from "../util/chain" - -// Helper function to convert a number or bigint to a Uint8Array (minimal byte representation) -function numberToUint8Array(value: number | bigint): Uint8Array { - const big = typeof value === "bigint" ? value : BigInt(value) - if (big === BigInt(0)) return new Uint8Array([]) - let hex = big.toString(16) - if (hex.length % 2 !== 0) { - hex = "0" + hex - } - return hexToBytes(hex) -} +import {precalculateTxHash} from "../util/transaction" export class AccountManager { private $addressStore = new BehaviorSubject({ @@ -288,34 +276,14 @@ export class AccountManager { const evmAddress = fcl.sansPrefix(expectedCOAAddress!).toLowerCase() const nonceStr = await this.getNonce(evmAddress) const nonce = parseInt(nonceStr, 10) - - const gasLimit = BigInt(gas) - - const valueHex = fcl.sansPrefix(value) - const txValue = BigInt("0x" + valueHex) - - const dataHex = fcl.sansPrefix(data) - - const gasPrice = BigInt(0) - const directCallTxType = BigInt(255) - const contractCallSubType = BigInt(5) - - // Build the transaction fields array, converting numbers/bigints using numberToUint8Array - const txArray = [ - numberToUint8Array(nonce), - numberToUint8Array(gasPrice), - numberToUint8Array(gasLimit), - hexToBytes(fcl.sansPrefix(to)), - numberToUint8Array(txValue), - hexToBytes(dataHex), - numberToUint8Array(directCallTxType), - numberToUint8Array(BigInt(fcl.withPrefix(evmAddress))), - numberToUint8Array(contractCallSubType), - ] - - const encodedTx = rlp.encode(txArray) - const digest = keccak_256(encodedTx) - const preCalculatedTxHash = fcl.withPrefix(bytesToHex(digest)) + const preCalculatedTxHash = precalculateTxHash( + nonce, + gas, + value, + to, + data, + evmAddress + ) // ----- End pre-calculation ----- await fcl.mutate({ diff --git a/packages/fcl-ethereum-provider/src/util/transaction.ts b/packages/fcl-ethereum-provider/src/util/transaction.ts new file mode 100644 index 000000000..55f612c67 --- /dev/null +++ b/packages/fcl-ethereum-provider/src/util/transaction.ts @@ -0,0 +1,53 @@ +import * as fcl from "@onflow/fcl" +import * as rlp from "@onflow/rlp" +import {bytesToHex, hexToBytes} from "@noble/hashes/utils" +import {keccak_256} from "@noble/hashes/sha3" + +// Helper function to convert a number or bigint to a Uint8Array (minimal byte representation) +function numberToUint8Array(value: number | bigint): Uint8Array { + const big = typeof value === "bigint" ? value : BigInt(value) + if (big === BigInt(0)) return new Uint8Array([]) + let hex = big.toString(16) + if (hex.length % 2 !== 0) { + hex = "0" + hex + } + return hexToBytes(hex) +} + +/** + * Pre-calculates the transaction hash by building the transaction array, + * encoding it with RLP, and hashing it with keccak_256. + */ +export function precalculateTxHash( + nonce: number, + gas: string, + value: string, + to: string, + data: string, + evmAddress: string +): string { + const gasLimit = BigInt(gas) + const valueHex = fcl.sansPrefix(value) + const txValue = BigInt("0x" + valueHex) + const dataHex = fcl.sansPrefix(data) + + const gasPrice = BigInt(0) + const directCallTxType = BigInt(255) + const contractCallSubType = BigInt(5) + + const txArray = [ + numberToUint8Array(nonce), + numberToUint8Array(gasPrice), + numberToUint8Array(gasLimit), + hexToBytes(fcl.sansPrefix(to)), + numberToUint8Array(txValue), + hexToBytes(dataHex), + numberToUint8Array(directCallTxType), + numberToUint8Array(BigInt(fcl.withPrefix(evmAddress))), + numberToUint8Array(contractCallSubType), + ] + + const encodedTx = rlp.encode(txArray) + const digest = keccak_256(encodedTx) + return fcl.withPrefix(bytesToHex(digest)) +} From 572686ff2c40fba7def8b555c3dfc312df10f0ac Mon Sep 17 00:00:00 2001 From: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> Date: Thu, 13 Feb 2025 16:19:28 -0800 Subject: [PATCH 33/56] Fix `eth_sendTransaction` bugs (#2144) --- .../src/accounts/account-manager.ts | 30 ++++++++++--------- packages/fcl-ethereum-provider/src/cadence.ts | 2 +- .../fcl-ethereum-provider/src/constants.ts | 2 +- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts index 529200631..6d2c99f2e 100644 --- a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts +++ b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts @@ -241,21 +241,23 @@ export class AccountManager { return nonce.toString() } - async sendTransaction({ - to, - from, - value, - data, - gas, - chainId, - }: { + async sendTransaction(params: { to: string from: string - value: string - data: string - gas: string chainId: string + value?: string + gas?: string + data?: string }) { + const { + to, + from, + value = "0", + data = "", + gas = DEFAULT_EVM_GAS_LIMIT, + chainId, + } = params + const parsedChainId = parseInt(chainId) this.getFlowNetworkOrThrow(parsedChainId) await this.validateChainId(parsedChainId) @@ -291,9 +293,9 @@ export class AccountManager { limit: 9999, args: (arg: typeof fcl.arg, t: typeof fcl.t) => [ arg(fcl.sansPrefix(to), t.String), - arg(fcl.sansPrefix(data ?? ""), t.String), - arg(BigInt(gas ?? DEFAULT_EVM_GAS_LIMIT).toString(), t.UInt64), - arg(BigInt(value ?? 0).toString(), t.UInt), + arg(fcl.sansPrefix(data), t.String), + arg(BigInt(gas).toString(), t.UInt64), + arg(BigInt(value).toString(), t.UInt), ], authz: this.user, }) diff --git a/packages/fcl-ethereum-provider/src/cadence.ts b/packages/fcl-ethereum-provider/src/cadence.ts index c638e4d95..3cb62e15c 100644 --- a/packages/fcl-ethereum-provider/src/cadence.ts +++ b/packages/fcl-ethereum-provider/src/cadence.ts @@ -60,7 +60,7 @@ export const getNonceScript = (chainId: number) => `${evmImport(chainId)} access(all) fun main(evmAddress: String): UInt64 { - let addr = EVM.EVMAddress(bytes: evmAddress.decodeHex().toConstantSized<[UInt8; 20]>()) + let addr = EVM.EVMAddress(bytes: evmAddress.decodeHex().toConstantSized<[UInt8; 20]>()!) let nonce = addr.nonce() return nonce }` diff --git a/packages/fcl-ethereum-provider/src/constants.ts b/packages/fcl-ethereum-provider/src/constants.ts index fa16c8215..ad428f642 100644 --- a/packages/fcl-ethereum-provider/src/constants.ts +++ b/packages/fcl-ethereum-provider/src/constants.ts @@ -61,4 +61,4 @@ export const ACCESS_NODE_API_KEY = "accessNode.api" // TODO: This is a fixed value matching what is used by Flow Wallet right now. // We should investigate whether eth_estimateGas can be used (& should be used) to get a more accurate value. -export const DEFAULT_EVM_GAS_LIMIT = BigInt("0x76c0") +export const DEFAULT_EVM_GAS_LIMIT = "0x76c0" From a483f3392616cde095d9c6d076022491acb8f8e5 Mon Sep 17 00:00:00 2001 From: Chase Fleming Date: Thu, 13 Feb 2025 16:24:48 -0800 Subject: [PATCH 34/56] Add readme (#2143) Co-authored-by: Chase Fleming <1666730+chasefleming@users.noreply.github.com> --- packages/fcl-rainbowkit-adapter/README.md | 74 +++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/packages/fcl-rainbowkit-adapter/README.md b/packages/fcl-rainbowkit-adapter/README.md index ad868842a..4a323a738 100644 --- a/packages/fcl-rainbowkit-adapter/README.md +++ b/packages/fcl-rainbowkit-adapter/README.md @@ -1 +1,75 @@ # @onflow/fcl-rainbowkit-adapter + +## Usage + +```typescript +import { createFclConnector } from '@onflow/fcl-rainbowkit-adapter'; +import { connectorsForWallets } from '@rainbow-me/rainbowkit'; +import { + flowTestnet, +} from 'wagmi/chains'; +import * as fcl from '@onflow/fcl'; +import { createConfig, http } from 'wagmi'; + +// Set FCL config +fcl.config({ + "accessNode.api": "https://rest-testnet.onflow.org", + "discovery.wallet": "https://fcl-discovery.onflow.org/testnet/authn", + "walletconnect.projectId": "9b70cfa398b2355a5eb9b1cf99f4a981", +}) + +// FCL connector +const flowWalletConnector = createFclConnector({ + user: fcl.currentUser, + config: fcl.config, + service: { + "f_type": "Service", + "f_vsn": "1.0.0", + "type": "authn", + "uid": "Flow Wallet", + "endpoint": "chrome-extension://hpclkefagolihohboafpheddmmgdffjm/popup.html", + "method": "EXT/RPC", + "id": "hpclkefagolihohboafpheddmmgdffjm", + "identity": { + "address": "0x33f75ff0b830dcec" + }, + "provider": { + "name": "Flow Wallet", + "address": "0x33f75ff0b830dcec", + "description": "A wallet created for everyone", + "icon": "https://lilico.app/frw-logo.png", + "color": "#41CC5D", + "website": "https://core.flow.com", + "requires_install": true, + "is_installed": true, + "install_link": "https://chromewebstore.google.com/detail/flow-wallet/hpclkefagolihohboafpheddmmgdffjm", + "rdns": "com.flowfoundation.wallet" + } + } as any, +}) + +// RainbowKit connectors +const connectors = connectorsForWallets([ + { + groupName: "Recommended", + "wallets": [ + flowWalletConnector as any, + ], + } +], { + appName: 'RainbowKit demo', + projectId: '9b70cfa398b2355a5eb9b1cf99f4a981', +}); + +// Wagmi config +export const config = createConfig({ + chains: [ + flowTestnet + ], + connectors, + ssr: true, + transports: { + [flowTestnet.id]: http(), + } +}); +``` \ No newline at end of file From 5a9f98fa986bc2a45b07f6cab383e1b8b6301dbd Mon Sep 17 00:00:00 2001 From: Chase Fleming Date: Thu, 13 Feb 2025 16:48:32 -0800 Subject: [PATCH 35/56] Implement disconnected spec (#2141) * Implement disconnected spec * Fix disconnected * Fix error * Fix * Update packages/fcl-wagmi-adapter/src/index.ts Co-authored-by: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> * Update packages/fcl-wagmi-adapter/src/index.ts Co-authored-by: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> * Fix --------- Co-authored-by: Chase Fleming <1666730+chasefleming@users.noreply.github.com> Co-authored-by: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> --- .../src/events/event-dispatcher.ts | 16 +++++++++++++--- .../src/types/provider.ts | 4 +++- .../src/util/observable.ts | 18 ++++++++++++++++++ packages/fcl-wagmi-adapter/src/index.ts | 14 +++++++------- 4 files changed, 41 insertions(+), 11 deletions(-) diff --git a/packages/fcl-ethereum-provider/src/events/event-dispatcher.ts b/packages/fcl-ethereum-provider/src/events/event-dispatcher.ts index 559fe4d6a..81e94fc02 100644 --- a/packages/fcl-ethereum-provider/src/events/event-dispatcher.ts +++ b/packages/fcl-ethereum-provider/src/events/event-dispatcher.ts @@ -2,15 +2,18 @@ import {EventCallback, ProviderEvents} from "../types/provider" import {AccountManager} from "../accounts/account-manager" import {NetworkManager} from "../network/network-manager" import { + distinctUntilChanged, filter, map, Observable, + pairwise, skip, Subscription, takeFirst, } from "../util/observable" import {formatChainId} from "../util/eth" import {withPrefix} from "@onflow/fcl" +import {ProviderError, ProviderErrorCode} from "../util/errors" export class EventDispatcher { private $emitters: { @@ -47,9 +50,16 @@ export class EventDispatcher { }), takeFirst() ), - disconnect: new Observable<{reason: string}>(() => { - return () => {} - }), + disconnect: networkManager.$chainId.pipe( + filter(({isLoading, error}) => !isLoading && !error), + pairwise(), + filter( + ([prev, curr]) => prev.chainId !== null && curr.chainId === null + ), + map(() => { + return new ProviderError({code: ProviderErrorCode.Disconnected}) + }) + ), } this.subscriptions = { diff --git a/packages/fcl-ethereum-provider/src/types/provider.ts b/packages/fcl-ethereum-provider/src/types/provider.ts index f2ae42a93..c9fcfa21e 100644 --- a/packages/fcl-ethereum-provider/src/types/provider.ts +++ b/packages/fcl-ethereum-provider/src/types/provider.ts @@ -1,4 +1,6 @@ // Types for RPC request and events +import {ProviderError} from "../util/errors" + export type ProviderRequest = { method: string params?: unknown[] | Record @@ -9,7 +11,7 @@ export type ProviderResponse = T // Event types for the provider export type ProviderEvents = { connect: {chainId: string} - disconnect: {reason: string} + disconnect: ProviderError chainChanged: string accountsChanged: string[] } diff --git a/packages/fcl-ethereum-provider/src/util/observable.ts b/packages/fcl-ethereum-provider/src/util/observable.ts index ca975b040..5a15f6f96 100644 --- a/packages/fcl-ethereum-provider/src/util/observable.ts +++ b/packages/fcl-ethereum-provider/src/util/observable.ts @@ -344,6 +344,24 @@ export function takeFirst(): (source: Observable) => Observable { } } +export function pairwise(): (source: Observable) => Observable<[T, T]> { + return source => { + return new Observable<[T, T]>(subscriber => { + let previous: T | undefined + return source.subscribe({ + next: value => { + if (previous !== undefined) { + subscriber.next([previous, value]) + } + previous = value + }, + error: subscriber.error?.bind(subscriber), + complete: subscriber.complete?.bind(subscriber), + }) + }) + } +} + /******************************* * Internal utility *******************************/ diff --git a/packages/fcl-wagmi-adapter/src/index.ts b/packages/fcl-wagmi-adapter/src/index.ts index 615c512ad..5fffa43bb 100644 --- a/packages/fcl-wagmi-adapter/src/index.ts +++ b/packages/fcl-wagmi-adapter/src/index.ts @@ -26,7 +26,7 @@ export function fclWagmiAdapter(params: FclWagmiAdapterParams) { let accountsChanged: Connector["onAccountsChanged"] | undefined let chainChanged: Connector["onChainChanged"] | undefined let connect: Connector["onConnect"] | undefined - let disconnect: (({reason}: {reason: string}) => void) | undefined + let disconnect: ((error: Error) => void) | undefined // Parse and validate service parameters const id = params.service?.uid || "fcl" @@ -84,9 +84,9 @@ export function fclWagmiAdapter(params: FclWagmiAdapterParams) { provider.on("chainChanged", chainChanged) if (disconnect) provider.removeListener("disconnect", disconnect) - disconnect = (({reason}) => { - throw new ProviderDisconnectedError(new Error(reason)) - }) as ({reason}: {reason: string}) => void + disconnect = (error: Error) => { + throw new ProviderDisconnectedError(error) + } provider.on("disconnect", disconnect) return {accounts, chainId: await this.getChainId()} @@ -183,9 +183,9 @@ export function fclWagmiAdapter(params: FclWagmiAdapterParams) { provider.on("chainChanged", chainChanged) if (disconnect) provider.removeListener("disconnect", disconnect) - disconnect = (({reason}) => { - throw new ProviderDisconnectedError(new Error(reason)) - }) as ({reason}: {reason: string}) => void + disconnect = (error: Error) => { + throw new ProviderDisconnectedError(error) + } provider.on("disconnect", disconnect) }, // TODO: waht to do with error? From 8ffcc1b90555770724c94371d1b5b4415c6fb356 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> Date: Fri, 14 Feb 2025 16:25:14 -0800 Subject: [PATCH 36/56] Create Flow Wallet Connector (#2146) --- packages/fcl-rainbowkit-adapter/README.md | 32 +------------- .../src/create-connector.ts | 32 ++++++++++++++ .../fcl-rainbowkit-adapter/src/flow-wallet.ts | 43 +++++++++++++++++++ packages/fcl-rainbowkit-adapter/src/index.ts | 34 +-------------- 4 files changed, 78 insertions(+), 63 deletions(-) create mode 100644 packages/fcl-rainbowkit-adapter/src/create-connector.ts create mode 100644 packages/fcl-rainbowkit-adapter/src/flow-wallet.ts diff --git a/packages/fcl-rainbowkit-adapter/README.md b/packages/fcl-rainbowkit-adapter/README.md index 4a323a738..6503701f3 100644 --- a/packages/fcl-rainbowkit-adapter/README.md +++ b/packages/fcl-rainbowkit-adapter/README.md @@ -18,42 +18,12 @@ fcl.config({ "walletconnect.projectId": "9b70cfa398b2355a5eb9b1cf99f4a981", }) -// FCL connector -const flowWalletConnector = createFclConnector({ - user: fcl.currentUser, - config: fcl.config, - service: { - "f_type": "Service", - "f_vsn": "1.0.0", - "type": "authn", - "uid": "Flow Wallet", - "endpoint": "chrome-extension://hpclkefagolihohboafpheddmmgdffjm/popup.html", - "method": "EXT/RPC", - "id": "hpclkefagolihohboafpheddmmgdffjm", - "identity": { - "address": "0x33f75ff0b830dcec" - }, - "provider": { - "name": "Flow Wallet", - "address": "0x33f75ff0b830dcec", - "description": "A wallet created for everyone", - "icon": "https://lilico.app/frw-logo.png", - "color": "#41CC5D", - "website": "https://core.flow.com", - "requires_install": true, - "is_installed": true, - "install_link": "https://chromewebstore.google.com/detail/flow-wallet/hpclkefagolihohboafpheddmmgdffjm", - "rdns": "com.flowfoundation.wallet" - } - } as any, -}) - // RainbowKit connectors const connectors = connectorsForWallets([ { groupName: "Recommended", "wallets": [ - flowWalletConnector as any, + flowWallet(), ], } ], { diff --git a/packages/fcl-rainbowkit-adapter/src/create-connector.ts b/packages/fcl-rainbowkit-adapter/src/create-connector.ts new file mode 100644 index 000000000..00ad8b1bc --- /dev/null +++ b/packages/fcl-rainbowkit-adapter/src/create-connector.ts @@ -0,0 +1,32 @@ +import {fclWagmiAdapter} from "@onflow/fcl-wagmi-adapter" +import {Wallet} from "@rainbow-me/rainbowkit" +import {createConnector} from "@wagmi/core" + +type FclConnectorOptions = Parameters[0] + +type DefaultWalletOptions = { + projectId: string +} + +const FALLBACK_ICON = + "https://assets.website-files.com/5f6294c0c7a8cdd643b1c820/5f6294c0c7a8cda55cb1c936_Flow_Wordmark.svg" + +export const createFclConnector = (options: FclConnectorOptions) => { + const uid = options.service?.uid + const name = options.service?.provider?.name + const iconUrl = options.service?.provider?.icon! + + return ({projectId}: DefaultWalletOptions): Wallet => ({ + id: uid ? `fcl-${uid}` : "fcl", + name: name || "Cadence Wallet", + iconUrl: iconUrl || FALLBACK_ICON, + iconBackground: "#FFFFFF", + createConnector: walletDetails => { + const connector = fclWagmiAdapter(options) + return createConnector(config => ({ + ...connector(config), + ...walletDetails, + })) + }, + }) +} diff --git a/packages/fcl-rainbowkit-adapter/src/flow-wallet.ts b/packages/fcl-rainbowkit-adapter/src/flow-wallet.ts new file mode 100644 index 000000000..55c544476 --- /dev/null +++ b/packages/fcl-rainbowkit-adapter/src/flow-wallet.ts @@ -0,0 +1,43 @@ +import {Service} from "@onflow/typedefs" +import {createFclConnector} from "./create-connector" +import * as fcl from "@onflow/fcl" + +/** + * Create a connector for the Flow Wallet (currently only supports the extension) + * @param params + * @returns + */ +export const flowWallet = (params: { + user: typeof fcl.currentUser + config: typeof fcl.config +}) => + createFclConnector({ + user: params.user || fcl.currentUser, + config: params.config || fcl.config, + service: { + f_type: "Service", + f_vsn: "1.0.0", + type: "authn", + uid: "Flow Wallet", + endpoint: + "chrome-extension://hpclkefagolihohboafpheddmmgdffjm/popup.html", + method: "EXT/RPC", + id: "hpclkefagolihohboafpheddmmgdffjm", + identity: { + address: "0x33f75ff0b830dcec", + }, + provider: { + name: "Flow Wallet", + address: "0x33f75ff0b830dcec", + description: "A wallet created for everyone", + icon: "https://lilico.app/frw-logo.png", + color: "#41CC5D", + website: "https://core.flow.com", + requires_install: true, + is_installed: true, + install_link: + "https://chromewebstore.google.com/detail/flow-wallet/hpclkefagolihohboafpheddmmgdffjm", + rdns: "com.flowfoundation.wallet", + }, + } as unknown as Service, + }) diff --git a/packages/fcl-rainbowkit-adapter/src/index.ts b/packages/fcl-rainbowkit-adapter/src/index.ts index 00ad8b1bc..7c9d28a7c 100644 --- a/packages/fcl-rainbowkit-adapter/src/index.ts +++ b/packages/fcl-rainbowkit-adapter/src/index.ts @@ -1,32 +1,2 @@ -import {fclWagmiAdapter} from "@onflow/fcl-wagmi-adapter" -import {Wallet} from "@rainbow-me/rainbowkit" -import {createConnector} from "@wagmi/core" - -type FclConnectorOptions = Parameters[0] - -type DefaultWalletOptions = { - projectId: string -} - -const FALLBACK_ICON = - "https://assets.website-files.com/5f6294c0c7a8cdd643b1c820/5f6294c0c7a8cda55cb1c936_Flow_Wordmark.svg" - -export const createFclConnector = (options: FclConnectorOptions) => { - const uid = options.service?.uid - const name = options.service?.provider?.name - const iconUrl = options.service?.provider?.icon! - - return ({projectId}: DefaultWalletOptions): Wallet => ({ - id: uid ? `fcl-${uid}` : "fcl", - name: name || "Cadence Wallet", - iconUrl: iconUrl || FALLBACK_ICON, - iconBackground: "#FFFFFF", - createConnector: walletDetails => { - const connector = fclWagmiAdapter(options) - return createConnector(config => ({ - ...connector(config), - ...walletDetails, - })) - }, - }) -} +export {createFclConnector} from "./create-connector" +export {flowWallet} from "./flow-wallet" From c93d497df8fc0e85ed937fdced0e655e520d4b3a Mon Sep 17 00:00:00 2001 From: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> Date: Tue, 18 Feb 2025 12:56:57 -0800 Subject: [PATCH 37/56] Update Rainbowkit instructions (#2149) --- packages/fcl-rainbowkit-adapter/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/fcl-rainbowkit-adapter/README.md b/packages/fcl-rainbowkit-adapter/README.md index 6503701f3..b8286a0ab 100644 --- a/packages/fcl-rainbowkit-adapter/README.md +++ b/packages/fcl-rainbowkit-adapter/README.md @@ -3,7 +3,7 @@ ## Usage ```typescript -import { createFclConnector } from '@onflow/fcl-rainbowkit-adapter'; +import { createFclConnector, flowWallet } from '@onflow/fcl-rainbowkit-adapter'; import { connectorsForWallets } from '@rainbow-me/rainbowkit'; import { flowTestnet, From e843d561f6ef344d62165952ee7881d43130f397 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> Date: Wed, 19 Feb 2025 12:36:07 -0800 Subject: [PATCH 38/56] Make FCL Ethereum provider associative (#2150) --- .../src/accounts/account-manager.test.ts | 54 +++++++++++++++++++ .../src/accounts/account-manager.ts | 14 ++++- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts b/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts index fc1bd14bc..f8f898449 100644 --- a/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts +++ b/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts @@ -271,6 +271,60 @@ describe("AccountManager", () => { expect(await accountManager.getAccounts()).toEqual(["0x123"]) }) + + test("should update accounts if user is connected to the same authz service", async () => { + userMock.set({ + addr: "0x1", + services: [ + { + f_type: "Service", + f_vsn: "1.0.0", + endpoint: "flow_authn", + method: "EXT/RPC", + type: "authz", + uid: "123", + }, + ], + } as CurrentUser) + + const accountManager = new AccountManager(userMock.mock, networkManager, { + uid: "123", + } as any) + + const callback = jest.fn() + accountManager.subscribe(callback) + + await new Promise(setImmediate) + + expect(callback).toHaveBeenCalledWith([]) + }) + + it("should not update accounts if user is connected to different authz service", async () => { + userMock.set({ + addr: "0x1", + services: [ + { + f_type: "Service", + f_vsn: "1.0.0", + endpoint: "flow_authn", + method: "EXT/RPC", + type: "authz", + uid: "123", + }, + ], + } as CurrentUser) + + const accountManager = new AccountManager(userMock.mock, networkManager, { + uid: "abc", + } as any) + + const callback = jest.fn() + accountManager.subscribe(callback) + + await new Promise(setImmediate) + + expect(callback).toHaveBeenCalledWith([]) + }) }) describe("sendTransaction", () => { diff --git a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts index 6d2c99f2e..ee9895ea9 100644 --- a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts +++ b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts @@ -70,7 +70,19 @@ export class AccountManager { $user .pipe( - map(snapshot => snapshot.addr || null), + // Only listen bind to users matching the current authz service + map(snapshot => { + const addr = snapshot?.addr || null + if (!addr) { + return null + } + + const authzService = snapshot?.services?.find( + service => service.type === "authz" + ) + const matchingAuthzService = authzService?.uid === this.service?.uid + return matchingAuthzService ? addr : null + }), distinctUntilChanged(), switchMap(addr => concat( From f691a8cb0bd335c4b3df40782298bafcae3491d7 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> Date: Wed, 19 Feb 2025 15:28:32 -0800 Subject: [PATCH 39/56] Remove panic from Cadence TX (#2153) --- packages/fcl-ethereum-provider/src/cadence.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/fcl-ethereum-provider/src/cadence.ts b/packages/fcl-ethereum-provider/src/cadence.ts index 3cb62e15c..4c48bd26e 100644 --- a/packages/fcl-ethereum-provider/src/cadence.ts +++ b/packages/fcl-ethereum-provider/src/cadence.ts @@ -52,7 +52,6 @@ transaction(evmContractAddressHex: String, calldata: String, gasLimit: UInt64, v gasLimit: gasLimit, value: valueBalance ) - assert(callResult.status == EVM.Status.successful, message: "Call failed") } }` From b010de219e1d8aba61d980ac54431981c0c5d4cc Mon Sep 17 00:00:00 2001 From: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> Date: Fri, 21 Feb 2025 09:28:15 -0800 Subject: [PATCH 40/56] Add WalletConnect support (#2133) --- package-lock.json | 542 +++++++++++------- packages/fcl-core/src/current-user/index.js | 5 +- packages/fcl-ethereum-provider/package.json | 7 +- .../src/accounts/account-manager.ts | 12 +- .../src/create-provider.ts | 16 +- packages/fcl-ethereum-provider/src/index.ts | 1 + .../fcl-ethereum-provider/src/wc-provider.ts | 221 +++++++ packages/fcl-rainbowkit-adapter/package.json | 4 +- .../src/create-connector.ts | 44 +- .../src/get-wc-connector.ts | 153 +++++ packages/fcl-rainbowkit-adapter/src/index.ts | 6 +- .../src/{ => wallets}/flow-wallet.ts | 4 +- .../src/wallets/wc-wallet.ts | 63 ++ packages/fcl-wagmi-adapter/package.json | 4 + .../fcl-wagmi-adapter/src/fcl-connector.ts | 213 +++++++ packages/fcl-wagmi-adapter/src/index.ts | 217 +------ .../fcl-wagmi-adapter/src/wc-connector.ts | 500 ++++++++++++++++ packages/fcl-wc/package.json | 2 + packages/fcl-wc/src/fcl-wc.ts | 36 +- packages/fcl-wc/src/index.ts | 2 +- packages/fcl-wc/src/service.ts | 129 ++++- packages/fcl-wc/src/session.ts | 83 ++- packages/fcl-wc/src/store.ts | 24 + .../discovery/rpc/handlers/request-wc-qr.ts | 10 +- .../fcl/src/utils/walletconnect/loader.ts | 11 +- 25 files changed, 1801 insertions(+), 508 deletions(-) create mode 100644 packages/fcl-ethereum-provider/src/wc-provider.ts create mode 100644 packages/fcl-rainbowkit-adapter/src/get-wc-connector.ts rename packages/fcl-rainbowkit-adapter/src/{ => wallets}/flow-wallet.ts (93%) create mode 100644 packages/fcl-rainbowkit-adapter/src/wallets/wc-wallet.ts create mode 100644 packages/fcl-wagmi-adapter/src/fcl-connector.ts create mode 100644 packages/fcl-wagmi-adapter/src/wc-connector.ts create mode 100644 packages/fcl-wc/src/store.ts diff --git a/package-lock.json b/package-lock.json index c4ae5c935..7190656c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2365,11 +2365,10 @@ } }, "node_modules/@coinbase/wallet-sdk": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/@coinbase/wallet-sdk/-/wallet-sdk-4.2.3.tgz", - "integrity": "sha512-BcyHZ/Ec84z0emORzqdXDv4P0oV+tV3a0OirfA8Ko1JGBIAVvB+hzLvZzCDvnuZx7MTK+Dd8Y9Tjlo446BpCIg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@coinbase/wallet-sdk/-/wallet-sdk-4.3.0.tgz", + "integrity": "sha512-T3+SNmiCw4HzDm4we9wCHCxlP0pqCiwKe4sOwPH3YAK2KSKjxPRydKu6UQJrdONFVLG7ujXvbd/6ZqmvJb8rkw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@noble/hashes": "^1.4.0", "clsx": "^1.2.1", @@ -2381,8 +2380,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@discoveryjs/json-ext": { "version": "0.5.7", @@ -2397,7 +2395,6 @@ "resolved": "https://registry.npmjs.org/@ecies/ciphers/-/ciphers-0.2.2.tgz", "integrity": "sha512-ylfGR7PyTd+Rm2PqQowG08BCKA22QuX8NzrL+LxAAvazN10DMwdJ2fWwAzRj05FI/M8vNFGm3cv9Wq/GFWCBLg==", "license": "MIT", - "peer": true, "engines": { "bun": ">=1", "deno": ">=2", @@ -2585,7 +2582,6 @@ "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-3.2.0.tgz", "integrity": "sha512-pksvzI0VyLgmuEF2FA/JR/4/y6hcPq8OUail3/AvycBaW1d5VSauOZzqGvJ3RTmR4MU35lWE8KseKOsEhrFRBA==", "license": "MIT", - "peer": true, "dependencies": { "@ethereumjs/util": "^8.1.0", "crc-32": "^1.2.0" @@ -2596,7 +2592,6 @@ "resolved": "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-4.0.1.tgz", "integrity": "sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw==", "license": "MPL-2.0", - "peer": true, "bin": { "rlp": "bin/rlp" }, @@ -2609,7 +2604,6 @@ "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-4.2.0.tgz", "integrity": "sha512-1nc6VO4jtFd172BbSnTnDQVr9IYBFl1y4xPzZdtkrkKIncBCkdbgfdRV+MiTkJYAtTxvV12GRZLqBFT1PNK6Yw==", "license": "MPL-2.0", - "peer": true, "dependencies": { "@ethereumjs/common": "^3.2.0", "@ethereumjs/rlp": "^4.0.1", @@ -2625,7 +2619,6 @@ "resolved": "https://registry.npmjs.org/@ethereumjs/util/-/util-8.1.0.tgz", "integrity": "sha512-zQ0IqbdX8FZ9aw11vP+dZkKDkS+kgIvQPHnSAXzP9pLu+Rfu3D3XEeLbicvoXJTYnhZiPmsZUxgdzXwNKxRPbA==", "license": "MPL-2.0", - "peer": true, "dependencies": { "@ethereumjs/rlp": "^4.0.1", "ethereum-cryptography": "^2.0.0", @@ -5778,7 +5771,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@metamask/eth-json-rpc-provider/-/eth-json-rpc-provider-1.0.1.tgz", "integrity": "sha512-whiUMPlAOrVGmX8aKYVPvlKyG4CpQXiNNyt74vE1xb5sPvmx5oA7B/kOi/JdBvhGQq97U1/AVdXEdk2zkP8qyA==", - "peer": true, "dependencies": { "@metamask/json-rpc-engine": "^7.0.0", "@metamask/safe-event-emitter": "^3.0.0", @@ -5793,7 +5785,6 @@ "resolved": "https://registry.npmjs.org/@metamask/json-rpc-engine/-/json-rpc-engine-7.3.3.tgz", "integrity": "sha512-dwZPq8wx9yV3IX2caLi9q9xZBw2XeIoYqdyihDDDpuHVCEiqadJLwqM3zy+uwf6F1QYQ65A8aOMQg1Uw7LMLNg==", "license": "ISC", - "peer": true, "dependencies": { "@metamask/rpc-errors": "^6.2.1", "@metamask/safe-event-emitter": "^3.0.0", @@ -5808,7 +5799,6 @@ "resolved": "https://registry.npmjs.org/@metamask/utils/-/utils-8.5.0.tgz", "integrity": "sha512-I6bkduevXb72TIM9q2LRO63JSsF9EXduh3sBr9oybNX2hNNpr/j1tEjXrsG0Uabm4MJ1xkGAQEMwifvKZIkyxQ==", "license": "ISC", - "peer": true, "dependencies": { "@ethereumjs/tx": "^4.2.0", "@metamask/superstruct": "^3.0.0", @@ -5829,7 +5819,6 @@ "resolved": "https://registry.npmjs.org/@metamask/utils/-/utils-5.0.2.tgz", "integrity": "sha512-yfmE79bRQtnMzarnKfX7AEJBwFTxvTyw3nBQlu/5rmGXrjAeAMltoGxO62TFurxrQAFMNa/fEjIHNvungZp0+g==", "license": "ISC", - "peer": true, "dependencies": { "@ethereumjs/tx": "^4.1.2", "@types/debug": "^4.1.7", @@ -5846,7 +5835,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "license": "ISC", - "peer": true, "bin": { "semver": "bin/semver.js" }, @@ -5859,7 +5847,6 @@ "resolved": "https://registry.npmjs.org/@metamask/json-rpc-engine/-/json-rpc-engine-8.0.2.tgz", "integrity": "sha512-IoQPmql8q7ABLruW7i4EYVHWUbF74yrp63bRuXV5Zf9BQwcn5H9Ww1eLtROYvI1bUXwOiHZ6qT5CWTrDc/t/AA==", "license": "ISC", - "peer": true, "dependencies": { "@metamask/rpc-errors": "^6.2.1", "@metamask/safe-event-emitter": "^3.0.0", @@ -5874,7 +5861,6 @@ "resolved": "https://registry.npmjs.org/@metamask/json-rpc-middleware-stream/-/json-rpc-middleware-stream-7.0.2.tgz", "integrity": "sha512-yUdzsJK04Ev98Ck4D7lmRNQ8FPioXYhEUZOMS01LXW8qTvPGiRVXmVltj2p4wrLkh0vW7u6nv0mNl5xzC5Qmfg==", "license": "ISC", - "peer": true, "dependencies": { "@metamask/json-rpc-engine": "^8.0.2", "@metamask/safe-event-emitter": "^3.0.0", @@ -5890,7 +5876,6 @@ "resolved": "https://registry.npmjs.org/@metamask/object-multiplex/-/object-multiplex-2.1.0.tgz", "integrity": "sha512-4vKIiv0DQxljcXwfpnbsXcfa5glMj5Zg9mqn4xpIWqkv6uJ2ma5/GtUfLFSxhlxnR8asRMv8dDmWya1Tc1sDFA==", "license": "ISC", - "peer": true, "dependencies": { "once": "^1.4.0", "readable-stream": "^3.6.2" @@ -5904,7 +5889,6 @@ "resolved": "https://registry.npmjs.org/@metamask/onboarding/-/onboarding-1.0.1.tgz", "integrity": "sha512-FqHhAsCI+Vacx2qa5mAFcWNSrTcVGMNjzxVgaX8ECSny/BJ9/vgXP9V7WF/8vb9DltPeQkxr+Fnfmm6GHfmdTQ==", "license": "MIT", - "peer": true, "dependencies": { "bowser": "^2.9.0" } @@ -5914,7 +5898,6 @@ "resolved": "https://registry.npmjs.org/@metamask/providers/-/providers-16.1.0.tgz", "integrity": "sha512-znVCvux30+3SaUwcUGaSf+pUckzT5ukPRpcBmy+muBLC0yaWnBcvDqGfcsw6CBIenUdFrVoAFa8B6jsuCY/a+g==", "license": "MIT", - "peer": true, "dependencies": { "@metamask/json-rpc-engine": "^8.0.1", "@metamask/json-rpc-middleware-stream": "^7.0.1", @@ -5938,7 +5921,6 @@ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "license": "MIT", - "peer": true, "engines": { "node": ">=8" }, @@ -5951,7 +5933,6 @@ "resolved": "https://registry.npmjs.org/@metamask/rpc-errors/-/rpc-errors-6.4.0.tgz", "integrity": "sha512-1ugFO1UoirU2esS3juZanS/Fo8C8XYocCuBpfZI5N7ECtoG+zu0wF+uWZASik6CkO6w9n/Iebt4iI4pT0vptpg==", "license": "MIT", - "peer": true, "dependencies": { "@metamask/utils": "^9.0.0", "fast-safe-stringify": "^2.0.6" @@ -5965,7 +5946,6 @@ "resolved": "https://registry.npmjs.org/@metamask/utils/-/utils-9.3.0.tgz", "integrity": "sha512-w8CVbdkDrVXFJbfBSlDfafDR6BAkpDmv1bC1UJVCoVny5tW2RKAdn9i68Xf7asYT4TnUhl/hN4zfUiKQq9II4g==", "license": "ISC", - "peer": true, "dependencies": { "@ethereumjs/tx": "^4.2.0", "@metamask/superstruct": "^3.1.0", @@ -5986,7 +5966,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "license": "ISC", - "peer": true, "bin": { "semver": "bin/semver.js" }, @@ -5999,22 +5978,20 @@ "resolved": "https://registry.npmjs.org/@metamask/safe-event-emitter/-/safe-event-emitter-3.1.2.tgz", "integrity": "sha512-5yb2gMI1BDm0JybZezeoX/3XhPDOtTbcFvpTXM9kxsoZjPZFh4XciqRbpD6N86HYZqWDhEaKUDuOyR0sQHEjMA==", "license": "ISC", - "peer": true, "engines": { "node": ">=12.0.0" } }, "node_modules/@metamask/sdk": { - "version": "0.31.5", - "resolved": "https://registry.npmjs.org/@metamask/sdk/-/sdk-0.31.5.tgz", - "integrity": "sha512-i7wteqO/fU2JWQrMZz+addHokYThHYznp4nYXviv+QysdxGVgAYvcW/PBA+wpeP3veX7QGfNqMPgSsZbBrASYw==", - "peer": true, + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/@metamask/sdk/-/sdk-0.32.0.tgz", + "integrity": "sha512-WmGAlP1oBuD9hk4CsdlG1WJFuPtYJY+dnTHJMeCyohTWD2GgkcLMUUuvu9lO1/NVzuOoSi1OrnjbuY1O/1NZ1g==", "dependencies": { "@babel/runtime": "^7.26.0", "@metamask/onboarding": "^1.0.1", "@metamask/providers": "16.1.0", - "@metamask/sdk-communication-layer": "0.31.0", - "@metamask/sdk-install-modal-web": "0.31.5", + "@metamask/sdk-communication-layer": "0.32.0", + "@metamask/sdk-install-modal-web": "0.32.0", "@paulmillr/qr": "^0.2.1", "bowser": "^2.9.0", "cross-fetch": "^4.0.0", @@ -6032,19 +6009,17 @@ } }, "node_modules/@metamask/sdk-install-modal-web": { - "version": "0.31.5", - "resolved": "https://registry.npmjs.org/@metamask/sdk-install-modal-web/-/sdk-install-modal-web-0.31.5.tgz", - "integrity": "sha512-ZfrVkPAabfH4AIxcTlxQN5oyyzzVXFTLZrm1/BJ+X632d9MiyAVHNtiqa9EZpZYkZGk2icmDVP+xCpvJmVOVpQ==", - "peer": true, + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/@metamask/sdk-install-modal-web/-/sdk-install-modal-web-0.32.0.tgz", + "integrity": "sha512-TFoktj0JgfWnQaL3yFkApqNwcaqJ+dw4xcnrJueMP3aXkSNev2Ido+WVNOg4IIMxnmOrfAC9t0UJ0u/dC9MjOQ==", "dependencies": { "@paulmillr/qr": "^0.2.1" } }, "node_modules/@metamask/sdk/node_modules/@metamask/sdk-communication-layer": { - "version": "0.31.0", - "resolved": "https://registry.npmjs.org/@metamask/sdk-communication-layer/-/sdk-communication-layer-0.31.0.tgz", - "integrity": "sha512-V9CxdzabDPjQVgmKGHsyU3SYt4Af27g+4DbGCx0fLoHqN/i1RBDZqs/LYbJX3ykJCANzE+llz/MolMCMrzM2RA==", - "peer": true, + "version": "0.32.0", + "resolved": "https://registry.npmjs.org/@metamask/sdk-communication-layer/-/sdk-communication-layer-0.32.0.tgz", + "integrity": "sha512-dmj/KFjMi1fsdZGIOtbhxdg3amxhKL/A5BqSU4uh/SyDKPub/OT+x5pX8bGjpTL1WPWY/Q0OIlvFyX3VWnT06Q==", "dependencies": { "bufferutil": "^4.0.8", "date-fns": "^2.29.3", @@ -6065,7 +6040,6 @@ "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz", "integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==", "license": "MIT", - "peer": true, "dependencies": { "node-fetch": "^2.7.0" } @@ -6074,15 +6048,13 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD", - "peer": true + "license": "0BSD" }, "node_modules/@metamask/sdk/node_modules/util": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", "license": "MIT", - "peer": true, "dependencies": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", @@ -6096,7 +6068,6 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "license": "MIT", - "peer": true, "bin": { "uuid": "dist/bin/uuid" } @@ -6106,7 +6077,6 @@ "resolved": "https://registry.npmjs.org/@metamask/superstruct/-/superstruct-3.1.0.tgz", "integrity": "sha512-N08M56HdOgBfRKkrgCMZvQppkZGcArEop3kixNEtVbJKm6P9Cfg0YkI6X0s1g78sNrj2fWUwvJADdZuzJgFttA==", "license": "MIT", - "peer": true, "engines": { "node": ">=16.0.0" } @@ -6116,7 +6086,6 @@ "resolved": "https://registry.npmjs.org/@metamask/utils/-/utils-8.5.0.tgz", "integrity": "sha512-I6bkduevXb72TIM9q2LRO63JSsF9EXduh3sBr9oybNX2hNNpr/j1tEjXrsG0Uabm4MJ1xkGAQEMwifvKZIkyxQ==", "license": "ISC", - "peer": true, "dependencies": { "@ethereumjs/tx": "^4.2.0", "@metamask/superstruct": "^3.0.0", @@ -6137,7 +6106,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "license": "ISC", - "peer": true, "bin": { "semver": "bin/semver.js" }, @@ -6256,7 +6224,6 @@ "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.2.1.tgz", "integrity": "sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA==", "license": "MIT", - "peer": true, "engines": { "node": "^14.21.3 || >=16" }, @@ -7401,7 +7368,6 @@ "resolved": "https://registry.npmjs.org/@paulmillr/qr/-/qr-0.2.1.tgz", "integrity": "sha512-IHnV6A+zxU7XwmKFinmYjUcwlyK9+xkG3/s9KcQhI9BjQKycrJ1JRO+FbNYPwZiPKW3je/DR0k7w8/gLa5eaxQ==", "license": "(MIT OR Apache-2.0)", - "peer": true, "funding": { "url": "https://paulmillr.com/funding/" } @@ -9554,7 +9520,6 @@ "resolved": "https://registry.npmjs.org/@safe-global/safe-apps-provider/-/safe-apps-provider-0.18.5.tgz", "integrity": "sha512-9v9wjBi3TwLsEJ3C2ujYoexp3pFJ0omDLH/GX91e2QB+uwCKTBYyhxFSrTQ9qzoyQd+bfsk4gjOGW87QcJhf7g==", "license": "MIT", - "peer": true, "dependencies": { "@safe-global/safe-apps-sdk": "^9.1.0", "events": "^3.3.0" @@ -9565,7 +9530,6 @@ "resolved": "https://registry.npmjs.org/@safe-global/safe-apps-sdk/-/safe-apps-sdk-9.1.0.tgz", "integrity": "sha512-N5p/ulfnnA2Pi2M3YeWjULeWbjo7ei22JwU/IXnhoHzKq3pYCN6ynL9mJBOlvDVv892EgLPCWCOwQk/uBT2v0Q==", "license": "MIT", - "peer": true, "dependencies": { "@safe-global/safe-gateway-typescript-sdk": "^3.5.3", "viem": "^2.1.1" @@ -9576,7 +9540,6 @@ "resolved": "https://registry.npmjs.org/@safe-global/safe-gateway-typescript-sdk/-/safe-gateway-typescript-sdk-3.22.9.tgz", "integrity": "sha512-7ojVK/crhOaGowEO8uYWaopZzcr5rR76emgllGIfjCLR70aY4PbASpi9Pbs+7jIRzPDBBkM0RBo+zYx5UduX8Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=16" } @@ -9744,15 +9707,18 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@stablelib/aead": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/aead/-/aead-1.0.1.tgz", + "integrity": "sha512-q39ik6sxGHewqtO0nP4BuSe3db5G1fEJE8ukvngS2gLkBXyy6E7pLubhbYgnkDFv6V8cWaxcE4Xn0t6LWcJkyg==", "license": "MIT" }, "node_modules/@stablelib/binary": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/binary/-/binary-1.0.1.tgz", + "integrity": "sha512-ClJWvmL6UBM/wjkvv/7m5VP3GMr9t0osr4yVgLZsLCOz4hGN9gIAFEqnJ0TsSMAN+n840nf2cHZnA5/KFqHC7Q==", "license": "MIT", "dependencies": { "@stablelib/int": "^1.0.1" @@ -9760,10 +9726,14 @@ }, "node_modules/@stablelib/bytes": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/bytes/-/bytes-1.0.1.tgz", + "integrity": "sha512-Kre4Y4kdwuqL8BR2E9hV/R5sOrUj6NanZaZis0V6lX5yzqC3hBuVSDXUIBqQv/sCpmuWRiHLwqiT1pqqjuBXoQ==", "license": "MIT" }, "node_modules/@stablelib/chacha": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/chacha/-/chacha-1.0.1.tgz", + "integrity": "sha512-Pmlrswzr0pBzDofdFuVe1q7KdsHKhhU24e8gkEwnTGOmlC7PADzLVxGdn2PoNVBBabdg0l/IfLKg6sHAbTQugg==", "license": "MIT", "dependencies": { "@stablelib/binary": "^1.0.1", @@ -9772,6 +9742,8 @@ }, "node_modules/@stablelib/chacha20poly1305": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/chacha20poly1305/-/chacha20poly1305-1.0.1.tgz", + "integrity": "sha512-MmViqnqHd1ymwjOQfghRKw2R/jMIGT3wySN7cthjXCBdO+qErNPUBnRzqNpnvIwg7JBCg3LdeCZZO4de/yEhVA==", "license": "MIT", "dependencies": { "@stablelib/aead": "^1.0.1", @@ -9784,10 +9756,14 @@ }, "node_modules/@stablelib/constant-time": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/constant-time/-/constant-time-1.0.1.tgz", + "integrity": "sha512-tNOs3uD0vSJcK6z1fvef4Y+buN7DXhzHDPqRLSXUel1UfqMB1PWNsnnAezrKfEwTLpN0cGH2p9NNjs6IqeD0eg==", "license": "MIT" }, "node_modules/@stablelib/ed25519": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@stablelib/ed25519/-/ed25519-1.0.3.tgz", + "integrity": "sha512-puIMWaX9QlRsbhxfDc5i+mNPMY+0TmQEskunY1rZEBPi1acBCVQAhnsk/1Hk50DGPtVsZtAWQg4NHGlVaO9Hqg==", "license": "MIT", "dependencies": { "@stablelib/random": "^1.0.2", @@ -9797,10 +9773,14 @@ }, "node_modules/@stablelib/hash": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/hash/-/hash-1.0.1.tgz", + "integrity": "sha512-eTPJc/stDkdtOcrNMZ6mcMK1e6yBbqRBaNW55XA1jU8w/7QdnCF0CmMmOD1m7VSkBR44PWrMHU2l6r8YEQHMgg==", "license": "MIT" }, "node_modules/@stablelib/hkdf": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/hkdf/-/hkdf-1.0.1.tgz", + "integrity": "sha512-SBEHYE16ZXlHuaW5RcGk533YlBj4grMeg5TooN80W3NpcHRtLZLLXvKyX0qcRFxf+BGDobJLnwkvgEwHIDBR6g==", "license": "MIT", "dependencies": { "@stablelib/hash": "^1.0.1", @@ -9810,6 +9790,8 @@ }, "node_modules/@stablelib/hmac": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/hmac/-/hmac-1.0.1.tgz", + "integrity": "sha512-V2APD9NSnhVpV/QMYgCVMIYKiYG6LSqw1S65wxVoirhU/51ACio6D4yDVSwMzuTJXWZoVHbDdINioBwKy5kVmA==", "license": "MIT", "dependencies": { "@stablelib/constant-time": "^1.0.1", @@ -9819,10 +9801,14 @@ }, "node_modules/@stablelib/int": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/int/-/int-1.0.1.tgz", + "integrity": "sha512-byr69X/sDtDiIjIV6m4roLVWnNNlRGzsvxw+agj8CIEazqWGOQp2dTYgQhtyVXV9wpO6WyXRQUzLV/JRNumT2w==", "license": "MIT" }, "node_modules/@stablelib/keyagreement": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/keyagreement/-/keyagreement-1.0.1.tgz", + "integrity": "sha512-VKL6xBwgJnI6l1jKrBAfn265cspaWBPAPEc62VBQrWHLqVgNRE09gQ/AnOEyKUWrrqfD+xSQ3u42gJjLDdMDQg==", "license": "MIT", "dependencies": { "@stablelib/bytes": "^1.0.1" @@ -9830,6 +9816,8 @@ }, "node_modules/@stablelib/poly1305": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/poly1305/-/poly1305-1.0.1.tgz", + "integrity": "sha512-1HlG3oTSuQDOhSnLwJRKeTRSAdFNVB/1djy2ZbS35rBSJ/PFqx9cf9qatinWghC2UbfOYD8AcrtbUQl8WoxabA==", "license": "MIT", "dependencies": { "@stablelib/constant-time": "^1.0.1", @@ -9838,6 +9826,8 @@ }, "node_modules/@stablelib/random": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@stablelib/random/-/random-1.0.2.tgz", + "integrity": "sha512-rIsE83Xpb7clHPVRlBj8qNe5L8ISQOzjghYQm/dZ7VaM2KHYwMW5adjQjrzTZCchFnNCNhkwtnOBa9HTMJCI8w==", "license": "MIT", "dependencies": { "@stablelib/binary": "^1.0.1", @@ -9846,6 +9836,8 @@ }, "node_modules/@stablelib/sha256": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/sha256/-/sha256-1.0.1.tgz", + "integrity": "sha512-GIIH3e6KH+91FqGV42Kcj71Uefd/QEe7Dy42sBTeqppXV95ggCcxLTk39bEr+lZfJmp+ghsR07J++ORkRELsBQ==", "license": "MIT", "dependencies": { "@stablelib/binary": "^1.0.1", @@ -9855,6 +9847,8 @@ }, "node_modules/@stablelib/sha512": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/sha512/-/sha512-1.0.1.tgz", + "integrity": "sha512-13gl/iawHV9zvDKciLo1fQ8Bgn2Pvf7OV6amaRVKiq3pjQ3UmEpXxWiAfV8tYjUpeZroBxtyrwtdooQT/i3hzw==", "license": "MIT", "dependencies": { "@stablelib/binary": "^1.0.1", @@ -9864,10 +9858,14 @@ }, "node_modules/@stablelib/wipe": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/wipe/-/wipe-1.0.1.tgz", + "integrity": "sha512-WfqfX/eXGiAd3RJe4VU2snh/ZPwtSjLG4ynQ/vYzvghTh7dHFcI1wl+nrkWG6lGhukOxOsUHfv8dUXr58D0ayg==", "license": "MIT" }, "node_modules/@stablelib/x25519": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@stablelib/x25519/-/x25519-1.0.3.tgz", + "integrity": "sha512-KnTbKmUhPhHavzobclVJQG5kuivH+qDLpe84iRqX3CLrKp881cF160JvXJ+hjn1aMyCwYOKeIZefIH/P5cJoRw==", "license": "MIT", "dependencies": { "@stablelib/keyagreement": "^1.0.1", @@ -10008,7 +10006,6 @@ "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", "license": "MIT", - "peer": true, "dependencies": { "@types/ms": "*" } @@ -10081,8 +10078,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/node": { "version": "18.19.57", @@ -10447,14 +10443,13 @@ } }, "node_modules/@wagmi/connectors": { - "version": "5.7.5", - "resolved": "https://registry.npmjs.org/@wagmi/connectors/-/connectors-5.7.5.tgz", - "integrity": "sha512-btqHHUSTzg4BZe9at/7SnRPv4cz8O3pisbeZBh0qxKz7PVm+9vRxY0bSala3xQPDcS0PRTB30Vn/+lM73GCjbw==", + "version": "5.7.7", + "resolved": "https://registry.npmjs.org/@wagmi/connectors/-/connectors-5.7.7.tgz", + "integrity": "sha512-hveKxuR35ZQQyteLo7aiN/TBVECYKVbLNTYGGgqzTNHJ8vVoblTP9PwPrRPGOPi5ji8raYSFWShxNK7QpGL+Kg==", "license": "MIT", - "peer": true, "dependencies": { - "@coinbase/wallet-sdk": "4.2.3", - "@metamask/sdk": "0.31.5", + "@coinbase/wallet-sdk": "4.3.0", + "@metamask/sdk": "0.32.0", "@safe-global/safe-apps-provider": "0.18.5", "@safe-global/safe-apps-sdk": "9.1.0", "@walletconnect/ethereum-provider": "2.17.0", @@ -10464,7 +10459,7 @@ "url": "https://github.com/sponsors/wevm" }, "peerDependencies": { - "@wagmi/core": "2.16.3", + "@wagmi/core": "2.16.4", "typescript": ">=5.0.4", "viem": "2.x" }, @@ -10475,9 +10470,9 @@ } }, "node_modules/@wagmi/core": { - "version": "2.16.3", - "resolved": "https://registry.npmjs.org/@wagmi/core/-/core-2.16.3.tgz", - "integrity": "sha512-SVovoWHaQ2AIkmGf+ucNijT6AHXcTMffFcLmcFF6++y21x+ge7Gkh3UoJiU91SDDv8n08eTQ9jbyia3GEgU5jQ==", + "version": "2.16.4", + "resolved": "https://registry.npmjs.org/@wagmi/core/-/core-2.16.4.tgz", + "integrity": "sha512-E4jY4A98gwuHCjzuEajHIG/WhNDY5BChVHMjflV9Bx5CO7COqYRG2dcRLuF6Bo0LQNvVvXDAFUwR2JShJnT5pA==", "license": "MIT", "dependencies": { "eventemitter3": "5.0.1", @@ -10508,22 +10503,24 @@ "license": "MIT" }, "node_modules/@walletconnect/core": { - "version": "2.17.1", + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/@walletconnect/core/-/core-2.18.0.tgz", + "integrity": "sha512-i/olu/IwYtBiWYqyfNUMxq4b6QS5dv+ZVVGmLT2buRwdH6MGETN0Bx3/z6rXJzd1sNd+QL07fxhSFxCekL57tA==", "license": "Apache-2.0", "dependencies": { "@walletconnect/heartbeat": "1.2.2", "@walletconnect/jsonrpc-provider": "1.0.14", "@walletconnect/jsonrpc-types": "1.0.4", "@walletconnect/jsonrpc-utils": "1.0.8", - "@walletconnect/jsonrpc-ws-connection": "1.0.14", + "@walletconnect/jsonrpc-ws-connection": "1.0.16", "@walletconnect/keyvaluestorage": "1.1.1", "@walletconnect/logger": "2.1.2", "@walletconnect/relay-api": "1.0.11", - "@walletconnect/relay-auth": "1.0.4", + "@walletconnect/relay-auth": "1.1.0", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.17.1", - "@walletconnect/utils": "2.17.1", + "@walletconnect/types": "2.18.0", + "@walletconnect/utils": "2.18.0", "@walletconnect/window-getters": "1.0.1", "events": "3.3.0", "lodash.isequal": "4.5.0", @@ -10533,6 +10530,79 @@ "node": ">=18" } }, + "node_modules/@walletconnect/core/node_modules/@noble/curves": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.0.tgz", + "integrity": "sha512-j84kjAbzEnQHaSIhRPUmB3/eVXu2k3dKPl2LOrR8fSOIL+89U+7lV117EWHtq/GHM3ReGHM46iRBdZfpc4HRUQ==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.7.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@walletconnect/core/node_modules/@noble/hashes": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.0.tgz", + "integrity": "sha512-HXydb0DgzTpDPwbVeDGCG1gIu7X6+AuU6Zl6av/E/KG8LMsvPntvq+w17CHRpKBmN6Ybdrt1eP3k4cj8DJa78w==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@walletconnect/core/node_modules/@walletconnect/jsonrpc-ws-connection": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-ws-connection/-/jsonrpc-ws-connection-1.0.16.tgz", + "integrity": "sha512-G81JmsMqh5nJheE1mPst1W0WfVv0SG3N7JggwLLGnI7iuDZJq8cRJvQwLGKHn5H1WTW7DEPCo00zz5w62AbL3Q==", + "license": "MIT", + "dependencies": { + "@walletconnect/jsonrpc-utils": "^1.0.6", + "@walletconnect/safe-json": "^1.0.2", + "events": "^3.3.0", + "ws": "^7.5.1" + } + }, + "node_modules/@walletconnect/core/node_modules/@walletconnect/relay-auth": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@walletconnect/relay-auth/-/relay-auth-1.1.0.tgz", + "integrity": "sha512-qFw+a9uRz26jRCDgL7Q5TA9qYIgcNY8jpJzI1zAWNZ8i7mQjaijRnWFKsCHAU9CyGjvt6RKrRXyFtFOpWTVmCQ==", + "license": "MIT", + "dependencies": { + "@noble/curves": "1.8.0", + "@noble/hashes": "1.7.0", + "@walletconnect/safe-json": "^1.0.1", + "@walletconnect/time": "^1.0.2", + "uint8arrays": "^3.0.0" + } + }, + "node_modules/@walletconnect/core/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/@walletconnect/environment": { "version": "1.0.1", "license": "MIT", @@ -10545,7 +10615,6 @@ "resolved": "https://registry.npmjs.org/@walletconnect/ethereum-provider/-/ethereum-provider-2.17.0.tgz", "integrity": "sha512-b+KTAXOb6JjoxkwpgYQQKPUcTwENGmdEdZoIDLeRicUmZTn/IQKfkMoC2frClB4YxkyoVMtj1oMV2JAax+yu9A==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@walletconnect/jsonrpc-http-connection": "1.0.8", "@walletconnect/jsonrpc-provider": "1.0.14", @@ -10564,7 +10633,6 @@ "resolved": "https://registry.npmjs.org/@walletconnect/core/-/core-2.17.0.tgz", "integrity": "sha512-On+uSaCfWdsMIQsECwWHZBmUXfrnqmv6B8SXRRuTJgd8tUpEvBkLQH4X7XkSm3zW6ozEkQTCagZ2ox2YPn3kbw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@walletconnect/heartbeat": "1.2.2", "@walletconnect/jsonrpc-provider": "1.0.14", @@ -10592,7 +10660,6 @@ "resolved": "https://registry.npmjs.org/@walletconnect/sign-client/-/sign-client-2.17.0.tgz", "integrity": "sha512-sErYwvSSHQolNXni47L3Bm10ptJc1s1YoJvJd34s5E9h9+d3rj7PrhbiW9X82deN+Dm5oA8X9tC4xty1yIBrVg==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@walletconnect/core": "2.17.0", "@walletconnect/events": "1.0.1", @@ -10610,7 +10677,6 @@ "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-2.17.0.tgz", "integrity": "sha512-i1pn9URpvt9bcjRDkabuAmpA9K7mzyKoLJlbsAujRVX7pfaG7wur7u9Jz0bk1HxvuABL5LHNncTnVKSXKQ5jZA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@walletconnect/events": "1.0.1", "@walletconnect/heartbeat": "1.2.2", @@ -10625,7 +10691,6 @@ "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-2.17.0.tgz", "integrity": "sha512-1aeQvjwsXy4Yh9G6g2eGmXrEl+BzkNjHRdCrGdMYqFTFa8ROEJfTGsSH3pLsNDlOY94CoBUvJvM55q/PMoN/FQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@stablelib/chacha20poly1305": "1.0.1", "@stablelib/hkdf": "1.0.1", @@ -10650,7 +10715,6 @@ "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==", "license": "MIT", - "peer": true, "dependencies": { "decode-uri-component": "^0.2.2", "filter-obj": "^1.1.0", @@ -10669,7 +10733,6 @@ "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -10733,6 +10796,8 @@ }, "node_modules/@walletconnect/jsonrpc-ws-connection": { "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-ws-connection/-/jsonrpc-ws-connection-1.0.14.tgz", + "integrity": "sha512-Jsl6fC55AYcbkNVkwNM6Jo+ufsuCQRqViOQ8ZBPH9pRREHH9welbBiszuTLqEJiQcO/6XfFDl6bzCJIkrEi8XA==", "license": "MIT", "dependencies": { "@walletconnect/jsonrpc-utils": "^1.0.6", @@ -10743,6 +10808,8 @@ }, "node_modules/@walletconnect/jsonrpc-ws-connection/node_modules/ws": { "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "license": "MIT", "engines": { "node": ">=8.3.0" @@ -10787,6 +10854,8 @@ }, "node_modules/@walletconnect/modal": { "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@walletconnect/modal/-/modal-2.7.0.tgz", + "integrity": "sha512-RQVt58oJ+rwqnPcIvRFeMGKuXb9qkgSmwz4noF8JZGUym3gUAzVs+uW2NQ1Owm9XOJAV+sANrtJ+VoVq1ftElw==", "license": "Apache-2.0", "dependencies": { "@walletconnect/modal-core": "2.7.0", @@ -10819,6 +10888,8 @@ }, "node_modules/@walletconnect/relay-auth": { "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@walletconnect/relay-auth/-/relay-auth-1.0.4.tgz", + "integrity": "sha512-kKJcS6+WxYq5kshpPaxGHdwf5y98ZwbfuS4EE/NkQzqrDFm5Cj+dP8LofzWvjrrLkZq7Afy7WrQMXdLy8Sx7HQ==", "license": "MIT", "dependencies": { "@stablelib/ed25519": "^1.0.2", @@ -10837,17 +10908,19 @@ } }, "node_modules/@walletconnect/sign-client": { - "version": "2.17.1", + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/@walletconnect/sign-client/-/sign-client-2.18.0.tgz", + "integrity": "sha512-oUjlRIsbHxMSRif2WvMRdvm6tMsQjMj07rl7YVcKVvZ1gF1/9GcbJPjzL/U87fv8qAQkVhIlbEg2vHaVYf6J/g==", "license": "Apache-2.0", "dependencies": { - "@walletconnect/core": "2.17.1", + "@walletconnect/core": "2.18.0", "@walletconnect/events": "1.0.1", "@walletconnect/heartbeat": "1.2.2", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/logger": "2.1.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.17.1", - "@walletconnect/utils": "2.17.1", + "@walletconnect/types": "2.18.0", + "@walletconnect/utils": "2.18.0", "events": "3.3.0" } }, @@ -10859,7 +10932,9 @@ } }, "node_modules/@walletconnect/types": { - "version": "2.17.1", + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-2.18.0.tgz", + "integrity": "sha512-g0jU+6LUuw3E/EPAQfHNK2xK/95IpRfz68tdNAFckLmefZU6kzoE1mIM1SrPJq8rT9kUPp6/APMQE+ReH2OdBA==", "license": "Apache-2.0", "dependencies": { "@walletconnect/events": "1.0.1", @@ -10875,7 +10950,6 @@ "resolved": "https://registry.npmjs.org/@walletconnect/universal-provider/-/universal-provider-2.17.0.tgz", "integrity": "sha512-d3V5Be7AqLrvzcdMZSBS8DmGDRdqnyLk1DWmRKAGgR6ieUWykhhUKlvfeoZtvJrIXrY7rUGYpH1X41UtFkW5Pw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@walletconnect/jsonrpc-http-connection": "1.0.8", "@walletconnect/jsonrpc-provider": "1.0.14", @@ -10893,7 +10967,6 @@ "resolved": "https://registry.npmjs.org/@walletconnect/core/-/core-2.17.0.tgz", "integrity": "sha512-On+uSaCfWdsMIQsECwWHZBmUXfrnqmv6B8SXRRuTJgd8tUpEvBkLQH4X7XkSm3zW6ozEkQTCagZ2ox2YPn3kbw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@walletconnect/heartbeat": "1.2.2", "@walletconnect/jsonrpc-provider": "1.0.14", @@ -10921,7 +10994,6 @@ "resolved": "https://registry.npmjs.org/@walletconnect/sign-client/-/sign-client-2.17.0.tgz", "integrity": "sha512-sErYwvSSHQolNXni47L3Bm10ptJc1s1YoJvJd34s5E9h9+d3rj7PrhbiW9X82deN+Dm5oA8X9tC4xty1yIBrVg==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@walletconnect/core": "2.17.0", "@walletconnect/events": "1.0.1", @@ -10939,7 +11011,6 @@ "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-2.17.0.tgz", "integrity": "sha512-i1pn9URpvt9bcjRDkabuAmpA9K7mzyKoLJlbsAujRVX7pfaG7wur7u9Jz0bk1HxvuABL5LHNncTnVKSXKQ5jZA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@walletconnect/events": "1.0.1", "@walletconnect/heartbeat": "1.2.2", @@ -10954,7 +11025,6 @@ "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-2.17.0.tgz", "integrity": "sha512-1aeQvjwsXy4Yh9G6g2eGmXrEl+BzkNjHRdCrGdMYqFTFa8ROEJfTGsSH3pLsNDlOY94CoBUvJvM55q/PMoN/FQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@stablelib/chacha20poly1305": "1.0.1", "@stablelib/hkdf": "1.0.1", @@ -10979,7 +11049,6 @@ "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==", "license": "MIT", - "peer": true, "dependencies": { "decode-uri-component": "^0.2.2", "filter-obj": "^1.1.0", @@ -10998,37 +11067,75 @@ "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=4" } }, "node_modules/@walletconnect/utils": { - "version": "2.17.1", + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-2.18.0.tgz", + "integrity": "sha512-6AUXIcjSxTHGRsTtmUP/oqudtwRILrQqrJsH3jS5T28FFDzZt7+On6fR4mXzi64k4nNYeWg1wMCGLEdtxmGbZQ==", "license": "Apache-2.0", "dependencies": { - "@ethersproject/hash": "5.7.0", "@ethersproject/transactions": "5.7.0", - "@stablelib/chacha20poly1305": "1.0.1", - "@stablelib/hkdf": "1.0.1", - "@stablelib/random": "1.0.2", - "@stablelib/sha256": "1.0.1", - "@stablelib/x25519": "1.0.3", + "@noble/ciphers": "1.2.1", + "@noble/curves": "1.8.1", + "@noble/hashes": "1.7.1", "@walletconnect/jsonrpc-utils": "1.0.8", "@walletconnect/keyvaluestorage": "1.1.1", "@walletconnect/relay-api": "1.0.11", - "@walletconnect/relay-auth": "1.0.4", + "@walletconnect/relay-auth": "1.1.0", "@walletconnect/safe-json": "1.0.2", "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.17.1", + "@walletconnect/types": "2.18.0", "@walletconnect/window-getters": "1.0.1", "@walletconnect/window-metadata": "1.0.1", "detect-browser": "5.3.0", - "elliptic": "6.5.7", + "elliptic": "6.6.1", "query-string": "7.1.3", "uint8arrays": "3.1.0" } }, + "node_modules/@walletconnect/utils/node_modules/@walletconnect/relay-auth": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@walletconnect/relay-auth/-/relay-auth-1.1.0.tgz", + "integrity": "sha512-qFw+a9uRz26jRCDgL7Q5TA9qYIgcNY8jpJzI1zAWNZ8i7mQjaijRnWFKsCHAU9CyGjvt6RKrRXyFtFOpWTVmCQ==", + "license": "MIT", + "dependencies": { + "@noble/curves": "1.8.0", + "@noble/hashes": "1.7.0", + "@walletconnect/safe-json": "^1.0.1", + "@walletconnect/time": "^1.0.2", + "uint8arrays": "^3.0.0" + } + }, + "node_modules/@walletconnect/utils/node_modules/@walletconnect/relay-auth/node_modules/@noble/curves": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.0.tgz", + "integrity": "sha512-j84kjAbzEnQHaSIhRPUmB3/eVXu2k3dKPl2LOrR8fSOIL+89U+7lV117EWHtq/GHM3ReGHM46iRBdZfpc4HRUQ==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.7.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@walletconnect/utils/node_modules/@walletconnect/relay-auth/node_modules/@noble/hashes": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.0.tgz", + "integrity": "sha512-HXydb0DgzTpDPwbVeDGCG1gIu7X6+AuU6Zl6av/E/KG8LMsvPntvq+w17CHRpKBmN6Ybdrt1eP3k4cj8DJa78w==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@walletconnect/utils/node_modules/query-string": { "version": "7.1.3", "license": "MIT", @@ -11690,7 +11797,6 @@ "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.2.6.tgz", "integrity": "sha512-Hs4R+4SPgamu6rSGW8C7cV9gaWUKEHykfzCCvIRuaVv636Ju10ZdeUbvb4TBEW0INuq2DHZqXbK4Nd3yG4RaRw==", "license": "MIT", - "peer": true, "dependencies": { "tslib": "^2.0.0" } @@ -11699,8 +11805,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD", - "peer": true + "license": "0BSD" }, "node_modules/asynckit": { "version": "0.4.0", @@ -11761,7 +11866,6 @@ "node_modules/available-typed-arrays": { "version": "1.0.7", "license": "MIT", - "peer": true, "dependencies": { "possible-typed-array-names": "^1.0.0" }, @@ -12391,8 +12495,7 @@ "version": "2.11.0", "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/bplist-creator": { "version": "0.0.7", @@ -12537,7 +12640,6 @@ "integrity": "sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==", "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "node-gyp-build": "^4.3.0" }, @@ -12636,7 +12738,6 @@ "node_modules/call-bind": { "version": "1.0.7", "license": "MIT", - "peer": true, "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -12656,7 +12757,6 @@ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", "license": "MIT", - "peer": true, "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" @@ -12670,7 +12770,6 @@ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", "license": "MIT", - "peer": true, "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" @@ -12786,7 +12885,6 @@ "resolved": "https://registry.npmjs.org/@coinbase/wallet-sdk/-/wallet-sdk-3.9.3.tgz", "integrity": "sha512-N/A2DRIf0Y3PHc1XAMvbBUu4zisna6qAdqABMZwBMNEfWrXpAwx16pZGkYCLGE+Rvv1edbcB2LYDRnACNcmCiw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "bn.js": "^5.2.1", "buffer": "^6.0.3", @@ -12803,15 +12901,13 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/cbw-sdk/node_modules/eventemitter3": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/chalk": { "version": "2.4.2", @@ -13161,7 +13257,6 @@ "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -13649,7 +13744,6 @@ "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", "license": "Apache-2.0", - "peer": true, "bin": { "crc32": "bin/crc32.njs" }, @@ -14056,7 +14150,6 @@ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.21.0" }, @@ -14209,7 +14302,6 @@ "node_modules/define-data-property": { "version": "1.1.4", "license": "MIT", - "peer": true, "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -14529,7 +14621,6 @@ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "license": "MIT", - "peer": true, "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", @@ -14563,7 +14654,6 @@ "resolved": "https://registry.npmjs.org/eciesjs/-/eciesjs-0.4.13.tgz", "integrity": "sha512-zBdtR4K+wbj10bWPpIOF9DW+eFYQu8miU5ypunh0t4Bvt83ZPlEWgT5Dq/0G6uwEXumZKjfb5BZxYUZQ2Hzn/Q==", "license": "MIT", - "peer": true, "dependencies": { "@ecies/ciphers": "^0.2.2", "@noble/ciphers": "^1.0.0", @@ -14600,7 +14690,9 @@ "license": "ISC" }, "node_modules/elliptic": { - "version": "6.5.7", + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", "license": "MIT", "dependencies": { "bn.js": "^4.11.9", @@ -14670,7 +14762,6 @@ "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.3.tgz", "integrity": "sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==", "license": "MIT", - "peer": true, "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1", @@ -14684,7 +14775,6 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=10.0.0" }, @@ -14706,7 +14796,6 @@ "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=10.0.0" } @@ -14873,7 +14962,6 @@ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4" } @@ -14881,7 +14969,6 @@ "node_modules/es-errors": { "version": "1.3.0", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4" } @@ -14894,7 +14981,6 @@ "node_modules/es-object-atoms": { "version": "1.0.0", "license": "MIT", - "peer": true, "dependencies": { "es-errors": "^1.3.0" }, @@ -15368,7 +15454,6 @@ "resolved": "https://registry.npmjs.org/eth-block-tracker/-/eth-block-tracker-7.1.0.tgz", "integrity": "sha512-8YdplnuE1IK4xfqpf4iU7oBxnOYAc35934o083G8ao+8WM8QQtt/mVlAY6yIAdY1eMeLqg4Z//PZjJGmWGPMRg==", "license": "MIT", - "peer": true, "dependencies": { "@metamask/eth-json-rpc-provider": "^1.0.0", "@metamask/safe-event-emitter": "^3.0.0", @@ -15385,7 +15470,6 @@ "resolved": "https://registry.npmjs.org/@metamask/utils/-/utils-5.0.2.tgz", "integrity": "sha512-yfmE79bRQtnMzarnKfX7AEJBwFTxvTyw3nBQlu/5rmGXrjAeAMltoGxO62TFurxrQAFMNa/fEjIHNvungZp0+g==", "license": "ISC", - "peer": true, "dependencies": { "@ethereumjs/tx": "^4.1.2", "@types/debug": "^4.1.7", @@ -15402,7 +15486,6 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -15412,7 +15495,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "license": "ISC", - "peer": true, "bin": { "semver": "bin/semver.js" }, @@ -15425,7 +15507,6 @@ "resolved": "https://registry.npmjs.org/eth-json-rpc-filters/-/eth-json-rpc-filters-6.0.1.tgz", "integrity": "sha512-ITJTvqoCw6OVMLs7pI8f4gG92n/St6x80ACtHodeS+IXmO0w+t1T5OOzfSt7KLSMLRkVUoexV7tztLgDxg+iig==", "license": "ISC", - "peer": true, "dependencies": { "@metamask/safe-event-emitter": "^3.0.0", "async-mutex": "^0.2.6", @@ -15442,7 +15523,6 @@ "resolved": "https://registry.npmjs.org/eth-query/-/eth-query-2.1.2.tgz", "integrity": "sha512-srES0ZcvwkR/wd5OQBRA1bIJMww1skfGS0s8wlwK3/oNP4+wnds60krvu5R1QbpRQjMmpG5OMIWro5s7gvDPsA==", "license": "ISC", - "peer": true, "dependencies": { "json-rpc-random-id": "^1.0.0", "xtend": "^4.0.1" @@ -15453,7 +15533,6 @@ "resolved": "https://registry.npmjs.org/eth-rpc-errors/-/eth-rpc-errors-4.0.3.tgz", "integrity": "sha512-Z3ymjopaoft7JDoxZcEb3pwdGh7yiYMhOwm2doUt6ASXlMavpNlK6Cre0+IMl2VSGyEU9rkiperQhp5iRxn5Pg==", "license": "MIT", - "peer": true, "dependencies": { "fast-safe-stringify": "^2.0.6" } @@ -15463,7 +15542,6 @@ "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz", "integrity": "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==", "license": "MIT", - "peer": true, "dependencies": { "@noble/curves": "1.4.2", "@noble/hashes": "1.4.0", @@ -15476,7 +15554,6 @@ "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", "license": "MIT", - "peer": true, "dependencies": { "@noble/hashes": "1.4.0" }, @@ -15489,7 +15566,6 @@ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 16" }, @@ -15502,7 +15578,6 @@ "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", "integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==", "license": "MIT", - "peer": true, "funding": { "url": "https://paulmillr.com/funding/" } @@ -15512,7 +15587,6 @@ "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.4.0.tgz", "integrity": "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==", "license": "MIT", - "peer": true, "dependencies": { "@noble/curves": "~1.4.0", "@noble/hashes": "~1.4.0", @@ -15527,7 +15601,6 @@ "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.3.0.tgz", "integrity": "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==", "license": "MIT", - "peer": true, "dependencies": { "@noble/hashes": "~1.4.0", "@scure/base": "~1.1.6" @@ -15547,8 +15620,7 @@ "version": "6.4.9", "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/eventemitter3": { "version": "4.0.7", @@ -16258,7 +16330,6 @@ "resolved": "https://registry.npmjs.org/extension-port-stream/-/extension-port-stream-3.0.0.tgz", "integrity": "sha512-an2S5quJMiy5bnZKEf6AkfH/7r8CzHvhchU40gxN+OM6HPhe7Z9T1FUychcf2M9PpPOO0Hf7BAEfJkw2TDIBDw==", "license": "ISC", - "peer": true, "dependencies": { "readable-stream": "^3.6.2 || ^4.4.2", "webextension-polyfill": ">=0.10.0 <1.0" @@ -16329,8 +16400,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/fast-xml-parser": { "version": "4.5.0", @@ -16689,7 +16759,6 @@ "node_modules/for-each": { "version": "0.3.3", "license": "MIT", - "peer": true, "dependencies": { "is-callable": "^1.1.3" } @@ -16870,7 +16939,6 @@ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", "license": "MIT", - "peer": true, "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", @@ -17004,7 +17072,6 @@ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "license": "MIT", - "peer": true, "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" @@ -17246,7 +17313,6 @@ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4" }, @@ -17359,7 +17425,6 @@ "node_modules/has-property-descriptors": { "version": "1.0.2", "license": "MIT", - "peer": true, "dependencies": { "es-define-property": "^1.0.0" }, @@ -17383,7 +17448,6 @@ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4" }, @@ -17394,7 +17458,6 @@ "node_modules/has-tostringtag": { "version": "1.0.2", "license": "MIT", - "peer": true, "dependencies": { "has-symbols": "^1.0.3" }, @@ -18030,7 +18093,6 @@ "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", "license": "MIT", - "peer": true, "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" @@ -18119,7 +18181,6 @@ "node_modules/is-callable": { "version": "1.2.7", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4" }, @@ -18228,7 +18289,6 @@ "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", "license": "MIT", - "peer": true, "dependencies": { "call-bound": "^1.0.3", "get-proto": "^1.0.0", @@ -18417,7 +18477,6 @@ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "license": "MIT", - "peer": true, "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", @@ -18515,7 +18574,6 @@ "node_modules/is-typed-array": { "version": "1.1.13", "license": "MIT", - "peer": true, "dependencies": { "which-typed-array": "^1.1.14" }, @@ -20799,7 +20857,6 @@ "resolved": "https://registry.npmjs.org/json-rpc-engine/-/json-rpc-engine-6.1.0.tgz", "integrity": "sha512-NEdLrtrq1jUZyfjkr9OCz9EzCNhnRyWtt1PAnvnhwy6e8XETS0Dtc+ZNCO2gvuAoKsIn2+vCSowXTYE4CkgnAQ==", "license": "ISC", - "peer": true, "dependencies": { "@metamask/safe-event-emitter": "^2.0.0", "eth-rpc-errors": "^4.0.2" @@ -20812,15 +20869,13 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@metamask/safe-event-emitter/-/safe-event-emitter-2.0.0.tgz", "integrity": "sha512-/kSXhY692qiV1MXu6EeOZvg5nECLclxNXcKCxJ3cXQgYuRymRHpdx/t7JXfsK+JLjwA1e1c1/SBrlQYpusC29Q==", - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/json-rpc-random-id": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-rpc-random-id/-/json-rpc-random-id-1.0.1.tgz", "integrity": "sha512-RJ9YYNCkhVDBuP4zN5BBtYAzEl03yq/jIIsyif0JY9qyJuQQZNeDK7anAPKKlyEtLSj2s8h6hNh2F8zO5q7ScA==", - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/json-schema-deref-sync": { "version": "0.13.0", @@ -20934,7 +20989,6 @@ "integrity": "sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q==", "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "node-addon-api": "^2.0.0", "node-gyp-build": "^4.2.0", @@ -20948,8 +21002,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/keyv": { "version": "4.5.4", @@ -22020,7 +22073,6 @@ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4" } @@ -23071,8 +23123,7 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/micro-ftch/-/micro-ftch-0.3.1.tgz", "integrity": "sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/micromatch": { "version": "4.0.8", @@ -23635,7 +23686,6 @@ "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", "license": "MIT", - "peer": true, "bin": { "node-gyp-build": "bin.js", "node-gyp-build-optional": "optional.js", @@ -24331,7 +24381,6 @@ "resolved": "https://registry.npmjs.org/obj-multiplex/-/obj-multiplex-1.0.0.tgz", "integrity": "sha512-0GNJAOsHoBHeNTvl5Vt6IWnpUEcc3uSRxzBri7EDyIcMgYvnY2JL2qdeV5zTMjWQX5OHcD5amcW2HFfDh0gjIA==", "license": "ISC", - "peer": true, "dependencies": { "end-of-stream": "^1.4.0", "once": "^1.4.0", @@ -24342,15 +24391,13 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/obj-multiplex/node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "license": "MIT", - "peer": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -24366,7 +24413,6 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "license": "MIT", - "peer": true, "dependencies": { "safe-buffer": "~5.1.0" } @@ -25152,7 +25198,6 @@ "resolved": "https://registry.npmjs.org/pony-cause/-/pony-cause-2.1.11.tgz", "integrity": "sha512-M7LhCsdNbNgiLYiP4WjsfLUuFmCfnjdF6jKe2R9NKl4WFN+HZPGHJZ9lnLP7f9ZnKe3U9nuWD0szirmj+migUg==", "license": "0BSD", - "peer": true, "engines": { "node": ">=12.0.0" } @@ -25160,7 +25205,6 @@ "node_modules/possible-typed-array-names": { "version": "1.0.0", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4" } @@ -26229,7 +26273,6 @@ "node_modules/pump": { "version": "3.0.2", "license": "MIT", - "peer": true, "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -27770,7 +27813,6 @@ "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", "license": "MIT", - "peer": true, "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -28039,7 +28081,6 @@ "node_modules/set-function-length": { "version": "1.2.2", "license": "MIT", - "peer": true, "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -28081,7 +28122,6 @@ "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "license": "(MIT AND BSD-3-Clause)", - "peer": true, "dependencies": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -28248,7 +28288,6 @@ "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", "license": "MIT", - "peer": true, "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.2", @@ -28264,7 +28303,6 @@ "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", "license": "MIT", - "peer": true, "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1" @@ -28849,7 +28887,6 @@ "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-1.0.4.tgz", "integrity": "sha512-7JpaAoX2NGyoFlI9NBh66BQXGONc+uE+MRS5i2iOBKuS4e+ccgMDjATgZldkah+33DakBxDHiss9kvUcGAO8UQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=14.0.0" } @@ -30217,7 +30254,6 @@ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==", "license": "MIT", - "peer": true, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } @@ -30228,7 +30264,6 @@ "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "node-gyp-build": "^4.3.0" }, @@ -30400,14 +30435,13 @@ } }, "node_modules/wagmi": { - "version": "2.14.9", - "resolved": "https://registry.npmjs.org/wagmi/-/wagmi-2.14.9.tgz", - "integrity": "sha512-nDJ5hwPaiVpn/8Bi82m5K4BCqDiOSnOV976p/jKXt0svQABGdAxUxej0UgDRoVlrp+NutmejN+SyQKmhV477/A==", + "version": "2.14.11", + "resolved": "https://registry.npmjs.org/wagmi/-/wagmi-2.14.11.tgz", + "integrity": "sha512-Qj79cq+9MAcnKict9QLo60Lc4S2IXVVE94HBwCmczDrFtoM31NxfX4uQP73Elj2fV9lXH4/dw3jlb8eDhlm6iQ==", "license": "MIT", - "peer": true, "dependencies": { - "@wagmi/connectors": "5.7.5", - "@wagmi/core": "2.16.3", + "@wagmi/connectors": "5.7.7", + "@wagmi/core": "2.16.4", "use-sync-external-store": "1.4.0" }, "funding": { @@ -30460,8 +30494,7 @@ "version": "0.10.0", "resolved": "https://registry.npmjs.org/webextension-polyfill/-/webextension-polyfill-0.10.0.tgz", "integrity": "sha512-c5s35LgVa5tFaHhrZDnr3FpQpjj1BB+RXhLTYUxGqBVN460HkbM8TBtEqdXWbpTKfzwCcjAZVF7zXCYSKtcp9g==", - "license": "MPL-2.0", - "peer": true + "license": "MPL-2.0" }, "node_modules/webidl-conversions": { "version": "7.0.0", @@ -30740,7 +30773,6 @@ "node_modules/which-typed-array": { "version": "1.1.15", "license": "MIT", - "peer": true, "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.7", @@ -31041,7 +31073,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", - "peer": true, "engines": { "node": ">=0.4.0" } @@ -31331,9 +31362,14 @@ "@ethersproject/bytes": "^5.7.0", "@ethersproject/hash": "^5.7.0", "@noble/hashes": "^1.7.1", + "@onflow/fcl-wc": "^5.5.4", "@onflow/rlp": "^1.2.3", + "@walletconnect/ethereum-provider": "^2.18.0", "@walletconnect/jsonrpc-http-connection": "^1.0.8", - "@walletconnect/jsonrpc-provider": "^1.0.14" + "@walletconnect/jsonrpc-provider": "^1.0.14", + "@walletconnect/types": "^2.18.0", + "@walletconnect/universal-provider": "^2.18.0", + "@walletconnect/utils": "^2.18.0" }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", @@ -31350,6 +31386,45 @@ "@onflow/fcl": "^1.13.4" } }, + "packages/fcl-ethereum-provider/node_modules/@walletconnect/ethereum-provider": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/@walletconnect/ethereum-provider/-/ethereum-provider-2.18.0.tgz", + "integrity": "sha512-YExNYP/z1qNmkLwMutqVxl/rrGX7RS5PCEOVLYCiGsV+vDB9Z6iHP2FgRWh8kZvnmBv5IVvPnQdE7rTzWeUl1Q==", + "license": "Apache-2.0", + "dependencies": { + "@walletconnect/jsonrpc-http-connection": "1.0.8", + "@walletconnect/jsonrpc-provider": "1.0.14", + "@walletconnect/jsonrpc-types": "1.0.4", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/keyvaluestorage": "1.1.1", + "@walletconnect/modal": "2.7.0", + "@walletconnect/sign-client": "2.18.0", + "@walletconnect/types": "2.18.0", + "@walletconnect/universal-provider": "2.18.0", + "@walletconnect/utils": "2.18.0", + "events": "3.3.0" + } + }, + "packages/fcl-ethereum-provider/node_modules/@walletconnect/universal-provider": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/@walletconnect/universal-provider/-/universal-provider-2.18.0.tgz", + "integrity": "sha512-zF/e1NAipLqYjNNgM+XZTchh94efaxciBmgcDOaLznS97R7S/1bYj5okQCAEDKx9RALhEKqZKoyo9jwn4p3BVA==", + "license": "Apache-2.0", + "dependencies": { + "@walletconnect/events": "1.0.1", + "@walletconnect/jsonrpc-http-connection": "1.0.8", + "@walletconnect/jsonrpc-provider": "1.0.14", + "@walletconnect/jsonrpc-types": "1.0.4", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/keyvaluestorage": "1.1.1", + "@walletconnect/logger": "2.1.2", + "@walletconnect/sign-client": "2.18.0", + "@walletconnect/types": "2.18.0", + "@walletconnect/utils": "2.18.0", + "events": "3.3.0", + "lodash": "4.17.21" + } + }, "packages/fcl-rainbowkit-adapter": { "name": "@onflow/fcl-rainbowkit-adapter", "version": "0.0.0", @@ -31364,7 +31439,9 @@ "@wagmi/core": "^2.16.3", "@walletconnect/jsonrpc-http-connection": "^1.0.8", "@walletconnect/jsonrpc-provider": "^1.0.14", - "viem": "^2.22.21" + "mipd": "^0.0.7", + "viem": "^2.22.21", + "wagmi": "^2.14.11" }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", @@ -31655,6 +31732,10 @@ "@onflow/rlp": "^1.2.3", "@walletconnect/jsonrpc-http-connection": "^1.0.8", "@walletconnect/jsonrpc-provider": "^1.0.14", + "@walletconnect/modal": "^2.7.0", + "@walletconnect/types": "^2.18.0", + "@walletconnect/universal-provider": "^2.18.0", + "@walletconnect/utils": "^2.18.0", "viem": "^2.22.21" }, "devDependencies": { @@ -31673,6 +31754,26 @@ "@wagmi/core": "^2.16.3" } }, + "packages/fcl-wagmi-adapter/node_modules/@walletconnect/universal-provider": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/@walletconnect/universal-provider/-/universal-provider-2.18.0.tgz", + "integrity": "sha512-zF/e1NAipLqYjNNgM+XZTchh94efaxciBmgcDOaLznS97R7S/1bYj5okQCAEDKx9RALhEKqZKoyo9jwn4p3BVA==", + "license": "Apache-2.0", + "dependencies": { + "@walletconnect/events": "1.0.1", + "@walletconnect/jsonrpc-http-connection": "1.0.8", + "@walletconnect/jsonrpc-provider": "1.0.14", + "@walletconnect/jsonrpc-types": "1.0.4", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/keyvaluestorage": "1.1.1", + "@walletconnect/logger": "2.1.2", + "@walletconnect/sign-client": "2.18.0", + "@walletconnect/types": "2.18.0", + "@walletconnect/utils": "2.18.0", + "events": "3.3.0", + "lodash": "4.17.21" + } + }, "packages/fcl-wc": { "name": "@onflow/fcl-wc", "version": "5.5.4", @@ -31682,10 +31783,12 @@ "@onflow/config": "1.5.1", "@onflow/util-invariant": "1.2.4", "@onflow/util-logger": "1.3.3", + "@walletconnect/ethereum-provider": "^2.18.0", "@walletconnect/modal": "^2.7.0", "@walletconnect/modal-core": "^2.6.2", "@walletconnect/sign-client": "^2.17.1", "@walletconnect/types": "^2.8.1", + "@walletconnect/universal-provider": "^2.18.0", "@walletconnect/utils": "^2.8.1", "postcss-cli": "^11.0.0", "preact": "^10.24.3", @@ -31706,6 +31809,45 @@ "@onflow/fcl-core": "1.13.4" } }, + "packages/fcl-wc/node_modules/@walletconnect/ethereum-provider": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/@walletconnect/ethereum-provider/-/ethereum-provider-2.18.0.tgz", + "integrity": "sha512-YExNYP/z1qNmkLwMutqVxl/rrGX7RS5PCEOVLYCiGsV+vDB9Z6iHP2FgRWh8kZvnmBv5IVvPnQdE7rTzWeUl1Q==", + "license": "Apache-2.0", + "dependencies": { + "@walletconnect/jsonrpc-http-connection": "1.0.8", + "@walletconnect/jsonrpc-provider": "1.0.14", + "@walletconnect/jsonrpc-types": "1.0.4", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/keyvaluestorage": "1.1.1", + "@walletconnect/modal": "2.7.0", + "@walletconnect/sign-client": "2.18.0", + "@walletconnect/types": "2.18.0", + "@walletconnect/universal-provider": "2.18.0", + "@walletconnect/utils": "2.18.0", + "events": "3.3.0" + } + }, + "packages/fcl-wc/node_modules/@walletconnect/universal-provider": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/@walletconnect/universal-provider/-/universal-provider-2.18.0.tgz", + "integrity": "sha512-zF/e1NAipLqYjNNgM+XZTchh94efaxciBmgcDOaLznS97R7S/1bYj5okQCAEDKx9RALhEKqZKoyo9jwn4p3BVA==", + "license": "Apache-2.0", + "dependencies": { + "@walletconnect/events": "1.0.1", + "@walletconnect/jsonrpc-http-connection": "1.0.8", + "@walletconnect/jsonrpc-provider": "1.0.14", + "@walletconnect/jsonrpc-types": "1.0.4", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/keyvaluestorage": "1.1.1", + "@walletconnect/logger": "2.1.2", + "@walletconnect/sign-client": "2.18.0", + "@walletconnect/types": "2.18.0", + "@walletconnect/utils": "2.18.0", + "events": "3.3.0", + "lodash": "4.17.21" + } + }, "packages/fcl/node_modules/cross-fetch": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", diff --git a/packages/fcl-core/src/current-user/index.js b/packages/fcl-core/src/current-user/index.js index 58248ccbc..32d59d048 100644 --- a/packages/fcl-core/src/current-user/index.js +++ b/packages/fcl-core/src/current-user/index.js @@ -165,9 +165,10 @@ const getAuthenticate = * @param {object} [opts] - Options * @param {object} [opts.service] - Optional service to use for authentication * @param {boolean} [opts.redir] - Optional redirect flag + * @param {boolean} [opts.forceReauth] - Optional force re-authentication flag * @returns */ - async ({service, redir = false} = {}) => { + async ({service, redir = false, forceReauth = false} = {}) => { if ( service && !service?.provider?.is_installed && @@ -185,7 +186,7 @@ const getAuthenticate = const refreshService = serviceOfType(user.services, "authn-refresh") let accountProofData - if (user.loggedIn) { + if (user.loggedIn && !forceReauth) { if (refreshService) { try { const response = await execService({ diff --git a/packages/fcl-ethereum-provider/package.json b/packages/fcl-ethereum-provider/package.json index 3cd7ea871..d690cd37f 100644 --- a/packages/fcl-ethereum-provider/package.json +++ b/packages/fcl-ethereum-provider/package.json @@ -40,9 +40,14 @@ "@ethersproject/bytes": "^5.7.0", "@ethersproject/hash": "^5.7.0", "@noble/hashes": "^1.7.1", + "@onflow/fcl-wc": "^5.5.4", "@onflow/rlp": "^1.2.3", + "@walletconnect/ethereum-provider": "^2.18.0", "@walletconnect/jsonrpc-http-connection": "^1.0.8", - "@walletconnect/jsonrpc-provider": "^1.0.14" + "@walletconnect/jsonrpc-provider": "^1.0.14", + "@walletconnect/types": "^2.18.0", + "@walletconnect/universal-provider": "^2.18.0", + "@walletconnect/utils": "^2.18.0" }, "peerDependencies": { "@onflow/fcl": "^1.13.4" diff --git a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts index ee9895ea9..6a2cc1051 100644 --- a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts +++ b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts @@ -70,18 +70,18 @@ export class AccountManager { $user .pipe( - // Only listen bind to users matching the current authz service + // Only listen bind to users matching the current authn service map(snapshot => { const addr = snapshot?.addr || null if (!addr) { return null } - const authzService = snapshot?.services?.find( - service => service.type === "authz" + const authnService = snapshot?.services?.find( + service => service.type === "authn" ) - const matchingAuthzService = authzService?.uid === this.service?.uid - return matchingAuthzService ? addr : null + const matchingAuthnService = authnService?.uid === this.service?.uid + return matchingAuthnService ? addr : null }), distinctUntilChanged(), switchMap(addr => @@ -110,7 +110,7 @@ export class AccountManager { } public async authenticate(): Promise { - await this.user.authenticate({service: this.service}) + await this.user.authenticate({service: this.service, forceReauth: true}) return this.getAccounts() } diff --git a/packages/fcl-ethereum-provider/src/create-provider.ts b/packages/fcl-ethereum-provider/src/create-provider.ts index 1b1fd3ea4..d8eab92f8 100644 --- a/packages/fcl-ethereum-provider/src/create-provider.ts +++ b/packages/fcl-ethereum-provider/src/create-provider.ts @@ -8,7 +8,14 @@ import {AccountManager} from "./accounts/account-manager" import {FLOW_CHAINS} from "./constants" import {Gateway} from "./gateway/gateway" import {NetworkManager} from "./network/network-manager" -import {Subject} from "./util/observable" + +export type FclProviderConfig = { + user: typeof fcl.currentUser + config: typeof fcl.config + service?: Service + gateway?: string + rpcUrls?: {[chainId: string]: number} +} /** * Create a new FCL Ethereum provider @@ -29,12 +36,7 @@ import {Subject} from "./util/observable" * }) * ``` */ -export function createProvider(config: { - user: typeof fcl.currentUser - config: typeof fcl.config - service?: Service - rpcUrls?: {[chainId: string]: number} -}): Eip1193Provider { +export function createProvider(config: FclProviderConfig): Eip1193Provider { const defaultRpcUrls = Object.values(FLOW_CHAINS).reduce( (acc, chain) => { acc[chain.eip155ChainId] = chain.publicRpcUrl diff --git a/packages/fcl-ethereum-provider/src/index.ts b/packages/fcl-ethereum-provider/src/index.ts index 746b52b0a..30fb17946 100644 --- a/packages/fcl-ethereum-provider/src/index.ts +++ b/packages/fcl-ethereum-provider/src/index.ts @@ -1 +1,2 @@ export {createProvider} from "./create-provider" +export {WalletConnectEthereumProvider} from "./wc-provider" diff --git a/packages/fcl-ethereum-provider/src/wc-provider.ts b/packages/fcl-ethereum-provider/src/wc-provider.ts new file mode 100644 index 000000000..9bbbad14f --- /dev/null +++ b/packages/fcl-ethereum-provider/src/wc-provider.ts @@ -0,0 +1,221 @@ +import { + EthereumProvider, + EthereumProviderOptions, + OPTIONAL_EVENTS, + OPTIONAL_METHODS, + REQUIRED_EVENTS, + REQUIRED_METHODS, +} from "@walletconnect/ethereum-provider" +import { + NamespaceConfig, + UniversalProvider, +} from "@walletconnect/universal-provider" +import {SessionTypes} from "@walletconnect/types" +import {FLOW_CHAINS, FlowNetwork} from "./constants" +import {formatChainId} from "./util/eth" +import {getAccountsFromNamespaces} from "@walletconnect/utils" +import {FLOW_METHODS} from "@onflow/fcl-wc" +import * as fcl from "@onflow/fcl" +import {Service} from "@onflow/typedefs" + +const BASE_WC_SERVICE = ( + externalProvider: InstanceType +) => + ({ + f_type: "Service", + f_vsn: "1.0.0", + type: "authn", + method: "WC/RPC", + uid: "cross-vm-walletconnect#authn", + endpoint: "flow_authn", + optIn: true, + provider: { + address: null, + name: "WalletConnect", + icon: "https://avatars.githubusercontent.com/u/37784886", + description: "WalletConnect Base Service", + website: "https://walletconnect.com", + color: null, + supportEmail: null, + }, + params: { + externalProvider, + }, + }) as unknown as Service + +export class WalletConnectEthereumProvider extends EthereumProvider { + static async init( + opts: EthereumProviderOptions + ): Promise { + const provider = new WalletConnectEthereumProvider() + await provider.initialize(opts) + + // Refresh the FCL user to align with the WalletConnect session + async function refreshFclUser() { + const fclUser = fcl.currentUser() + const wcService = BASE_WC_SERVICE(provider.signer) + const snapshot = await fclUser.snapshot() + + // Find the authentication service from the current FCL user snapshot + const authnService = snapshot?.services.find( + service => service.type === "authn" + ) + + // If there’s no auth service or the auth service + if (authnService && authnService.uid !== wcService.uid) { + // Another FCL user is already authenticated, we need to unauthenticate it + if (provider.signer.session) { + await fclUser.authenticate({service: wcService, forceReauth: true}) + } + } else { + // Determine the external provider's topic from the auth service params + const externalProvider = authnService?.params?.externalProvider as + | string + | InstanceType + | undefined + const externalProviderTopic = + typeof externalProvider === "string" + ? externalProvider + : (externalProvider?.session?.topic ?? null) + + // If the provider is already connected with a matching session, re-authenticate the user + if ( + provider.signer.session && + (externalProviderTopic == null || + externalProviderTopic === provider.signer.session.topic) + ) { + await fclUser.authenticate({service: wcService, forceReauth: true}) + } else if (!provider.signer.session) { + // If no session is set but FCL is still authenticated, unauthenticate the user + await fclUser.unauthenticate() + } + } + } + + // Set up event listeners regardless of the current authentication state + provider.on("connect", async () => { + try { + await refreshFclUser() + } catch (error) { + console.error("Error during authentication on connect:", error) + } + }) + + provider.on("disconnect", async () => { + try { + await refreshFclUser() + } catch (error) { + console.error("Error during unauthentication on disconnect:", error) + } + }) + + return provider + } + + async connect( + opts?: Parameters["connect"]>[0] + ) { + if (!this.signer.client) { + throw new Error("Provider not initialized. Call init() first") + } + + this.loadConnectOpts(opts) + + const chains = new Set(opts?.chains ?? []) + const optionalChains = new Set(opts?.optionalChains ?? []) + const chainIds = Array.from(chains).concat(Array.from(optionalChains)) + + const flowNetwork = Object.entries(FLOW_CHAINS).find( + ([, {eip155ChainId}]) => { + if (chainIds.includes(eip155ChainId)) { + return true + } + return false + } + )?.[0] + if (!flowNetwork) { + throw new Error( + `Unsupported chainId: ${chainIds.join(", ")}, expected one of ${Object.values( + FLOW_CHAINS + ) + .map(({eip155ChainId}) => eip155ChainId) + .join(", ")}` + ) + } + + const {required, optional} = buildNamespaces(flowNetwork as FlowNetwork) + try { + const session = await new Promise( + async (resolve, reject) => { + if (this.rpc.showQrModal) { + this.modal?.subscribeModal((state: {open: boolean}) => { + // the modal was closed so reject the promise + if (!state.open && !this.signer.session) { + this.signer.abortPairingAttempt() + reject(new Error("Connection request reset. Please try again.")) + } + }) + } + await this.signer + .connect({ + namespaces: required, + optionalNamespaces: optional, + pairingTopic: opts?.pairingTopic, + }) + .then((session?: SessionTypes.Struct) => { + resolve(session) + }) + .catch((error: unknown) => { + var newErr = new Error("Failed to connect") + if (error instanceof Error) + newErr.stack += "\nCaused by: " + error.stack + throw newErr + }) + } + ) + if (!session) return + + const accounts = getAccountsFromNamespaces(session.namespaces, [ + this.namespace, + ]) + // if no required chains are set, use the approved accounts to fetch chainIds + this.setChainIds(this.rpc.chains.length ? this.rpc.chains : accounts) + this.setAccounts(accounts) + this.events.emit("connect", {chainId: formatChainId(this.chainId)}) + } catch (error) { + this.signer.logger.error(error) + throw error + } finally { + if (this.modal) this.modal.closeModal() + } + } +} + +function buildNamespaces(network: FlowNetwork): { + required: NamespaceConfig + optional: NamespaceConfig +} { + const {eip155ChainId} = FLOW_CHAINS[network] + + return { + required: { + eip155: { + methods: REQUIRED_METHODS, + chains: [`eip155:${eip155ChainId}`], + events: REQUIRED_EVENTS, + }, + }, + optional: { + eip155: { + methods: OPTIONAL_METHODS, + chains: [`eip155:${eip155ChainId}`], + events: OPTIONAL_EVENTS, + }, + flow: { + methods: Object.values(FLOW_METHODS), + events: ["chainChanged", "accountsChanged"], + chains: [`flow:${network}`], + }, + }, + } +} diff --git a/packages/fcl-rainbowkit-adapter/package.json b/packages/fcl-rainbowkit-adapter/package.json index 8998b5a3a..ef4ea572a 100644 --- a/packages/fcl-rainbowkit-adapter/package.json +++ b/packages/fcl-rainbowkit-adapter/package.json @@ -45,7 +45,9 @@ "@wagmi/core": "^2.16.3", "@walletconnect/jsonrpc-http-connection": "^1.0.8", "@walletconnect/jsonrpc-provider": "^1.0.14", - "viem": "^2.22.21" + "mipd": "^0.0.7", + "viem": "^2.22.21", + "wagmi": "^2.14.11" }, "peerDependencies": { "@onflow/fcl": "^1.13.4", diff --git a/packages/fcl-rainbowkit-adapter/src/create-connector.ts b/packages/fcl-rainbowkit-adapter/src/create-connector.ts index 00ad8b1bc..96bcad645 100644 --- a/packages/fcl-rainbowkit-adapter/src/create-connector.ts +++ b/packages/fcl-rainbowkit-adapter/src/create-connector.ts @@ -1,8 +1,11 @@ import {fclWagmiAdapter} from "@onflow/fcl-wagmi-adapter" -import {Wallet} from "@rainbow-me/rainbowkit" +import {RainbowKitWalletConnectParameters, Wallet} from "@rainbow-me/rainbowkit" import {createConnector} from "@wagmi/core" -type FclConnectorOptions = Parameters[0] +type FclConnectorOptions = Parameters[0] & { + supportsWc?: boolean + walletConnectParams?: RainbowKitWalletConnectParameters +} type DefaultWalletOptions = { projectId: string @@ -14,19 +17,46 @@ const FALLBACK_ICON = export const createFclConnector = (options: FclConnectorOptions) => { const uid = options.service?.uid const name = options.service?.provider?.name - const iconUrl = options.service?.provider?.icon! + const iconUrl = options.service?.provider?.icon ?? "" + const rdns = (options.service?.provider as any)?.rdns as string | undefined + + const getUri = (uri: string) => { + return uri + } return ({projectId}: DefaultWalletOptions): Wallet => ({ id: uid ? `fcl-${uid}` : "fcl", name: name || "Cadence Wallet", iconUrl: iconUrl || FALLBACK_ICON, iconBackground: "#FFFFFF", + installed: true, + downloadUrls: { + browserExtension: + "https://chrome.google.com/webstore/detail/flow-wallet/hpclkefagolihohboafpheddmmgdffjm", + mobile: "https://core.flow.com", + }, + // Do not list RDNS here since Rainbowkit will discard the wallet + // when conflicting with an injected wallet + rdns: undefined, createConnector: walletDetails => { - const connector = fclWagmiAdapter(options) - return createConnector(config => ({ - ...connector(config), + // TODO, we need to check whether the wallet is installed + // and use the WalletConnect connector if it is not installed + const newDetails = { ...walletDetails, - })) + rkDetails: { + ...walletDetails.rkDetails, + groupIndex: -1, + groupName: "Installed", + }, + } + + const connector = fclWagmiAdapter(options) + return createConnector(config => { + return { + ...connector(config), + ...newDetails, + } + }) }, }) } diff --git a/packages/fcl-rainbowkit-adapter/src/get-wc-connector.ts b/packages/fcl-rainbowkit-adapter/src/get-wc-connector.ts new file mode 100644 index 000000000..ff1df663e --- /dev/null +++ b/packages/fcl-rainbowkit-adapter/src/get-wc-connector.ts @@ -0,0 +1,153 @@ +/* + * This file is mostly a copy of Rainbowkit's internal get wc connector utility, + * meant to cache and reuse walletConnect connector instances. + * + * The caveat is that we are substituting the original connector with a multi-scoped + * version that is able to be used in a cross-VM context. + * + * See: https://github.com/rainbow-me/rainbowkit/blob/0c9679812123e17b45e1330d5e3b665b48c82864/packages/rainbowkit/src/wallets/getWalletConnectConnector.ts + */ + +/*! + * MIT License + * + * Copyright (c) 2024 Rainbow + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import {createConnector} from "wagmi" +import type {CreateConnectorFn} from "wagmi" +import {walletConnect, WalletConnectParameters} from "@onflow/fcl-wagmi-adapter" +import type { + RainbowKitWalletConnectParameters, + WalletDetailsParams, +} from "@rainbow-me/rainbowkit" + +type RainbowKitDetails = any +type CreateConnector = (walletDetails: WalletDetailsParams) => CreateConnectorFn + +interface GetWalletConnectConnectorParams { + projectId: string + walletConnectParameters?: RainbowKitWalletConnectParameters +} + +interface CreateWalletConnectConnectorParams { + projectId: string + walletDetails: WalletDetailsParams + walletConnectParameters?: RainbowKitWalletConnectParameters +} + +interface GetOrCreateWalletConnectInstanceParams { + projectId: string + walletConnectParameters?: RainbowKitWalletConnectParameters + rkDetailsShowQrModal?: RainbowKitDetails["showQrModal"] +} + +const walletConnectInstances = new Map< + string, + ReturnType +>() + +// Function to get or create a walletConnect instance +const getOrCreateWalletConnectInstance = ({ + projectId, + walletConnectParameters, + rkDetailsShowQrModal, +}: GetOrCreateWalletConnectInstanceParams): ReturnType< + typeof walletConnect +> => { + let config: WalletConnectParameters = { + ...(walletConnectParameters ? walletConnectParameters : {}), + projectId, + showQrModal: false, // Required. Otherwise WalletConnect modal (Web3Modal) will popup during time of connection for a wallet + } as any + + // `rkDetailsShowQrModal` should always be `true` + if (rkDetailsShowQrModal) { + config = {...config, showQrModal: true} + } + + const serializedConfig = JSON.stringify( + Object.keys(config) + .sort() + .reduce((obj: any, key) => { + obj[key] = config[key as keyof WalletConnectParameters] + return obj + }, {} as WalletConnectParameters) + ) + + const sharedWalletConnector = walletConnectInstances.get(serializedConfig) + + if (sharedWalletConnector) { + return sharedWalletConnector + } + + // Create a new walletConnect instance and store it + const newWalletConnectInstance = walletConnect(config) + + walletConnectInstances.set(serializedConfig, newWalletConnectInstance) + + return newWalletConnectInstance +} + +// Creates a WalletConnect connector with the given project ID and additional options. +function createWalletConnectConnector({ + projectId, + walletDetails, + walletConnectParameters, +}: CreateWalletConnectConnectorParams): CreateConnectorFn { + // Create and configure the WalletConnect connector with project ID and options. + return createConnector(config => ({ + ...getOrCreateWalletConnectInstance({ + projectId, + walletConnectParameters, + // Used in `connectorsForWallets` to add another + // walletConnect wallet into rainbowkit with modal popup option + rkDetailsShowQrModal: walletDetails.rkDetails.showQrModal, + })(config), + ...walletDetails, + })) +} + +// Factory function to obtain a configured WalletConnect connector. +export function getWalletConnectConnector({ + projectId, + walletConnectParameters, +}: GetWalletConnectConnectorParams): CreateConnector { + // We use this projectId in place of YOUR_PROJECT_ID for our examples. + // This allows us our examples and templates to be functional with WalletConnect v2. + // We warn developers against using this projectId in their dApp in production. + const exampleProjectId = "21fef48091f12692cad574a6f7753643" + + if (!projectId || projectId === "") { + throw new Error( + "No projectId found. Every dApp must now provide a WalletConnect Cloud projectId to enable WalletConnect v2 https://www.rainbowkit.com/docs/installation#configure" + ) + } + + if (projectId === "YOUR_PROJECT_ID") { + projectId = exampleProjectId + } + + // Return a function that merges additional wallet details with `CreateConnectorFn`. + return (walletDetails: WalletDetailsParams) => + createWalletConnectConnector({ + projectId, + walletDetails, + walletConnectParameters, + }) +} diff --git a/packages/fcl-rainbowkit-adapter/src/index.ts b/packages/fcl-rainbowkit-adapter/src/index.ts index 7c9d28a7c..c7607699a 100644 --- a/packages/fcl-rainbowkit-adapter/src/index.ts +++ b/packages/fcl-rainbowkit-adapter/src/index.ts @@ -1,2 +1,6 @@ +export {flowWallet} from "./wallets/flow-wallet" export {createFclConnector} from "./create-connector" -export {flowWallet} from "./flow-wallet" +export { + type WalletConnectWalletOptions, + walletConnectWallet, +} from "./wallets/wc-wallet" diff --git a/packages/fcl-rainbowkit-adapter/src/flow-wallet.ts b/packages/fcl-rainbowkit-adapter/src/wallets/flow-wallet.ts similarity index 93% rename from packages/fcl-rainbowkit-adapter/src/flow-wallet.ts rename to packages/fcl-rainbowkit-adapter/src/wallets/flow-wallet.ts index 55c544476..64ed23a92 100644 --- a/packages/fcl-rainbowkit-adapter/src/flow-wallet.ts +++ b/packages/fcl-rainbowkit-adapter/src/wallets/flow-wallet.ts @@ -1,5 +1,5 @@ import {Service} from "@onflow/typedefs" -import {createFclConnector} from "./create-connector" +import {createFclConnector} from "../create-connector" import * as fcl from "@onflow/fcl" /** @@ -18,7 +18,7 @@ export const flowWallet = (params: { f_type: "Service", f_vsn: "1.0.0", type: "authn", - uid: "Flow Wallet", + uid: "fcw#authn", endpoint: "chrome-extension://hpclkefagolihohboafpheddmmgdffjm/popup.html", method: "EXT/RPC", diff --git a/packages/fcl-rainbowkit-adapter/src/wallets/wc-wallet.ts b/packages/fcl-rainbowkit-adapter/src/wallets/wc-wallet.ts new file mode 100644 index 000000000..64680ed33 --- /dev/null +++ b/packages/fcl-rainbowkit-adapter/src/wallets/wc-wallet.ts @@ -0,0 +1,63 @@ +/* + * This file is mostly a copy of Rainbowkit's internal WalletConnect wallet implementation. + * The purpose is to substitute the original connector with a multi-scoped vesion that is + * able to be used in a cross-VM context. + * + * See: https://github.com/rainbow-me/rainbowkit/blob/0c9679812123e17b45e1330d5e3b665b48c82864/packages/rainbowkit/src/wallets/walletConnectors/walletConnectWallet/walletConnectWallet.ts#L9 + */ + +/*! + * MIT License + * + * Copyright (c) 2024 Rainbow + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software + * and associated documentation files (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or + * substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT + * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import { + type RainbowKitWalletConnectParameters, + type Wallet, +} from "@rainbow-me/rainbowkit" +import {getWalletConnectConnector} from "../get-wc-connector" + +const WALLETCONNECT_ICON = `data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjgiIGhlaWdodD0iMjgiIHZpZXdCb3g9IjAgMCAyOCAyOCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjI4IiBoZWlnaHQ9IjI4IiBmaWxsPSIjM0I5OUZDIi8+CjxwYXRoIGQ9Ik04LjM4OTY5IDEwLjM3MzlDMTEuNDg4MiA3LjI3NTM4IDE2LjUxMTggNy4yNzUzOCAxOS42MTAzIDEwLjM3MzlMMTkuOTgzMiAxMC43NDY4QzIwLjEzODIgMTAuOTAxNyAyMC4xMzgyIDExLjE1MjkgMTkuOTgzMiAxMS4zMDc4TDE4LjcwNzYgMTIuNTgzNUMxOC42MzAxIDEyLjY2MDkgMTguNTA0NSAxMi42NjA5IDE4LjQyNzEgMTIuNTgzNUwxNy45MTM5IDEyLjA3MDNDMTUuNzUyMyA5LjkwODcgMTIuMjQ3NyA5LjkwODcgMTAuMDg2MSAxMi4wNzAzTDkuNTM2NTUgMTIuNjE5OEM5LjQ1OTA5IDEyLjY5NzMgOS4zMzM1IDEyLjY5NzMgOS4yNTYwNCAxMi42MTk4TDcuOTgwMzkgMTEuMzQ0MkM3LjgyNTQ3IDExLjE4OTMgNy44MjU0NyAxMC45MzgxIDcuOTgwMzkgMTAuNzgzMkw4LjM4OTY5IDEwLjM3MzlaTTIyLjI0ODUgMTMuMDEyTDIzLjM4MzggMTQuMTQ3NEMyMy41Mzg3IDE0LjMwMjMgMjMuNTM4NyAxNC41NTM1IDIzLjM4MzggMTQuNzA4NEwxOC4yNjQ1IDE5LjgyNzdDMTguMTA5NiAxOS45ODI3IDE3Ljg1ODQgMTkuOTgyNyAxNy43MDM1IDE5LjgyNzdDMTcuNzAzNSAxOS44Mjc3IDE3LjcwMzUgMTkuODI3NyAxNy43MDM1IDE5LjgyNzdMMTQuMDcwMiAxNi4xOTQ0QzE0LjAzMTQgMTYuMTU1NyAxMy45Njg2IDE2LjE1NTcgMTMuOTI5OSAxNi4xOTQ0QzEzLjkyOTkgMTYuMTk0NCAxMy45Mjk5IDE2LjE5NDQgMTMuOTI5OSAxNi4xOTQ0TDEwLjI5NjYgMTkuODI3N0MxMC4xNDE3IDE5Ljk4MjcgOS44OTA1MyAxOS45ODI3IDkuNzM1NjEgMTkuODI3OEM5LjczNTYgMTkuODI3OCA5LjczNTYgMTkuODI3NyA5LjczNTYgMTkuODI3N0w0LjYxNjE5IDE0LjcwODNDNC40NjEyNyAxNC41NTM0IDQuNDYxMjcgMTQuMzAyMiA0LjYxNjE5IDE0LjE0NzNMNS43NTE1MiAxMy4wMTJDNS45MDY0NSAxMi44NTcgNi4xNTc2MyAxMi44NTcgNi4zMTI1NSAxMy4wMTJMOS45NDU5NSAxNi42NDU0QzkuOTg0NjggMTYuNjg0MSAxMC4wNDc1IDE2LjY4NDEgMTAuMDg2MiAxNi42NDU0QzEwLjA4NjIgMTYuNjQ1NCAxMC4wODYyIDE2LjY0NTQgMTAuMDg2MiAxNi42NDU0TDEzLjcxOTQgMTMuMDEyQzEzLjg3NDMgMTIuODU3IDE0LjEyNTUgMTIuODU3IDE0LjI4MDUgMTMuMDEyQzE0LjI4MDUgMTMuMDEyIDE0LjI4MDUgMTMuMDEyIDE0LjI4MDUgMTMuMDEyTDE3LjkxMzkgMTYuNjQ1NEMxNy45NTI2IDE2LjY4NDEgMTguMDE1NCAxNi42ODQxIDE4LjA1NDEgMTYuNjQ1NEwyMS42ODc0IDEzLjAxMkMyMS44NDI0IDEyLjg1NzEgMjIuMDkzNiAxMi44NTcxIDIyLjI0ODUgMTMuMDEyWiIgZmlsbD0id2hpdGUiLz4KPC9zdmc+Cg==` + +export interface WalletConnectWalletOptions { + projectId: string + options?: RainbowKitWalletConnectParameters +} + +export const walletConnectWallet = ({ + projectId, + options, +}: WalletConnectWalletOptions): Wallet => { + const getUri = (uri: string) => { + return uri + } + + return { + id: "walletConnect", + name: "WalletConnect", + installed: undefined, + iconUrl: WALLETCONNECT_ICON, + iconBackground: "#3b99fc", + qrCode: {getUri}, + createConnector: getWalletConnectConnector({ + projectId, + walletConnectParameters: options, + }), + } +} diff --git a/packages/fcl-wagmi-adapter/package.json b/packages/fcl-wagmi-adapter/package.json index 27dcc5ce2..43022de41 100644 --- a/packages/fcl-wagmi-adapter/package.json +++ b/packages/fcl-wagmi-adapter/package.json @@ -43,6 +43,10 @@ "@onflow/rlp": "^1.2.3", "@walletconnect/jsonrpc-http-connection": "^1.0.8", "@walletconnect/jsonrpc-provider": "^1.0.14", + "@walletconnect/modal": "^2.7.0", + "@walletconnect/types": "^2.18.0", + "@walletconnect/universal-provider": "^2.18.0", + "@walletconnect/utils": "^2.18.0", "viem": "^2.22.21" }, "peerDependencies": { diff --git a/packages/fcl-wagmi-adapter/src/fcl-connector.ts b/packages/fcl-wagmi-adapter/src/fcl-connector.ts new file mode 100644 index 000000000..911a1f826 --- /dev/null +++ b/packages/fcl-wagmi-adapter/src/fcl-connector.ts @@ -0,0 +1,213 @@ +import { + ChainNotConfiguredError, + type Connector, + createConnector, +} from "@wagmi/core" +import { + type Address, + type ProviderConnectInfo, + ProviderDisconnectedError, + SwitchChainError, + getAddress, + numberToHex, +} from "viem" +import {createProvider} from "@onflow/fcl-ethereum-provider" + +type FclWagmiAdapterParams = Parameters[0] + +export function fclWagmiAdapter(params: FclWagmiAdapterParams) { + type Provider = ReturnType + type Properties = { + onConnect(connectInfo: ProviderConnectInfo): void + onDisplayUri(uri: string): void + } + let provider: Provider | undefined + + let accountsChanged: Connector["onAccountsChanged"] | undefined + let chainChanged: Connector["onChainChanged"] | undefined + let connect: Connector["onConnect"] | undefined + let disconnect: ((error: Error) => void) | undefined + + // Parse and validate service parameters + const id = params.service?.uid || "fcl" + const name = params.service?.provider?.name || "Cadence Wallet" + + // TODO: we need to surface this through FCL service configuration + const rdns = (params.service?.provider as any)?.rdns + + return createConnector(config => ({ + id: id, + name: name, + type: "fcl-wagmi-adapter", + rdns: rdns, + async setup() { + const provider = await this.getProvider() + + if (connect) provider.removeListener("connect", connect) + connect = this.onConnect.bind(this) + provider.on("connect", connect) + + // We shouldn't need to listen for `'accountsChanged'` here since the `'connect'` event should suffice (and wallet shouldn't be connected yet). + // Some wallets, like MetaMask, do not implement the `'connect'` event and overload `'accountsChanged'` instead. + if (!accountsChanged) { + accountsChanged = this.onAccountsChanged.bind(this) + provider.on("accountsChanged", accountsChanged) + } + }, + async connect({isReconnecting}: any = {}) { + const provider = await this.getProvider() + + let accounts: readonly Address[] + if (isReconnecting) { + accounts = await this.getAccounts() + } else { + accounts = ( + (await provider.request({ + method: "eth_requestAccounts", + })) as string[] + ).map(x => getAddress(x)) + } + + // Manage EIP-1193 event listeners + // https://eips.ethereum.org/EIPS/eip-1193#events + if (connect) provider.removeListener("connect", connect) + connect = this.onConnect.bind(this) + provider.on("connect", connect) + + if (accountsChanged) + provider.removeListener("accountsChanged", accountsChanged) + accountsChanged = this.onAccountsChanged.bind(this) + provider.on("accountsChanged", accountsChanged) + + if (chainChanged) provider.removeListener("chainChanged", chainChanged) + chainChanged = this.onChainChanged.bind(this) + provider.on("chainChanged", chainChanged) + + if (disconnect) provider.removeListener("disconnect", disconnect) + disconnect = (error: Error) => { + throw new ProviderDisconnectedError(error) + } + provider.on("disconnect", disconnect) + + return {accounts, chainId: await this.getChainId()} + }, + async disconnect() { + const provider = await this.getProvider() + + // Manage EIP-1193 event listeners + if (chainChanged) provider.removeListener("chainChanged", chainChanged) + chainChanged = undefined + + if (disconnect) provider.removeListener("disconnect", disconnect) + disconnect = undefined + + if (connect) provider.removeListener("connect", connect) + connect = this.onConnect.bind(this) + provider.on("connect", connect) + + await provider.disconnect() + }, + async getAccounts() { + const provider = await this.getProvider() + const accounts = (await provider.request({ + method: "eth_accounts", + })) as string[] + return accounts.map(x => getAddress(x)) + }, + async getChainId() { + const provider = await this.getProvider() + const chainId = await provider.request({method: "eth_chainId"}) + return Number(chainId) + }, + async getProvider() { + return provider ?? (provider = createProvider(params)) + }, + async isAuthorized() { + // TODO: There may be an issue here if a user without a COA refreshes the page + // We should instead be checking whether FCL itself is authorized + const accounts = await this.getAccounts() + return accounts.length > 0 + }, + async switchChain({addEthereumChainParameter, chainId}: any) { + const provider = await this.getProvider() + + const chain = config.chains.find(x => x.id === chainId) + if (!chain) throw new SwitchChainError(new ChainNotConfiguredError()) + + try { + await provider.request({ + method: "wallet_switchEthereumChain", + params: [{chainId: numberToHex(chainId)}], + }) + + return chain + } catch (err) { + // TODO: Error handling + throw new SwitchChainError(err as Error) + } + }, + onAccountsChanged(accounts) { + if (accounts.length === 0) this.onDisconnect() + else + config.emitter.emit("change", { + accounts: accounts.map((x: any) => getAddress(x)), + }) + }, + onChainChanged(chain) { + const chainId = Number(chain) + config.emitter.emit("change", {chainId}) + }, + async onConnect(connectInfo) { + const accounts = await this.getAccounts() + + // TODO: What to do if accounts is empty? not sure this is accurate + if (accounts.length === 0) return + + const chainId = Number(connectInfo.chainId) + config.emitter.emit("connect", {accounts, chainId}) + + const provider = await this.getProvider() + + if (connect) provider.removeListener("connect", connect) + connect = undefined + + if (accountsChanged) + provider.removeListener("accountsChanged", accountsChanged) + accountsChanged = this.onAccountsChanged.bind(this) + provider.on("accountsChanged", accountsChanged) + + if (chainChanged) provider.removeListener("chainChanged", chainChanged) + chainChanged = this.onChainChanged.bind(this) + provider.on("chainChanged", chainChanged) + + if (disconnect) provider.removeListener("disconnect", disconnect) + disconnect = (error: Error) => { + throw new ProviderDisconnectedError(error) + } + provider.on("disconnect", disconnect) + }, + // TODO: waht to do with error? + async onDisconnect(error) { + const provider = await this.getProvider() + + config.emitter.emit("disconnect") + + // Manage EIP-1193 event listeners + if (chainChanged) { + provider.removeListener("chainChanged", chainChanged) + chainChanged = undefined + } + if (disconnect) { + provider.removeListener("disconnect", disconnect) + disconnect = undefined + } + if (!connect) { + connect = this.onConnect.bind(this) + provider.on("connect", connect) + } + }, + onDisplayUri(uri: string) { + config.emitter.emit("message", {type: "display_uri", data: uri}) + }, + })) +} diff --git a/packages/fcl-wagmi-adapter/src/index.ts b/packages/fcl-wagmi-adapter/src/index.ts index 5fffa43bb..6cea6ebfa 100644 --- a/packages/fcl-wagmi-adapter/src/index.ts +++ b/packages/fcl-wagmi-adapter/src/index.ts @@ -1,215 +1,2 @@ -import { - ChainNotConfiguredError, - type Connector, - createConnector, -} from "@wagmi/core" -import { - type Address, - type ProviderConnectInfo, - ProviderDisconnectedError, - SwitchChainError, - getAddress, - numberToHex, -} from "viem" -import {createProvider} from "@onflow/fcl-ethereum-provider" - -type FclWagmiAdapterParams = Parameters[0] - -export function fclWagmiAdapter(params: FclWagmiAdapterParams) { - type Provider = ReturnType - type Properties = { - onConnect(connectInfo: ProviderConnectInfo): void - onDisplayUri(uri: string): void - } - let provider: Provider | undefined - - let accountsChanged: Connector["onAccountsChanged"] | undefined - let chainChanged: Connector["onChainChanged"] | undefined - let connect: Connector["onConnect"] | undefined - let disconnect: ((error: Error) => void) | undefined - - // Parse and validate service parameters - const id = params.service?.uid || "fcl" - const name = params.service?.provider?.name || "Cadence Wallet" - - // TODO: we need to surface this through FCL service configuration - const rdns = (params.service?.provider as any)?.rdns - - return createConnector(config => ({ - id: id, - name: name, - type: "fcl-wagmi-adapter", - rdns: rdns, - async setup() { - const provider = await this.getProvider() - - if (connect) provider.removeListener("connect", connect) - connect = this.onConnect.bind(this) - provider.on("connect", connect) - - // We shouldn't need to listen for `'accountsChanged'` here since the `'connect'` event should suffice (and wallet shouldn't be connected yet). - // Some wallets, like MetaMask, do not implement the `'connect'` event and overload `'accountsChanged'` instead. - if (!accountsChanged) { - accountsChanged = this.onAccountsChanged.bind(this) - provider.on("accountsChanged", accountsChanged) - } - }, - async connect({isReconnecting}: any = {}) { - const provider = await this.getProvider() - - let accounts: readonly Address[] - if (isReconnecting) { - accounts = await this.getAccounts() - } else { - accounts = ( - (await provider.request({ - method: "eth_requestAccounts", - })) as string[] - ).map(x => getAddress(x)) - } - - // Manage EIP-1193 event listeners - // https://eips.ethereum.org/EIPS/eip-1193#events - if (connect) provider.removeListener("connect", connect) - connect = this.onConnect.bind(this) - provider.on("connect", connect) - - if (accountsChanged) - provider.removeListener("accountsChanged", accountsChanged) - accountsChanged = this.onAccountsChanged.bind(this) - provider.on("accountsChanged", accountsChanged) - - if (chainChanged) provider.removeListener("chainChanged", chainChanged) - chainChanged = this.onChainChanged.bind(this) - provider.on("chainChanged", chainChanged) - - if (disconnect) provider.removeListener("disconnect", disconnect) - disconnect = (error: Error) => { - throw new ProviderDisconnectedError(error) - } - provider.on("disconnect", disconnect) - - return {accounts, chainId: await this.getChainId()} - }, - async disconnect() { - const provider = await this.getProvider() - - // Manage EIP-1193 event listeners - if (chainChanged) provider.removeListener("chainChanged", chainChanged) - chainChanged = undefined - - if (disconnect) provider.removeListener("disconnect", disconnect) - disconnect = undefined - - if (connect) provider.removeListener("connect", connect) - connect = this.onConnect.bind(this) - provider.on("connect", connect) - - await provider.disconnect() - }, - async getAccounts() { - const provider = await this.getProvider() - const accounts = (await provider.request({ - method: "eth_accounts", - })) as string[] - return accounts.map(x => getAddress(x)) - }, - async getChainId() { - const provider = await this.getProvider() - const chainId = await provider.request({method: "eth_chainId"}) - console.log("CHAIN ID", chainId) - return Number(chainId) - }, - async getProvider() { - return provider ?? (provider = createProvider(params)) - }, - async isAuthorized() { - // TODO: There may be an issue here if a user without a COA refreshes the page - // We should instead be checking whether FCL itself is authorized - const accounts = await this.getAccounts() - return accounts.length > 0 - }, - async switchChain({addEthereumChainParameter, chainId}: any) { - console.log("HEY") - const provider = await this.getProvider() - - const chain = config.chains.find(x => x.id === chainId) - if (!chain) throw new SwitchChainError(new ChainNotConfiguredError()) - - try { - await provider.request({ - method: "wallet_switchEthereumChain", - params: [{chainId: numberToHex(chainId)}], - }) - - return chain - } catch (err) { - // TODO: Error handling - throw new SwitchChainError(err as Error) - } - }, - onAccountsChanged(accounts) { - if (accounts.length === 0) this.onDisconnect() - else - config.emitter.emit("change", { - accounts: accounts.map((x: any) => getAddress(x)), - }) - }, - onChainChanged(chain) { - const chainId = Number(chain) - config.emitter.emit("change", {chainId}) - }, - async onConnect(connectInfo) { - const accounts = await this.getAccounts() - - // TODO: What to do if accounts is empty? not sure this is accurate - if (accounts.length === 0) return - - const chainId = Number(connectInfo.chainId) - config.emitter.emit("connect", {accounts, chainId}) - - const provider = await this.getProvider() - - if (connect) provider.removeListener("connect", connect) - connect = undefined - - if (accountsChanged) - provider.removeListener("accountsChanged", accountsChanged) - accountsChanged = this.onAccountsChanged.bind(this) - provider.on("accountsChanged", accountsChanged) - - if (chainChanged) provider.removeListener("chainChanged", chainChanged) - chainChanged = this.onChainChanged.bind(this) - provider.on("chainChanged", chainChanged) - - if (disconnect) provider.removeListener("disconnect", disconnect) - disconnect = (error: Error) => { - throw new ProviderDisconnectedError(error) - } - provider.on("disconnect", disconnect) - }, - // TODO: waht to do with error? - async onDisconnect(error) { - const provider = await this.getProvider() - - config.emitter.emit("disconnect") - - // Manage EIP-1193 event listeners - if (chainChanged) { - provider.removeListener("chainChanged", chainChanged) - chainChanged = undefined - } - if (disconnect) { - provider.removeListener("disconnect", disconnect) - disconnect = undefined - } - if (!connect) { - connect = this.onConnect.bind(this) - provider.on("connect", connect) - } - }, - onDisplayUri(uri: string) { - config.emitter.emit("message", {type: "display_uri", data: uri}) - }, - })) -} +export * from "./wc-connector" +export * from "./fcl-connector" diff --git a/packages/fcl-wagmi-adapter/src/wc-connector.ts b/packages/fcl-wagmi-adapter/src/wc-connector.ts new file mode 100644 index 000000000..f93c9dc5e --- /dev/null +++ b/packages/fcl-wagmi-adapter/src/wc-connector.ts @@ -0,0 +1,500 @@ +/** + * This file is a modified version of the original WalletConnect connector from wagmi. + * The purpose is to substitute the original WalletConnect EthereumProvider with an extended + * version that is able to be used in a cross-VM context (authenticating multiple VMs). + * + * See: https://github.com/wevm/wagmi/blob/2ca5742840f0c3be99cd61095650400aee514913/packages/connectors/src/walletConnect.ts + */ + +/*! + * MIT License + * + * Copyright (c) 2022-present weth, LLC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { + ChainNotConfiguredError, + type Connector, + ProviderNotFoundError, + createConnector, + extractRpcUrls, +} from "@wagmi/core" +import type {Compute, ExactPartial, Omit} from "@wagmi/core/internal" +import {WalletConnectEthereumProvider as EthereumProvider} from "@onflow/fcl-ethereum-provider" +import { + type AddEthereumChainParameter, + type Address, + type ProviderConnectInfo, + type ProviderRpcError, + type RpcError, + SwitchChainError, + UserRejectedRequestError, + getAddress, + numberToHex, +} from "viem" + +type WalletConnectConnector = Connector & { + onDisplayUri(uri: string): void + onSessionDelete(data: {topic: string}): void +} + +type EthereumProviderOptions = Parameters<(typeof EthereumProvider)["init"]>[0] + +export type WalletConnectParameters = Compute< + { + /** + * If a new chain is added to a previously existing configured connector `chains`, this flag + * will determine if that chain should be considered as stale. A stale chain is a chain that + * WalletConnect has yet to establish a relationship with (e.g. the user has not approved or + * rejected the chain). + * + * This flag mainly affects the behavior when a wallet does not support dynamic chain authorization + * with WalletConnect v2. + * + * If `true` (default), the new chain will be treated as a stale chain. If the user + * has yet to establish a relationship (approved/rejected) with this chain in their WalletConnect + * session, the connector will disconnect upon the dapp auto-connecting, and the user will have to + * reconnect to the dapp (revalidate the chain) in order to approve the newly added chain. + * This is the default behavior to avoid an unexpected error upon switching chains which may + * be a confusing user experience (e.g. the user will not know they have to reconnect + * unless the dapp handles these types of errors). + * + * If `false`, the new chain will be treated as a potentially valid chain. This means that if the user + * has yet to establish a relationship with the chain in their WalletConnect session, wagmi will successfully + * auto-connect the user. This comes with the trade-off that the connector will throw an error + * when attempting to switch to the unapproved chain if the wallet does not support dynamic session updates. + * This may be useful in cases where a dapp constantly + * modifies their configured chains, and they do not want to disconnect the user upon + * auto-connecting. If the user decides to switch to the unapproved chain, it is important that the + * dapp handles this error and prompts the user to reconnect to the dapp in order to approve + * the newly added chain. + * + * @default true + */ + isNewChainsStale?: boolean + } & Omit< + EthereumProviderOptions, + | "chains" + | "events" + | "optionalChains" + | "optionalEvents" + | "optionalMethods" + | "methods" + | "rpcMap" + | "showQrModal" + > & + ExactPartial> +> + +walletConnect.type = "walletConnect" as const +export function walletConnect(parameters: WalletConnectParameters) { + const isNewChainsStale = parameters.isNewChainsStale ?? true + + type Provider = Awaited> + type Properties = { + connect(parameters?: { + chainId?: number | undefined + isReconnecting?: boolean | undefined + pairingTopic?: string | undefined + }): Promise<{ + accounts: readonly Address[] + chainId: number + }> + getNamespaceChainsIds(): number[] + getRequestedChainsIds(): Promise + isChainsStale(): Promise + onConnect(connectInfo: ProviderConnectInfo): void + onDisplayUri(uri: string): void + onSessionDelete(data: {topic: string}): void + setRequestedChainsIds(chains: number[]): void + requestedChainsStorageKey: `${string}.requestedChains` + } + type StorageItem = { + [_ in Properties["requestedChainsStorageKey"]]: number[] + } + + let provider_: Provider | undefined + let providerPromise: Promise + const NAMESPACE = "eip155" + + let accountsChanged: WalletConnectConnector["onAccountsChanged"] | undefined + let chainChanged: WalletConnectConnector["onChainChanged"] | undefined + let connect: WalletConnectConnector["onConnect"] | undefined + let displayUri: WalletConnectConnector["onDisplayUri"] | undefined + let sessionDelete: WalletConnectConnector["onSessionDelete"] | undefined + let disconnect: WalletConnectConnector["onDisconnect"] | undefined + + return createConnector(config => ({ + id: "walletConnect", + name: "WalletConnect", + type: walletConnect.type, + async setup() { + const provider = await this.getProvider().catch(() => null) + if (!provider) return + if (!connect) { + connect = this.onConnect.bind(this) + provider.on("connect", connect) + } + if (!sessionDelete) { + sessionDelete = this.onSessionDelete.bind(this) + provider.on("session_delete", sessionDelete) + } + }, + async connect({chainId, ...rest} = {}) { + try { + const provider = await this.getProvider() + if (!provider) throw new ProviderNotFoundError() + if (!displayUri) { + displayUri = this.onDisplayUri + provider.on("display_uri", displayUri) + } + + let targetChainId = chainId + if (!targetChainId) { + const state = (await config.storage?.getItem("state")) ?? {} + const isChainSupported = config.chains.some( + x => x.id === state.chainId + ) + if (isChainSupported) targetChainId = state.chainId + else targetChainId = config.chains[0]?.id + } + if (!targetChainId) throw new Error("No chains found on connector.") + + const isChainsStale = await this.isChainsStale() + // If there is an active session with stale chains, disconnect current session. + if (provider.session && isChainsStale) await provider.disconnect() + + // If there isn't an active session or chains are stale, connect. + if (!provider.session || isChainsStale) { + const optionalChains = config.chains + .filter(chain => chain.id !== targetChainId) + .map(optionalChain => optionalChain.id) + await provider.connect({ + optionalChains: [targetChainId, ...optionalChains], + ...("pairingTopic" in rest + ? {pairingTopic: rest.pairingTopic} + : {}), + }) + + this.setRequestedChainsIds(config.chains.map(x => x.id)) + } + + // If session exists and chains are authorized, enable provider for required chain + const accounts = (await provider.enable()).map(x => getAddress(x)) + const currentChainId = await this.getChainId() + + if (displayUri) { + provider.removeListener("display_uri", displayUri) + displayUri = undefined + } + if (connect) { + provider.removeListener("connect", connect) + connect = undefined + } + if (!accountsChanged) { + accountsChanged = this.onAccountsChanged.bind(this) + provider.on("accountsChanged", accountsChanged) + } + if (!chainChanged) { + chainChanged = this.onChainChanged.bind(this) + provider.on("chainChanged", chainChanged) + } + if (!disconnect) { + disconnect = this.onDisconnect.bind(this) + provider.on("disconnect", disconnect) + } + if (!sessionDelete) { + sessionDelete = this.onSessionDelete.bind(this) + provider.on("session_delete", sessionDelete) + } + + return {accounts, chainId: currentChainId} + } catch (error) { + if ( + /(user rejected|connection request reset)/i.test( + (error as ProviderRpcError)?.message + ) + ) { + throw new UserRejectedRequestError(error as Error) + } + throw error + } + }, + async disconnect() { + const provider = await this.getProvider() + try { + await provider?.disconnect() + } catch (error) { + if (!/No matching key/i.test((error as Error).message)) throw error + } finally { + if (chainChanged) { + provider?.removeListener("chainChanged", chainChanged) + chainChanged = undefined + } + if (disconnect) { + provider?.removeListener("disconnect", disconnect) + disconnect = undefined + } + if (!connect) { + connect = this.onConnect.bind(this) + provider?.on("connect", connect) + } + if (accountsChanged) { + provider?.removeListener("accountsChanged", accountsChanged) + accountsChanged = undefined + } + if (sessionDelete) { + provider?.removeListener("session_delete", sessionDelete) + sessionDelete = undefined + } + + this.setRequestedChainsIds([]) + } + }, + async getAccounts() { + const provider = await this.getProvider() + return provider.accounts.map(x => getAddress(x)) + }, + async getProvider({chainId} = {}) { + async function initProvider() { + const optionalChains = config.chains.map(x => x.id) as [number] + if (!optionalChains.length) return + return await EthereumProvider.init({ + ...parameters, + disableProviderPing: true, + optionalChains, + projectId: parameters.projectId, + rpcMap: Object.fromEntries( + config.chains.map(chain => { + const [url] = extractRpcUrls({ + chain, + transports: config.transports, + }) + return [chain.id, url] + }) + ), + showQrModal: parameters.showQrModal ?? true, + customStoragePrefix: "cross-vm-walletconnect", + }) + } + + if (!provider_) { + if (!providerPromise) providerPromise = initProvider() as any + provider_ = await providerPromise + provider_?.events.setMaxListeners(Number.POSITIVE_INFINITY) + } + if (chainId) await this.switchChain?.({chainId}) + return provider_! + }, + async getChainId() { + const provider = await this.getProvider() + return provider.chainId + }, + async isAuthorized() { + try { + const [accounts, provider] = await Promise.all([ + this.getAccounts(), + this.getProvider(), + ]) + + // If an account does not exist on the session, then the connector is unauthorized. + if (!accounts.length) return false + + // If the chains are stale on the session, then the connector is unauthorized. + const isChainsStale = await this.isChainsStale() + if (isChainsStale && provider.session) { + await provider.disconnect().catch(() => {}) + return false + } + return true + } catch { + return false + } + }, + async switchChain({addEthereumChainParameter, chainId}) { + const provider = await this.getProvider() + if (!provider) throw new ProviderNotFoundError() + + const chain = config.chains.find(x => x.id === chainId) + if (!chain) throw new SwitchChainError(new ChainNotConfiguredError()) + + try { + await Promise.all([ + new Promise(resolve => { + const listener = ({ + chainId: currentChainId, + }: { + chainId?: number | undefined + }) => { + if (currentChainId === chainId) { + config.emitter.off("change", listener) + resolve() + } + } + config.emitter.on("change", listener) + }), + provider.request({ + method: "wallet_switchEthereumChain", + params: [{chainId: numberToHex(chainId)}], + }), + ]) + + const requestedChains = await this.getRequestedChainsIds() + this.setRequestedChainsIds([...requestedChains, chainId]) + + return chain + } catch (err) { + const error = err as RpcError + + if (/(user rejected)/i.test(error.message)) + throw new UserRejectedRequestError(error) + + // Indicates chain is not added to provider + try { + let blockExplorerUrls: string[] | undefined + if (addEthereumChainParameter?.blockExplorerUrls) + blockExplorerUrls = addEthereumChainParameter.blockExplorerUrls + else + blockExplorerUrls = chain.blockExplorers?.default.url + ? [chain.blockExplorers?.default.url] + : [] + + let rpcUrls: readonly string[] + if (addEthereumChainParameter?.rpcUrls?.length) + rpcUrls = addEthereumChainParameter.rpcUrls + else rpcUrls = [...chain.rpcUrls.default.http] + + const addEthereumChain = { + blockExplorerUrls, + chainId: numberToHex(chainId), + chainName: addEthereumChainParameter?.chainName ?? chain.name, + iconUrls: addEthereumChainParameter?.iconUrls, + nativeCurrency: + addEthereumChainParameter?.nativeCurrency ?? chain.nativeCurrency, + rpcUrls, + } satisfies AddEthereumChainParameter + + await provider.request({ + method: "wallet_addEthereumChain", + params: [addEthereumChain], + }) + + const requestedChains = await this.getRequestedChainsIds() + this.setRequestedChainsIds([...requestedChains, chainId]) + return chain + } catch (error) { + throw new UserRejectedRequestError(error as Error) + } + } + }, + onAccountsChanged(accounts) { + if (accounts.length === 0) this.onDisconnect() + else + config.emitter.emit("change", { + accounts: accounts.map(x => getAddress(x)), + }) + }, + onChainChanged(chain) { + const chainId = Number(chain) + config.emitter.emit("change", {chainId}) + }, + async onConnect(connectInfo) { + const chainId = Number(connectInfo.chainId) + const accounts = await this.getAccounts() + config.emitter.emit("connect", {accounts, chainId}) + }, + async onDisconnect(_error) { + this.setRequestedChainsIds([]) + config.emitter.emit("disconnect") + + const provider = await this.getProvider() + if (accountsChanged) { + provider.removeListener("accountsChanged", accountsChanged) + accountsChanged = undefined + } + if (chainChanged) { + provider.removeListener("chainChanged", chainChanged) + chainChanged = undefined + } + if (disconnect) { + provider.removeListener("disconnect", disconnect) + disconnect = undefined + } + if (sessionDelete) { + provider.removeListener("session_delete", sessionDelete) + sessionDelete = undefined + } + if (!connect) { + connect = this.onConnect.bind(this) + provider.on("connect", connect) + } + }, + onDisplayUri(uri) { + config.emitter.emit("message", {type: "display_uri", data: uri}) + }, + onSessionDelete() { + this.onDisconnect() + }, + getNamespaceChainsIds() { + if (!provider_) return [] + const chainIds = provider_.session?.namespaces[NAMESPACE]?.accounts?.map( + account => Number.parseInt(account.split(":")[1] || "") + ) + return chainIds ?? [] + }, + async getRequestedChainsIds() { + return ( + (await config.storage?.getItem(this.requestedChainsStorageKey)) ?? [] + ) + }, + /** + * Checks if the target chains match the chains that were + * initially requested by the connector for the WalletConnect session. + * If there is a mismatch, this means that the chains on the connector + * are considered stale, and need to be revalidated at a later point (via + * connection). + * + * There may be a scenario where a dapp adds a chain to the + * connector later on, however, this chain will not have been approved or rejected + * by the wallet. In this case, the chain is considered stale. + */ + async isChainsStale() { + if (!isNewChainsStale) return false + + const connectorChains = config.chains.map(x => x.id) + const namespaceChains = this.getNamespaceChainsIds() + if ( + namespaceChains.length && + !namespaceChains.some(id => connectorChains.includes(id)) + ) + return false + + const requestedChains = await this.getRequestedChainsIds() + return !connectorChains.every(id => requestedChains.includes(id)) + }, + async setRequestedChainsIds(chains) { + await config.storage?.setItem(this.requestedChainsStorageKey, chains) + }, + get requestedChainsStorageKey() { + return `${this.id}.requestedChains` as Properties["requestedChainsStorageKey"] + }, + })) +} diff --git a/packages/fcl-wc/package.json b/packages/fcl-wc/package.json index e996e0ddb..d7768e53e 100644 --- a/packages/fcl-wc/package.json +++ b/packages/fcl-wc/package.json @@ -43,10 +43,12 @@ "@onflow/config": "1.5.1", "@onflow/util-invariant": "1.2.4", "@onflow/util-logger": "1.3.3", + "@walletconnect/ethereum-provider": "^2.18.0", "@walletconnect/modal": "^2.7.0", "@walletconnect/modal-core": "^2.6.2", "@walletconnect/sign-client": "^2.17.1", "@walletconnect/types": "^2.8.1", + "@walletconnect/universal-provider": "^2.18.0", "@walletconnect/utils": "^2.8.1", "postcss-cli": "^11.0.0", "preact": "^10.24.3", diff --git a/packages/fcl-wc/src/fcl-wc.ts b/packages/fcl-wc/src/fcl-wc.ts index 2a26ed65e..0c69e1634 100644 --- a/packages/fcl-wc/src/fcl-wc.ts +++ b/packages/fcl-wc/src/fcl-wc.ts @@ -1,10 +1,13 @@ import * as fclCore from "@onflow/fcl-core" -import SignClient from "@walletconnect/sign-client" import {invariant} from "@onflow/util-invariant" import {LEVELS, log} from "@onflow/util-logger" export {getSdkError} from "@walletconnect/utils" import {makeServicePlugin} from "./service" import {CoreTypes} from "@walletconnect/types" +import { + UniversalProvider, + UniversalProviderOpts, +} from "@walletconnect/universal-provider" export interface FclWalletConnectConfig { projectId: string @@ -18,7 +21,8 @@ export interface FclWalletConnectConfig { const DEFAULT_RELAY_URL = "wss://relay.walletconnect.com" const DEFAULT_LOGGER = "debug" -let clientPromise: Promise = Promise.resolve(null) +let providerPromise: Promise | null> = + Promise.resolve(null) const initClient = async ({ projectId, @@ -32,7 +36,7 @@ const initClient = async ({ "FCL Wallet Connect Error: WalletConnect projectId is required" ) try { - return SignClient.init({ + return UniversalProvider.init({ logger: DEFAULT_LOGGER, relayUrl: DEFAULT_RELAY_URL, projectId: projectId, @@ -50,19 +54,23 @@ const initClient = async ({ } } +const initUniversalProvider = async (opts: UniversalProviderOpts) => { + return UniversalProvider.init({}) +} + export const initLazy = (config: FclWalletConnectConfig) => { - const {FclWcServicePlugin, clientPromise} = initHelper(config) + const {FclWcServicePlugin, providerPromise} = initHelper(config) fclCore.discovery.authn.update() return { FclWcServicePlugin, - clientPromise, + providerPromise, } } export const init = async (config: FclWalletConnectConfig) => { - const {FclWcServicePlugin, clientPromise} = initLazy(config) - const client = await clientPromise + const {FclWcServicePlugin, providerPromise} = initLazy(config) + const client = await providerPromise fclCore.discovery.authn.update() return { @@ -82,7 +90,7 @@ const initHelper = (config: FclWalletConnectConfig) => { // - Initialize the client if it doesn't exist // - If it does exist, return existing client // - If existing client fails to initialize, reinitialize - clientPromise = Promise.resolve(clientPromise) + providerPromise = providerPromise .catch(() => null) .then(_client => { if (_client) { @@ -103,22 +111,22 @@ const initHelper = (config: FclWalletConnectConfig) => { throw e }) - const FclWcServicePlugin = makeServicePlugin(clientPromise, config) + const FclWcServicePlugin = makeServicePlugin(providerPromise, config) return { FclWcServicePlugin, - clientPromise, + providerPromise, } } // Returns the SignClient instance used by this plugin if it has been initialized -export async function getSignClient() { - return clientPromise.then(client => { - if (!client) { +export async function getProvider() { + return providerPromise.then(provider => { + if (!provider) { throw new Error("WalletConnect client not initialized") } - return client + return provider }) } diff --git a/packages/fcl-wc/src/index.ts b/packages/fcl-wc/src/index.ts index 5bf8e4d34..e5b6e5548 100644 --- a/packages/fcl-wc/src/index.ts +++ b/packages/fcl-wc/src/index.ts @@ -1,3 +1,3 @@ -export {init, initLazy, getSignClient} from "./fcl-wc" +export {init, initLazy, getProvider} from "./fcl-wc" export {createSessionProposal, request} from "./session" export {FLOW_METHODS, SERVICE_PLUGIN_NAME, WC_SERVICE_METHOD} from "./constants" diff --git a/packages/fcl-wc/src/service.ts b/packages/fcl-wc/src/service.ts index 3783e46f5..bd17777f1 100644 --- a/packages/fcl-wc/src/service.ts +++ b/packages/fcl-wc/src/service.ts @@ -2,25 +2,31 @@ import {invariant} from "@onflow/util-invariant" import {log, LEVELS} from "@onflow/util-logger" import {isMobile, openDeeplink, preloadImage, shouldDeepLink} from "./utils" import { + FLOW_METHODS, REQUEST_TYPES, SERVICE_PLUGIN_NAME, WC_SERVICE_METHOD, } from "./constants" -import {SignClient} from "@walletconnect/sign-client/dist/types/client" import {createSessionProposal, request} from "./session" -import {ModalCtrlState} from "@walletconnect/modal-core/dist/_types/src/types/controllerTypes" import {showNotification} from "./ui/notifications" import type {FclWalletConnectConfig} from "./fcl-wc" import mobileIcon from "./ui/assets/mobile.svg" import {CurrentUser, Service} from "@onflow/typedefs" import {SessionTypes} from "@walletconnect/types" +import {UniversalProvider} from "@walletconnect/universal-provider" +import {createStore} from "./store" +import {ModalCtrlState} from "@walletconnect/modal-core/dist/_types/src/types/controllerTypes" type WalletConnectModalType = import("@walletconnect/modal").WalletConnectModal type Constructor = new (...args: any[]) => T +let providerStore = createStore<{ + [key: string]: InstanceType +}>({}) + export const makeServicePlugin = ( - client: Promise, + provider: Promise | null>, config: FclWalletConnectConfig = { projectId: "", includeBaseWC: false, @@ -36,7 +42,7 @@ export const makeServicePlugin = ( serviceStrategy: { method: WC_SERVICE_METHOD, exec: makeExec( - client, + provider, config, import("@walletconnect/modal").then(m => m.WalletConnectModal) ), @@ -45,7 +51,7 @@ export const makeServicePlugin = ( }) const makeExec = ( - clientPromise: Promise, + signerPromise: Promise | null>, config: FclWalletConnectConfig, WalletConnectModal: Promise> ) => { @@ -71,22 +77,25 @@ const makeExec = ( disableNotifications: appDisabledNotifications, } = config - const client = await clientPromise - invariant(!!client, "WalletConnect is not initialized") + const resolvedProvider = await resolveProvider({ + provider: signerPromise, + externalProviderOrTopic: service.params?.externalProvider, + }) + invariant(!!resolvedProvider, "WalletConnect is not initialized") + + const {provider: provider, isExternal} = resolvedProvider - let session: SessionTypes.Struct | null = null, + let session: SessionTypes.Struct | null = provider.session ?? null, pairing: any const method = service.endpoint const appLink = validateAppLink(service) - const pairings = client.pairing.getAll({active: true}) - if (pairings.length > 0) { - pairing = pairings?.find(p => p.peerMetadata?.url === service.uid) - } - - if (client.session.length > 0) { - const lastKeyIndex = client.session.keys.length - 1 - session = client.session.get(client.session.keys.at(lastKeyIndex)!) + // If the user is already connected to this session, use it + if ( + session?.topic === service.params?.externalProvider && + method === FLOW_METHODS.FLOW_AUTHN + ) { + return user } if (session == null) { @@ -99,7 +108,7 @@ const makeExec = ( service, onClose, appLink, - client, + provider, method, pairing, wcRequestHook, @@ -143,8 +152,9 @@ const makeExec = ( method, body, session, - client, + provider, abortSignal, + isExternal, }).finally(() => notification?.dismiss()) function validateAppLink({uid}: {uid: string}) { @@ -168,7 +178,7 @@ function connectWc( service, onClose, appLink, - client, + provider, method, pairing, wcRequestHook, @@ -178,14 +188,14 @@ function connectWc( service: any onClose: any appLink: string - client: SignClient + provider: InstanceType method: string pairing: any wcRequestHook: any pairingModalOverride: any abortSignal?: AbortSignal }): Promise => { - const projectId = client.opts?.projectId + const projectId = provider.providerOpts.projectId invariant( !!projectId, "Cannot establish connection, WalletConnect projectId is undefined" @@ -196,10 +206,9 @@ function connectWc( try { const {uri, approval} = await createSessionProposal({ - client, + provider, existingPairing: pairing, }) - _uri = uri if (wcRequestHook && wcRequestHook instanceof Function) { wcRequestHook({ @@ -253,6 +262,11 @@ function connectWc( }) }), ]) + + if (session == null) { + throw new Error("Session request failed") + } + return session } catch (error) { if (error instanceof Error) { @@ -268,7 +282,7 @@ function connectWc( onClose() throw error } finally { - walletConnectModal?.closeModal() + // walletConnectModal?.closeModal() } } } @@ -300,3 +314,70 @@ export function showWcRequestNotification({ debounceDelay: service.type === "pre-authz" ? 500 : 0, }) } + +async function resolveProvider({ + provider, + externalProviderOrTopic, +}: { + provider: Promise | null> + externalProviderOrTopic?: string | InstanceType +}): Promise<{ + provider: InstanceType + isExternal: boolean +} | null> { + if (!externalProviderOrTopic) { + const resolved = await provider + return resolved ? {provider: resolved, isExternal: false} : null + } + + // If it's a UniversalProvider instance, use it directly and store it. + if (typeof externalProviderOrTopic !== "string") { + const topic = externalProviderOrTopic.session?.topic + if (!topic) { + throw new Error( + "Cannot resolve provider: UniversalProvider is not initialized" + ) + } + providerStore.setState({ + [topic]: externalProviderOrTopic, + }) + return {provider: externalProviderOrTopic, isExternal: true} + } + + const externalTopic = externalProviderOrTopic + if (externalTopic) { + // Check if an external provider was passed in the options. + let storedProvider = providerStore.getState()[externalTopic] + if (!storedProvider) { + // No provider from opts and nothing in store yet—wait for it. + let unsubStore: () => void + let timeout: NodeJS.Timeout + + storedProvider = await new Promise((resolve, reject) => { + unsubStore = providerStore.subscribe(() => { + const provider = providerStore.getState()[externalTopic] + if (provider) { + resolve(provider) + } + }) + + // If the provider is not defined after 5 seconds, reject the promise. + timeout = setTimeout(() => { + reject( + new Error( + `Provider for external topic ${externalTopic} not found after 5 seconds` + ) + ) + }, 5000) + }).finally(() => { + clearTimeout(timeout) + unsubStore() + }) + } + + return {provider: storedProvider, isExternal: true} + } + + const resolved = await provider + return resolved ? {provider: resolved, isExternal: false} : null +} diff --git a/packages/fcl-wc/src/session.ts b/packages/fcl-wc/src/session.ts index 06efd2195..07673ca6b 100644 --- a/packages/fcl-wc/src/session.ts +++ b/packages/fcl-wc/src/session.ts @@ -1,14 +1,15 @@ import * as fclCore from "@onflow/fcl-core" import {FLOW_METHODS} from "./constants" -import {SignClient} from "@walletconnect/sign-client/dist/types/client" import {PairingTypes, SessionTypes} from "@walletconnect/types" +import {UniversalProvider} from "@walletconnect/universal-provider" +import {Service} from "@onflow/typedefs" // Create a new session proposal with the WalletConnect client export async function createSessionProposal({ - client, + provider, existingPairing, }: { - client: SignClient + provider: InstanceType existingPairing?: PairingTypes.Struct }) { const network = await fclCore.getChainId() @@ -26,44 +27,59 @@ export async function createSessionProposal({ }, } - const {uri, approval} = await client.connect({ - pairingTopic: existingPairing?.topic, - requiredNamespaces, + let cleanup: () => void + const uri = new Promise((resolve, reject) => { + const onDisplayUri = (uri: string) => { + resolve(uri) + } + provider.on("display_uri", onDisplayUri) + cleanup = () => { + provider.removeListener("display_uri", onDisplayUri) + reject(new Error("WalletConnect Session Request aborted")) + } }) - if (!uri) { - throw new Error( - "FCL-WC: Error creating session proposal. Could not create a proposal URI." - ) - } + const session = await provider + .connect({ + pairingTopic: existingPairing?.topic, + namespaces: requiredNamespaces, + }) + .finally(() => { + cleanup() + }) - return {uri, approval} + return { + uri: await uri, + approval: () => session, + } } export const request = async ({ method, body, session, - client, + provider, + isExternal, abortSignal, }: { method: any body: any session: SessionTypes.Struct - client: SignClient + provider: InstanceType + isExternal?: boolean abortSignal?: AbortSignal }) => { const [chainId, addr, address] = makeSessionData(session) const data = JSON.stringify({...body, addr, address}) const result: any = await Promise.race([ - client.request({ - topic: session.topic, - chainId, + provider.client.request({ request: { method, params: [data], }, + chainId, + topic: provider.session?.topic!, }), new Promise((_, reject) => { if (abortSignal?.aborted) { @@ -79,6 +95,39 @@ export const request = async ({ switch (result.status) { case "APPROVED": + function normalizeService(service: Service) { + if (service.method === "WC/RPC") { + return { + ...service, + params: { + ...service.params, + ...(isExternal ? {externalProvider: session.topic} : {}), + }, + } + } + return service + } + + if (method === FLOW_METHODS.FLOW_AUTHN) { + const services = (result?.data?.services ?? []).map(normalizeService) + + return { + ...(result.data ? result.data : {}), + services, + } + } + + if (method === FLOW_METHODS.FLOW_PRE_AUTHZ) { + return { + ...result.data, + ...(result.data?.proposer + ? {proposer: normalizeService(result.data.proposer)} + : {}), + payer: [...result.data?.payer?.map(normalizeService)], + authorization: [...result.data?.authorization?.map(normalizeService)], + } + } + return result.data case "DECLINED": diff --git a/packages/fcl-wc/src/store.ts b/packages/fcl-wc/src/store.ts new file mode 100644 index 000000000..9d81a813a --- /dev/null +++ b/packages/fcl-wc/src/store.ts @@ -0,0 +1,24 @@ +export function createStore(initialState: T) { + const subscribers = new Set<(state: T) => void>() + let state = initialState + + const subscribe = (subscriber: (state: T) => void) => { + subscribers.add(subscriber) + return () => { + subscribers.delete(subscriber) + } + } + + const setState = (newState: T) => { + state = newState + subscribers.forEach(subscriber => subscriber(state)) + } + + const getState = () => state + + return { + subscribe, + setState, + getState, + } +} diff --git a/packages/fcl/src/discovery/rpc/handlers/request-wc-qr.ts b/packages/fcl/src/discovery/rpc/handlers/request-wc-qr.ts index 4af8dc74a..5e97f119d 100644 --- a/packages/fcl/src/discovery/rpc/handlers/request-wc-qr.ts +++ b/packages/fcl/src/discovery/rpc/handlers/request-wc-qr.ts @@ -1,7 +1,7 @@ import { createSessionProposal, FLOW_METHODS, - getSignClient, + getProvider, request as wcRequest, } from "@onflow/fcl-wc" import {DiscoveryNotification, DiscoveryRpc} from "../requests" @@ -28,11 +28,11 @@ export const wcRequestHandlerFactory = ({ throw new Error("Handler has been terminated") } - const client = await getSignClient() + const provider = await getProvider() // Execute WC bypass if session is approved const {uri, approval} = await createSessionProposal({ - client, + provider, }) // Watch for QR code connection asynchronously @@ -65,7 +65,7 @@ export function watchQrFactory({ // Watch for QR code connection & resolve callback if connected setTimeout(async () => { try { - const client = await getSignClient() + const provider = await getProvider() const session = await approval() rpc.notify(DiscoveryNotification.NOTIFY_QRCODE_CONNECTING, { uri, @@ -75,7 +75,7 @@ export function watchQrFactory({ method: FLOW_METHODS.FLOW_AUTHN, body: authnBody, session, - client, + provider, }) rpc.notify(DiscoveryNotification.NOTIFY_QRCODE_CONNECTED, { diff --git a/packages/fcl/src/utils/walletconnect/loader.ts b/packages/fcl/src/utils/walletconnect/loader.ts index e48bd9e8a..2234c1ac0 100644 --- a/packages/fcl/src/utils/walletconnect/loader.ts +++ b/packages/fcl/src/utils/walletconnect/loader.ts @@ -99,11 +99,12 @@ ${lastConfig}` // We must lazy load the plugin to avoid race conditions // where the developer attempts to use the plugin before // our loader applies the configuration - const {clientPromise: _clientPromise, FclWcServicePlugin} = fclWc.initLazy({ - projectId, - metadata: getMetadata(wcConfig), - disableNotifications: disableNotifications, - }) + const {providerPromise: _clientPromise, FclWcServicePlugin} = + fclWc.initLazy({ + projectId, + metadata: getMetadata(wcConfig), + disableNotifications: disableNotifications, + }) pluginRegistry.add([FclWcServicePlugin]) }) } From e8f5585cd029716940c55e72fc53dc9563fec724 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> Date: Fri, 21 Feb 2025 09:53:37 -0800 Subject: [PATCH 41/56] Add changesets & enter pre-release mode (#2154) --- .changeset/pre.json | 32 ++++++++++++++++++++++++++++++++ .changeset/shaggy-carpets-eat.md | 11 +++++++++++ .github/workflows/release.yml | 1 + 3 files changed, 44 insertions(+) create mode 100644 .changeset/pre.json create mode 100644 .changeset/shaggy-carpets-eat.md diff --git a/.changeset/pre.json b/.changeset/pre.json new file mode 100644 index 000000000..560d6f545 --- /dev/null +++ b/.changeset/pre.json @@ -0,0 +1,32 @@ +{ + "mode": "pre", + "tag": "cross-vm", + "initialVersions": { + "@onflow/config": "1.5.1", + "@onflow/fcl": "1.13.4", + "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-core": "1.13.4", + "@onflow/fcl-ethereum-provider": "0.0.0", + "@onflow/fcl-rainbowkit-adapter": "0.0.0", + "@onflow/fcl-react-native": "1.9.11", + "@onflow/fcl-wagmi-adapter": "0.0.0", + "@onflow/fcl-wc": "5.5.4", + "@onflow/protobuf": "1.3.1", + "@onflow/rlp": "1.2.3", + "@onflow/sdk": "1.5.6", + "@onflow/transport-grpc": "1.4.2-alpha.0", + "@onflow/transport-http": "1.10.5", + "@onflow/typedefs": "1.4.0", + "@onflow/types": "1.4.1", + "@onflow/util-actor": "1.3.4", + "@onflow/util-address": "1.2.3", + "@onflow/util-encode-key": "1.2.4", + "@onflow/util-invariant": "1.2.4", + "@onflow/util-logger": "1.3.3", + "@onflow/util-rpc": "0.0.2", + "@onflow/util-semver": "1.0.3", + "@onflow/util-template": "1.2.3", + "@onflow/util-uid": "1.2.3" + }, + "changesets": [] +} diff --git a/.changeset/shaggy-carpets-eat.md b/.changeset/shaggy-carpets-eat.md new file mode 100644 index 000000000..723cb3e94 --- /dev/null +++ b/.changeset/shaggy-carpets-eat.md @@ -0,0 +1,11 @@ +--- +"@onflow/fcl-wc": major +"@onflow/fcl-bundle": minor +"@onflow/fcl-core": minor +"@onflow/fcl": minor +"@onflow/fcl-rainbowkit-adapter": patch +"@onflow/fcl-ethereum-provider": patch +"@onflow/fcl-wagmi-adapter": patch +--- + +FCL Ethereum Provider & Cross VM Support Alpha Release diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2872686ca..6281e5d22 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,6 +4,7 @@ on: push: branches: - master + - feature/cross-vm concurrency: ${{ github.workflow }}-${{ github.ref }} From 297bb09a01c65672205582f0e8702d647e186007 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Feb 2025 09:57:06 -0800 Subject: [PATCH 42/56] Version Packages (cross-vm) (#2155) Co-authored-by: github-actions[bot] --- .changeset/pre.json | 5 ++++- packages/config/package.json | 2 +- packages/fcl-bundle/CHANGELOG.md | 6 ++++++ packages/fcl-bundle/package.json | 2 +- packages/fcl-core/CHANGELOG.md | 10 ++++++++++ packages/fcl-core/package.json | 4 ++-- packages/fcl-ethereum-provider/CHANGELOG.md | 10 ++++++++++ packages/fcl-ethereum-provider/package.json | 8 ++++---- packages/fcl-rainbowkit-adapter/CHANGELOG.md | 11 +++++++++++ packages/fcl-rainbowkit-adapter/package.json | 10 +++++----- packages/fcl-react-native/CHANGELOG.md | 7 +++++++ packages/fcl-react-native/package.json | 6 +++--- packages/fcl-wagmi-adapter/CHANGELOG.md | 10 ++++++++++ packages/fcl-wagmi-adapter/package.json | 8 ++++---- packages/fcl-wc/CHANGELOG.md | 11 +++++++++++ packages/fcl-wc/package.json | 6 +++--- packages/fcl/CHANGELOG.md | 12 ++++++++++++ packages/fcl/package.json | 8 ++++---- packages/rlp/package.json | 2 +- packages/sdk/package.json | 2 +- packages/transport-grpc/package.json | 2 +- packages/transport-http/package.json | 2 +- packages/typedefs/package.json | 2 +- packages/types/package.json | 2 +- packages/util-actor/package.json | 2 +- packages/util-address/package.json | 2 +- packages/util-encode-key/package.json | 2 +- packages/util-invariant/package.json | 2 +- packages/util-logger/package.json | 2 +- packages/util-rpc/package.json | 2 +- packages/util-semver/package.json | 2 +- packages/util-template/package.json | 2 +- packages/util-uid/package.json | 2 +- 33 files changed, 123 insertions(+), 43 deletions(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index 560d6f545..dac5bb37b 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -28,5 +28,8 @@ "@onflow/util-template": "1.2.3", "@onflow/util-uid": "1.2.3" }, - "changesets": [] + "changesets": [ + "chilled-wombats-reply", + "shaggy-carpets-eat" + ] } diff --git a/packages/config/package.json b/packages/config/package.json index 6285ced3a..ceca38918 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@types/estree": "^1.0.6", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", diff --git a/packages/fcl-bundle/CHANGELOG.md b/packages/fcl-bundle/CHANGELOG.md index 0a0d1b3aa..2d7ca5faf 100644 --- a/packages/fcl-bundle/CHANGELOG.md +++ b/packages/fcl-bundle/CHANGELOG.md @@ -1,5 +1,11 @@ # @onflow/fcl-bundle +## 1.7.0-cross-vm.0 + +### Minor Changes + +- [#2154](https://github.com/onflow/fcl-js/pull/2154) [`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724) Thanks [@jribbink](https://github.com/jribbink)! - FCL Ethereum Provider & Cross VM Support Alpha Release + ## 1.6.0 ### Minor Changes diff --git a/packages/fcl-bundle/package.json b/packages/fcl-bundle/package.json index a46cc1bc9..7ec3cbf1e 100644 --- a/packages/fcl-bundle/package.json +++ b/packages/fcl-bundle/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl-bundle", - "version": "1.6.0", + "version": "1.7.0-cross-vm.0", "description": "FCL Bundler Tool", "license": "Apache-2.0", "author": "Flow Foundation", diff --git a/packages/fcl-core/CHANGELOG.md b/packages/fcl-core/CHANGELOG.md index 74e40741e..1a91f1497 100644 --- a/packages/fcl-core/CHANGELOG.md +++ b/packages/fcl-core/CHANGELOG.md @@ -1,5 +1,15 @@ # @onflow/fcl +## 1.14.0-cross-vm.0 + +### Minor Changes + +- [#2154](https://github.com/onflow/fcl-js/pull/2154) [`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724) Thanks [@jribbink](https://github.com/jribbink)! - FCL Ethereum Provider & Cross VM Support Alpha Release + +### Patch Changes + +- [#2065](https://github.com/onflow/fcl-js/pull/2065) [`3fccbef7bbf985f19d9a9bae2638e538f126f754`](https://github.com/onflow/fcl-js/commit/3fccbef7bbf985f19d9a9bae2638e538f126f754) Thanks [@jribbink](https://github.com/jribbink)! - Fix args type for `fcl.query` & `fcl.mutate` + ## 1.13.4 ### Patch Changes diff --git a/packages/fcl-core/package.json b/packages/fcl-core/package.json index 7356e3f82..cc15f535b 100644 --- a/packages/fcl-core/package.json +++ b/packages/fcl-core/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl-core", - "version": "1.13.4", + "version": "1.14.0-cross-vm.0", "description": "Flow Client Library", "license": "Apache-2.0", "author": "Flow Foundation", @@ -19,7 +19,7 @@ } }, "devDependencies": { - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@onflow/typedefs": "1.4.0", "@types/estree": "^1.0.6", "@types/jest": "^29.5.13", diff --git a/packages/fcl-ethereum-provider/CHANGELOG.md b/packages/fcl-ethereum-provider/CHANGELOG.md index fdb6897bf..0b3946994 100644 --- a/packages/fcl-ethereum-provider/CHANGELOG.md +++ b/packages/fcl-ethereum-provider/CHANGELOG.md @@ -1 +1,11 @@ # @onflow/fcl-ethereum-provider + +## 1.0.0-cross-vm.0 + +### Patch Changes + +- [#2154](https://github.com/onflow/fcl-js/pull/2154) [`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724) Thanks [@jribbink](https://github.com/jribbink)! - FCL Ethereum Provider & Cross VM Support Alpha Release + +- Updated dependencies [[`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724)]: + - @onflow/fcl-wc@6.0.0-cross-vm.0 + - @onflow/fcl@1.14.0-cross-vm.0 diff --git a/packages/fcl-ethereum-provider/package.json b/packages/fcl-ethereum-provider/package.json index d690cd37f..962f22234 100644 --- a/packages/fcl-ethereum-provider/package.json +++ b/packages/fcl-ethereum-provider/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl-ethereum-provider", - "version": "0.0.0", + "version": "1.0.0-cross-vm.0", "description": "Ethereum provider for FCL-compatible wallets", "license": "Apache-2.0", "author": "Dapper Labs ", @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@onflow/typedefs": "^1.4.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", @@ -40,7 +40,7 @@ "@ethersproject/bytes": "^5.7.0", "@ethersproject/hash": "^5.7.0", "@noble/hashes": "^1.7.1", - "@onflow/fcl-wc": "^5.5.4", + "@onflow/fcl-wc": "^6.0.0-cross-vm.0", "@onflow/rlp": "^1.2.3", "@walletconnect/ethereum-provider": "^2.18.0", "@walletconnect/jsonrpc-http-connection": "^1.0.8", @@ -50,6 +50,6 @@ "@walletconnect/utils": "^2.18.0" }, "peerDependencies": { - "@onflow/fcl": "^1.13.4" + "@onflow/fcl": "^1.14.0-cross-vm.0" } } diff --git a/packages/fcl-rainbowkit-adapter/CHANGELOG.md b/packages/fcl-rainbowkit-adapter/CHANGELOG.md index fdb6897bf..250e9950b 100644 --- a/packages/fcl-rainbowkit-adapter/CHANGELOG.md +++ b/packages/fcl-rainbowkit-adapter/CHANGELOG.md @@ -1 +1,12 @@ # @onflow/fcl-ethereum-provider + +## 1.0.0-cross-vm.0 + +### Patch Changes + +- [#2154](https://github.com/onflow/fcl-js/pull/2154) [`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724) Thanks [@jribbink](https://github.com/jribbink)! - FCL Ethereum Provider & Cross VM Support Alpha Release + +- Updated dependencies [[`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724)]: + - @onflow/fcl@1.14.0-cross-vm.0 + - @onflow/fcl-ethereum-provider@1.0.0-cross-vm.0 + - @onflow/fcl-wagmi-adapter@1.0.0-cross-vm.0 diff --git a/packages/fcl-rainbowkit-adapter/package.json b/packages/fcl-rainbowkit-adapter/package.json index ef4ea572a..69058327a 100644 --- a/packages/fcl-rainbowkit-adapter/package.json +++ b/packages/fcl-rainbowkit-adapter/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl-rainbowkit-adapter", - "version": "0.0.0", + "version": "1.0.0-cross-vm.0", "description": "Rainbowkit adapter for FCL-compatible wallets", "license": "Apache-2.0", "author": "Dapper Labs ", @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@onflow/typedefs": "^1.4.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", @@ -39,8 +39,8 @@ "@babel/runtime": "^7.25.7", "@ethersproject/bytes": "^5.7.0", "@ethersproject/hash": "^5.7.0", - "@onflow/fcl-ethereum-provider": "^0.0.0", - "@onflow/fcl-wagmi-adapter": "^0.0.0", + "@onflow/fcl-ethereum-provider": "^1.0.0-cross-vm.0", + "@onflow/fcl-wagmi-adapter": "^1.0.0-cross-vm.0", "@onflow/rlp": "^1.2.3", "@wagmi/core": "^2.16.3", "@walletconnect/jsonrpc-http-connection": "^1.0.8", @@ -50,7 +50,7 @@ "wagmi": "^2.14.11" }, "peerDependencies": { - "@onflow/fcl": "^1.13.4", + "@onflow/fcl": "^1.14.0-cross-vm.0", "@rainbow-me/rainbowkit": "^2.2.3" } } diff --git a/packages/fcl-react-native/CHANGELOG.md b/packages/fcl-react-native/CHANGELOG.md index f3053de4d..e5b5f71f5 100644 --- a/packages/fcl-react-native/CHANGELOG.md +++ b/packages/fcl-react-native/CHANGELOG.md @@ -1,5 +1,12 @@ # @onflow/fcl-react-native +## 1.9.12-cross-vm.0 + +### Patch Changes + +- Updated dependencies [[`3fccbef7bbf985f19d9a9bae2638e538f126f754`](https://github.com/onflow/fcl-js/commit/3fccbef7bbf985f19d9a9bae2638e538f126f754), [`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724)]: + - @onflow/fcl-core@1.14.0-cross-vm.0 + ## 1.9.11 ### Patch Changes diff --git a/packages/fcl-react-native/package.json b/packages/fcl-react-native/package.json index 4e6d7ceb8..374b25670 100644 --- a/packages/fcl-react-native/package.json +++ b/packages/fcl-react-native/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl-react-native", - "version": "1.9.11", + "version": "1.9.12-cross-vm.0", "description": "Flow Client Library", "license": "Apache-2.0", "author": "Flow Foundation", @@ -19,7 +19,7 @@ } }, "devDependencies": { - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@onflow/typedefs": "1.4.0", "@types/estree": "^1.0.6", "@types/node": "^18.19.57", @@ -48,7 +48,7 @@ "dependencies": { "@babel/runtime": "^7.25.7", "@onflow/config": "1.5.1", - "@onflow/fcl-core": "1.13.4", + "@onflow/fcl-core": "1.14.0-cross-vm.0", "@onflow/interaction": "0.0.11", "@onflow/rlp": "1.2.3", "@onflow/sdk": "1.5.6", diff --git a/packages/fcl-wagmi-adapter/CHANGELOG.md b/packages/fcl-wagmi-adapter/CHANGELOG.md index fdb6897bf..546883139 100644 --- a/packages/fcl-wagmi-adapter/CHANGELOG.md +++ b/packages/fcl-wagmi-adapter/CHANGELOG.md @@ -1 +1,11 @@ # @onflow/fcl-ethereum-provider + +## 1.0.0-cross-vm.0 + +### Patch Changes + +- [#2154](https://github.com/onflow/fcl-js/pull/2154) [`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724) Thanks [@jribbink](https://github.com/jribbink)! - FCL Ethereum Provider & Cross VM Support Alpha Release + +- Updated dependencies [[`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724)]: + - @onflow/fcl@1.14.0-cross-vm.0 + - @onflow/fcl-ethereum-provider@1.0.0-cross-vm.0 diff --git a/packages/fcl-wagmi-adapter/package.json b/packages/fcl-wagmi-adapter/package.json index 43022de41..d6a622009 100644 --- a/packages/fcl-wagmi-adapter/package.json +++ b/packages/fcl-wagmi-adapter/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl-wagmi-adapter", - "version": "0.0.0", + "version": "1.0.0-cross-vm.0", "description": "Wagmi adapter for FCL-compatible wallets", "license": "Apache-2.0", "author": "Dapper Labs ", @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@onflow/typedefs": "^1.4.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", @@ -39,7 +39,7 @@ "@babel/runtime": "^7.25.7", "@ethersproject/bytes": "^5.7.0", "@ethersproject/hash": "^5.7.0", - "@onflow/fcl-ethereum-provider": "^0.0.0", + "@onflow/fcl-ethereum-provider": "^1.0.0-cross-vm.0", "@onflow/rlp": "^1.2.3", "@walletconnect/jsonrpc-http-connection": "^1.0.8", "@walletconnect/jsonrpc-provider": "^1.0.14", @@ -50,7 +50,7 @@ "viem": "^2.22.21" }, "peerDependencies": { - "@onflow/fcl": "^1.13.4", + "@onflow/fcl": "^1.14.0-cross-vm.0", "@wagmi/core": "^2.16.3" } } diff --git a/packages/fcl-wc/CHANGELOG.md b/packages/fcl-wc/CHANGELOG.md index e306a0d7f..e8e24009f 100644 --- a/packages/fcl-wc/CHANGELOG.md +++ b/packages/fcl-wc/CHANGELOG.md @@ -1,5 +1,16 @@ # @onflow/fcl-wc +## 6.0.0-cross-vm.0 + +### Major Changes + +- [#2154](https://github.com/onflow/fcl-js/pull/2154) [`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724) Thanks [@jribbink](https://github.com/jribbink)! - FCL Ethereum Provider & Cross VM Support Alpha Release + +### Patch Changes + +- Updated dependencies [[`3fccbef7bbf985f19d9a9bae2638e538f126f754`](https://github.com/onflow/fcl-js/commit/3fccbef7bbf985f19d9a9bae2638e538f126f754), [`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724)]: + - @onflow/fcl-core@1.14.0-cross-vm.0 + ## 5.5.4 ### Patch Changes diff --git a/packages/fcl-wc/package.json b/packages/fcl-wc/package.json index d7768e53e..56846d4ad 100644 --- a/packages/fcl-wc/package.json +++ b/packages/fcl-wc/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl-wc", - "version": "5.5.4", + "version": "6.0.0-cross-vm.0", "description": "WalletConnect adapter for FCL", "license": "Apache-2.0", "author": "Flow Foundation", @@ -30,7 +30,7 @@ "devDependencies": { "@babel/plugin-transform-react-jsx": "^7.25.9", "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@onflow/typedefs": "^1.4.0", "autoprefixer": "^10.4.20", "eslint": "^8.57.1", @@ -55,6 +55,6 @@ "tailwindcss": "^3.4.14" }, "peerDependencies": { - "@onflow/fcl-core": "1.13.4" + "@onflow/fcl-core": "1.14.0-cross-vm.0" } } diff --git a/packages/fcl/CHANGELOG.md b/packages/fcl/CHANGELOG.md index c07884309..a784108cc 100644 --- a/packages/fcl/CHANGELOG.md +++ b/packages/fcl/CHANGELOG.md @@ -1,5 +1,17 @@ # @onflow/fcl +## 1.14.0-cross-vm.0 + +### Minor Changes + +- [#2154](https://github.com/onflow/fcl-js/pull/2154) [`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724) Thanks [@jribbink](https://github.com/jribbink)! - FCL Ethereum Provider & Cross VM Support Alpha Release + +### Patch Changes + +- Updated dependencies [[`3fccbef7bbf985f19d9a9bae2638e538f126f754`](https://github.com/onflow/fcl-js/commit/3fccbef7bbf985f19d9a9bae2638e538f126f754), [`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724)]: + - @onflow/fcl-core@1.14.0-cross-vm.0 + - @onflow/fcl-wc@6.0.0-cross-vm.0 + ## 1.13.4 ### Patch Changes diff --git a/packages/fcl/package.json b/packages/fcl/package.json index 92abace9b..963cee385 100644 --- a/packages/fcl/package.json +++ b/packages/fcl/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl", - "version": "1.13.4", + "version": "1.14.0-cross-vm.0", "description": "Flow Client Library", "license": "Apache-2.0", "author": "Flow Foundation", @@ -19,7 +19,7 @@ } }, "devDependencies": { - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@onflow/typedefs": "1.4.0", "@types/estree": "^1.0.6", "@types/jest": "^29.5.13", @@ -49,8 +49,8 @@ "dependencies": { "@babel/runtime": "^7.25.7", "@onflow/config": "1.5.1", - "@onflow/fcl-core": "1.13.4", - "@onflow/fcl-wc": "5.5.4", + "@onflow/fcl-core": "1.14.0-cross-vm.0", + "@onflow/fcl-wc": "6.0.0-cross-vm.0", "@onflow/interaction": "0.0.11", "@onflow/rlp": "1.2.3", "@onflow/sdk": "1.5.6", diff --git a/packages/rlp/package.json b/packages/rlp/package.json index 12a74f103..476f4d359 100644 --- a/packages/rlp/package.json +++ b/packages/rlp/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 82b189a7c..bc03a0f1a 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -18,7 +18,7 @@ } }, "devDependencies": { - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@types/uuid": "^9.0.8", "eslint": "^8.57.1", "eslint-plugin-jsdoc": "^46.10.1", diff --git a/packages/transport-grpc/package.json b/packages/transport-grpc/package.json index c5e952606..647716750 100644 --- a/packages/transport-grpc/package.json +++ b/packages/transport-grpc/package.json @@ -13,7 +13,7 @@ "url": "https://github.com/onflow/fcl-js/issues" }, "devDependencies": { - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@onflow/sdk": "1.5.6", "jest": "^29.7.0" }, diff --git a/packages/transport-http/package.json b/packages/transport-http/package.json index a02b8180d..fe7c7134d 100644 --- a/packages/transport-http/package.json +++ b/packages/transport-http/package.json @@ -13,7 +13,7 @@ "url": "https://github.com/onflow/fcl-js/issues" }, "devDependencies": { - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@onflow/rlp": "1.2.3", "@onflow/sdk": "1.5.6", "@onflow/types": "1.4.1", diff --git a/packages/typedefs/package.json b/packages/typedefs/package.json index 4dca8a913..9595606dd 100644 --- a/packages/typedefs/package.json +++ b/packages/typedefs/package.json @@ -13,7 +13,7 @@ "url": "https://github.com/onflow/fcl-js/issues" }, "devDependencies": { - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@types/node": "^18.19.57", "eslint": "^8.57.1", "eslint-plugin-jsdoc": "^46.10.1", diff --git a/packages/types/package.json b/packages/types/package.json index a37cd739d..7626ba0ec 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", diff --git a/packages/util-actor/package.json b/packages/util-actor/package.json index 8f26146c0..79806d3e4 100644 --- a/packages/util-actor/package.json +++ b/packages/util-actor/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", diff --git a/packages/util-address/package.json b/packages/util-address/package.json index b4b352e0d..4b06f7c4c 100644 --- a/packages/util-address/package.json +++ b/packages/util-address/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@onflow/types": "1.4.1", "@types/jest": "^29.5.13", "@types/node": "^18.19.57", diff --git a/packages/util-encode-key/package.json b/packages/util-encode-key/package.json index ad3cf927a..cee0f1f88 100644 --- a/packages/util-encode-key/package.json +++ b/packages/util-encode-key/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@onflow/types": "1.4.1", "@types/jest": "^29.5.13", "@types/node": "^18.19.57", diff --git a/packages/util-invariant/package.json b/packages/util-invariant/package.json index 859cf64ca..fdd2ef2ec 100644 --- a/packages/util-invariant/package.json +++ b/packages/util-invariant/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@onflow/types": "1.4.1", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", diff --git a/packages/util-logger/package.json b/packages/util-logger/package.json index b2875a1f3..7ef915255 100644 --- a/packages/util-logger/package.json +++ b/packages/util-logger/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", diff --git a/packages/util-rpc/package.json b/packages/util-rpc/package.json index 81e426ed4..2c220b95a 100644 --- a/packages/util-rpc/package.json +++ b/packages/util-rpc/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", diff --git a/packages/util-semver/package.json b/packages/util-semver/package.json index 93e4c829e..49be5c06a 100644 --- a/packages/util-semver/package.json +++ b/packages/util-semver/package.json @@ -14,7 +14,7 @@ "start": "fcl-bundle --watch" }, "devDependencies": { - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "jest": "^29.7.0" }, "dependencies": { diff --git a/packages/util-template/package.json b/packages/util-template/package.json index c9be82f05..e75a7ef90 100644 --- a/packages/util-template/package.json +++ b/packages/util-template/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", diff --git a/packages/util-uid/package.json b/packages/util-uid/package.json index a89611dfc..7be9538ff 100644 --- a/packages/util-uid/package.json +++ b/packages/util-uid/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", From 2865953f3377e9b5742708eda29200266b42f679 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> Date: Fri, 21 Feb 2025 09:58:31 -0800 Subject: [PATCH 43/56] Revert "Version Packages (cross-vm) (#2155)" (#2156) This reverts commit 297bb09a01c65672205582f0e8702d647e186007. --- .changeset/pre.json | 5 +---- packages/config/package.json | 2 +- packages/fcl-bundle/CHANGELOG.md | 6 ------ packages/fcl-bundle/package.json | 2 +- packages/fcl-core/CHANGELOG.md | 10 ---------- packages/fcl-core/package.json | 4 ++-- packages/fcl-ethereum-provider/CHANGELOG.md | 10 ---------- packages/fcl-ethereum-provider/package.json | 8 ++++---- packages/fcl-rainbowkit-adapter/CHANGELOG.md | 11 ----------- packages/fcl-rainbowkit-adapter/package.json | 10 +++++----- packages/fcl-react-native/CHANGELOG.md | 7 ------- packages/fcl-react-native/package.json | 6 +++--- packages/fcl-wagmi-adapter/CHANGELOG.md | 10 ---------- packages/fcl-wagmi-adapter/package.json | 8 ++++---- packages/fcl-wc/CHANGELOG.md | 11 ----------- packages/fcl-wc/package.json | 6 +++--- packages/fcl/CHANGELOG.md | 12 ------------ packages/fcl/package.json | 8 ++++---- packages/rlp/package.json | 2 +- packages/sdk/package.json | 2 +- packages/transport-grpc/package.json | 2 +- packages/transport-http/package.json | 2 +- packages/typedefs/package.json | 2 +- packages/types/package.json | 2 +- packages/util-actor/package.json | 2 +- packages/util-address/package.json | 2 +- packages/util-encode-key/package.json | 2 +- packages/util-invariant/package.json | 2 +- packages/util-logger/package.json | 2 +- packages/util-rpc/package.json | 2 +- packages/util-semver/package.json | 2 +- packages/util-template/package.json | 2 +- packages/util-uid/package.json | 2 +- 33 files changed, 43 insertions(+), 123 deletions(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index dac5bb37b..560d6f545 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -28,8 +28,5 @@ "@onflow/util-template": "1.2.3", "@onflow/util-uid": "1.2.3" }, - "changesets": [ - "chilled-wombats-reply", - "shaggy-carpets-eat" - ] + "changesets": [] } diff --git a/packages/config/package.json b/packages/config/package.json index ceca38918..6285ced3a 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.7.0-cross-vm.0", + "@onflow/fcl-bundle": "1.6.0", "@types/estree": "^1.0.6", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", diff --git a/packages/fcl-bundle/CHANGELOG.md b/packages/fcl-bundle/CHANGELOG.md index 2d7ca5faf..0a0d1b3aa 100644 --- a/packages/fcl-bundle/CHANGELOG.md +++ b/packages/fcl-bundle/CHANGELOG.md @@ -1,11 +1,5 @@ # @onflow/fcl-bundle -## 1.7.0-cross-vm.0 - -### Minor Changes - -- [#2154](https://github.com/onflow/fcl-js/pull/2154) [`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724) Thanks [@jribbink](https://github.com/jribbink)! - FCL Ethereum Provider & Cross VM Support Alpha Release - ## 1.6.0 ### Minor Changes diff --git a/packages/fcl-bundle/package.json b/packages/fcl-bundle/package.json index 7ec3cbf1e..a46cc1bc9 100644 --- a/packages/fcl-bundle/package.json +++ b/packages/fcl-bundle/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl-bundle", - "version": "1.7.0-cross-vm.0", + "version": "1.6.0", "description": "FCL Bundler Tool", "license": "Apache-2.0", "author": "Flow Foundation", diff --git a/packages/fcl-core/CHANGELOG.md b/packages/fcl-core/CHANGELOG.md index 1a91f1497..74e40741e 100644 --- a/packages/fcl-core/CHANGELOG.md +++ b/packages/fcl-core/CHANGELOG.md @@ -1,15 +1,5 @@ # @onflow/fcl -## 1.14.0-cross-vm.0 - -### Minor Changes - -- [#2154](https://github.com/onflow/fcl-js/pull/2154) [`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724) Thanks [@jribbink](https://github.com/jribbink)! - FCL Ethereum Provider & Cross VM Support Alpha Release - -### Patch Changes - -- [#2065](https://github.com/onflow/fcl-js/pull/2065) [`3fccbef7bbf985f19d9a9bae2638e538f126f754`](https://github.com/onflow/fcl-js/commit/3fccbef7bbf985f19d9a9bae2638e538f126f754) Thanks [@jribbink](https://github.com/jribbink)! - Fix args type for `fcl.query` & `fcl.mutate` - ## 1.13.4 ### Patch Changes diff --git a/packages/fcl-core/package.json b/packages/fcl-core/package.json index cc15f535b..7356e3f82 100644 --- a/packages/fcl-core/package.json +++ b/packages/fcl-core/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl-core", - "version": "1.14.0-cross-vm.0", + "version": "1.13.4", "description": "Flow Client Library", "license": "Apache-2.0", "author": "Flow Foundation", @@ -19,7 +19,7 @@ } }, "devDependencies": { - "@onflow/fcl-bundle": "1.7.0-cross-vm.0", + "@onflow/fcl-bundle": "1.6.0", "@onflow/typedefs": "1.4.0", "@types/estree": "^1.0.6", "@types/jest": "^29.5.13", diff --git a/packages/fcl-ethereum-provider/CHANGELOG.md b/packages/fcl-ethereum-provider/CHANGELOG.md index 0b3946994..fdb6897bf 100644 --- a/packages/fcl-ethereum-provider/CHANGELOG.md +++ b/packages/fcl-ethereum-provider/CHANGELOG.md @@ -1,11 +1 @@ # @onflow/fcl-ethereum-provider - -## 1.0.0-cross-vm.0 - -### Patch Changes - -- [#2154](https://github.com/onflow/fcl-js/pull/2154) [`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724) Thanks [@jribbink](https://github.com/jribbink)! - FCL Ethereum Provider & Cross VM Support Alpha Release - -- Updated dependencies [[`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724)]: - - @onflow/fcl-wc@6.0.0-cross-vm.0 - - @onflow/fcl@1.14.0-cross-vm.0 diff --git a/packages/fcl-ethereum-provider/package.json b/packages/fcl-ethereum-provider/package.json index 962f22234..d690cd37f 100644 --- a/packages/fcl-ethereum-provider/package.json +++ b/packages/fcl-ethereum-provider/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl-ethereum-provider", - "version": "1.0.0-cross-vm.0", + "version": "0.0.0", "description": "Ethereum provider for FCL-compatible wallets", "license": "Apache-2.0", "author": "Dapper Labs ", @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.7.0-cross-vm.0", + "@onflow/fcl-bundle": "1.6.0", "@onflow/typedefs": "^1.4.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", @@ -40,7 +40,7 @@ "@ethersproject/bytes": "^5.7.0", "@ethersproject/hash": "^5.7.0", "@noble/hashes": "^1.7.1", - "@onflow/fcl-wc": "^6.0.0-cross-vm.0", + "@onflow/fcl-wc": "^5.5.4", "@onflow/rlp": "^1.2.3", "@walletconnect/ethereum-provider": "^2.18.0", "@walletconnect/jsonrpc-http-connection": "^1.0.8", @@ -50,6 +50,6 @@ "@walletconnect/utils": "^2.18.0" }, "peerDependencies": { - "@onflow/fcl": "^1.14.0-cross-vm.0" + "@onflow/fcl": "^1.13.4" } } diff --git a/packages/fcl-rainbowkit-adapter/CHANGELOG.md b/packages/fcl-rainbowkit-adapter/CHANGELOG.md index 250e9950b..fdb6897bf 100644 --- a/packages/fcl-rainbowkit-adapter/CHANGELOG.md +++ b/packages/fcl-rainbowkit-adapter/CHANGELOG.md @@ -1,12 +1 @@ # @onflow/fcl-ethereum-provider - -## 1.0.0-cross-vm.0 - -### Patch Changes - -- [#2154](https://github.com/onflow/fcl-js/pull/2154) [`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724) Thanks [@jribbink](https://github.com/jribbink)! - FCL Ethereum Provider & Cross VM Support Alpha Release - -- Updated dependencies [[`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724)]: - - @onflow/fcl@1.14.0-cross-vm.0 - - @onflow/fcl-ethereum-provider@1.0.0-cross-vm.0 - - @onflow/fcl-wagmi-adapter@1.0.0-cross-vm.0 diff --git a/packages/fcl-rainbowkit-adapter/package.json b/packages/fcl-rainbowkit-adapter/package.json index 69058327a..ef4ea572a 100644 --- a/packages/fcl-rainbowkit-adapter/package.json +++ b/packages/fcl-rainbowkit-adapter/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl-rainbowkit-adapter", - "version": "1.0.0-cross-vm.0", + "version": "0.0.0", "description": "Rainbowkit adapter for FCL-compatible wallets", "license": "Apache-2.0", "author": "Dapper Labs ", @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.7.0-cross-vm.0", + "@onflow/fcl-bundle": "1.6.0", "@onflow/typedefs": "^1.4.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", @@ -39,8 +39,8 @@ "@babel/runtime": "^7.25.7", "@ethersproject/bytes": "^5.7.0", "@ethersproject/hash": "^5.7.0", - "@onflow/fcl-ethereum-provider": "^1.0.0-cross-vm.0", - "@onflow/fcl-wagmi-adapter": "^1.0.0-cross-vm.0", + "@onflow/fcl-ethereum-provider": "^0.0.0", + "@onflow/fcl-wagmi-adapter": "^0.0.0", "@onflow/rlp": "^1.2.3", "@wagmi/core": "^2.16.3", "@walletconnect/jsonrpc-http-connection": "^1.0.8", @@ -50,7 +50,7 @@ "wagmi": "^2.14.11" }, "peerDependencies": { - "@onflow/fcl": "^1.14.0-cross-vm.0", + "@onflow/fcl": "^1.13.4", "@rainbow-me/rainbowkit": "^2.2.3" } } diff --git a/packages/fcl-react-native/CHANGELOG.md b/packages/fcl-react-native/CHANGELOG.md index e5b5f71f5..f3053de4d 100644 --- a/packages/fcl-react-native/CHANGELOG.md +++ b/packages/fcl-react-native/CHANGELOG.md @@ -1,12 +1,5 @@ # @onflow/fcl-react-native -## 1.9.12-cross-vm.0 - -### Patch Changes - -- Updated dependencies [[`3fccbef7bbf985f19d9a9bae2638e538f126f754`](https://github.com/onflow/fcl-js/commit/3fccbef7bbf985f19d9a9bae2638e538f126f754), [`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724)]: - - @onflow/fcl-core@1.14.0-cross-vm.0 - ## 1.9.11 ### Patch Changes diff --git a/packages/fcl-react-native/package.json b/packages/fcl-react-native/package.json index 374b25670..4e6d7ceb8 100644 --- a/packages/fcl-react-native/package.json +++ b/packages/fcl-react-native/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl-react-native", - "version": "1.9.12-cross-vm.0", + "version": "1.9.11", "description": "Flow Client Library", "license": "Apache-2.0", "author": "Flow Foundation", @@ -19,7 +19,7 @@ } }, "devDependencies": { - "@onflow/fcl-bundle": "1.7.0-cross-vm.0", + "@onflow/fcl-bundle": "1.6.0", "@onflow/typedefs": "1.4.0", "@types/estree": "^1.0.6", "@types/node": "^18.19.57", @@ -48,7 +48,7 @@ "dependencies": { "@babel/runtime": "^7.25.7", "@onflow/config": "1.5.1", - "@onflow/fcl-core": "1.14.0-cross-vm.0", + "@onflow/fcl-core": "1.13.4", "@onflow/interaction": "0.0.11", "@onflow/rlp": "1.2.3", "@onflow/sdk": "1.5.6", diff --git a/packages/fcl-wagmi-adapter/CHANGELOG.md b/packages/fcl-wagmi-adapter/CHANGELOG.md index 546883139..fdb6897bf 100644 --- a/packages/fcl-wagmi-adapter/CHANGELOG.md +++ b/packages/fcl-wagmi-adapter/CHANGELOG.md @@ -1,11 +1 @@ # @onflow/fcl-ethereum-provider - -## 1.0.0-cross-vm.0 - -### Patch Changes - -- [#2154](https://github.com/onflow/fcl-js/pull/2154) [`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724) Thanks [@jribbink](https://github.com/jribbink)! - FCL Ethereum Provider & Cross VM Support Alpha Release - -- Updated dependencies [[`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724)]: - - @onflow/fcl@1.14.0-cross-vm.0 - - @onflow/fcl-ethereum-provider@1.0.0-cross-vm.0 diff --git a/packages/fcl-wagmi-adapter/package.json b/packages/fcl-wagmi-adapter/package.json index d6a622009..43022de41 100644 --- a/packages/fcl-wagmi-adapter/package.json +++ b/packages/fcl-wagmi-adapter/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl-wagmi-adapter", - "version": "1.0.0-cross-vm.0", + "version": "0.0.0", "description": "Wagmi adapter for FCL-compatible wallets", "license": "Apache-2.0", "author": "Dapper Labs ", @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.7.0-cross-vm.0", + "@onflow/fcl-bundle": "1.6.0", "@onflow/typedefs": "^1.4.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", @@ -39,7 +39,7 @@ "@babel/runtime": "^7.25.7", "@ethersproject/bytes": "^5.7.0", "@ethersproject/hash": "^5.7.0", - "@onflow/fcl-ethereum-provider": "^1.0.0-cross-vm.0", + "@onflow/fcl-ethereum-provider": "^0.0.0", "@onflow/rlp": "^1.2.3", "@walletconnect/jsonrpc-http-connection": "^1.0.8", "@walletconnect/jsonrpc-provider": "^1.0.14", @@ -50,7 +50,7 @@ "viem": "^2.22.21" }, "peerDependencies": { - "@onflow/fcl": "^1.14.0-cross-vm.0", + "@onflow/fcl": "^1.13.4", "@wagmi/core": "^2.16.3" } } diff --git a/packages/fcl-wc/CHANGELOG.md b/packages/fcl-wc/CHANGELOG.md index e8e24009f..e306a0d7f 100644 --- a/packages/fcl-wc/CHANGELOG.md +++ b/packages/fcl-wc/CHANGELOG.md @@ -1,16 +1,5 @@ # @onflow/fcl-wc -## 6.0.0-cross-vm.0 - -### Major Changes - -- [#2154](https://github.com/onflow/fcl-js/pull/2154) [`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724) Thanks [@jribbink](https://github.com/jribbink)! - FCL Ethereum Provider & Cross VM Support Alpha Release - -### Patch Changes - -- Updated dependencies [[`3fccbef7bbf985f19d9a9bae2638e538f126f754`](https://github.com/onflow/fcl-js/commit/3fccbef7bbf985f19d9a9bae2638e538f126f754), [`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724)]: - - @onflow/fcl-core@1.14.0-cross-vm.0 - ## 5.5.4 ### Patch Changes diff --git a/packages/fcl-wc/package.json b/packages/fcl-wc/package.json index 56846d4ad..d7768e53e 100644 --- a/packages/fcl-wc/package.json +++ b/packages/fcl-wc/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl-wc", - "version": "6.0.0-cross-vm.0", + "version": "5.5.4", "description": "WalletConnect adapter for FCL", "license": "Apache-2.0", "author": "Flow Foundation", @@ -30,7 +30,7 @@ "devDependencies": { "@babel/plugin-transform-react-jsx": "^7.25.9", "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.7.0-cross-vm.0", + "@onflow/fcl-bundle": "1.6.0", "@onflow/typedefs": "^1.4.0", "autoprefixer": "^10.4.20", "eslint": "^8.57.1", @@ -55,6 +55,6 @@ "tailwindcss": "^3.4.14" }, "peerDependencies": { - "@onflow/fcl-core": "1.14.0-cross-vm.0" + "@onflow/fcl-core": "1.13.4" } } diff --git a/packages/fcl/CHANGELOG.md b/packages/fcl/CHANGELOG.md index a784108cc..c07884309 100644 --- a/packages/fcl/CHANGELOG.md +++ b/packages/fcl/CHANGELOG.md @@ -1,17 +1,5 @@ # @onflow/fcl -## 1.14.0-cross-vm.0 - -### Minor Changes - -- [#2154](https://github.com/onflow/fcl-js/pull/2154) [`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724) Thanks [@jribbink](https://github.com/jribbink)! - FCL Ethereum Provider & Cross VM Support Alpha Release - -### Patch Changes - -- Updated dependencies [[`3fccbef7bbf985f19d9a9bae2638e538f126f754`](https://github.com/onflow/fcl-js/commit/3fccbef7bbf985f19d9a9bae2638e538f126f754), [`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724)]: - - @onflow/fcl-core@1.14.0-cross-vm.0 - - @onflow/fcl-wc@6.0.0-cross-vm.0 - ## 1.13.4 ### Patch Changes diff --git a/packages/fcl/package.json b/packages/fcl/package.json index 963cee385..92abace9b 100644 --- a/packages/fcl/package.json +++ b/packages/fcl/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl", - "version": "1.14.0-cross-vm.0", + "version": "1.13.4", "description": "Flow Client Library", "license": "Apache-2.0", "author": "Flow Foundation", @@ -19,7 +19,7 @@ } }, "devDependencies": { - "@onflow/fcl-bundle": "1.7.0-cross-vm.0", + "@onflow/fcl-bundle": "1.6.0", "@onflow/typedefs": "1.4.0", "@types/estree": "^1.0.6", "@types/jest": "^29.5.13", @@ -49,8 +49,8 @@ "dependencies": { "@babel/runtime": "^7.25.7", "@onflow/config": "1.5.1", - "@onflow/fcl-core": "1.14.0-cross-vm.0", - "@onflow/fcl-wc": "6.0.0-cross-vm.0", + "@onflow/fcl-core": "1.13.4", + "@onflow/fcl-wc": "5.5.4", "@onflow/interaction": "0.0.11", "@onflow/rlp": "1.2.3", "@onflow/sdk": "1.5.6", diff --git a/packages/rlp/package.json b/packages/rlp/package.json index 476f4d359..12a74f103 100644 --- a/packages/rlp/package.json +++ b/packages/rlp/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.7.0-cross-vm.0", + "@onflow/fcl-bundle": "1.6.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", diff --git a/packages/sdk/package.json b/packages/sdk/package.json index bc03a0f1a..82b189a7c 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -18,7 +18,7 @@ } }, "devDependencies": { - "@onflow/fcl-bundle": "1.7.0-cross-vm.0", + "@onflow/fcl-bundle": "1.6.0", "@types/uuid": "^9.0.8", "eslint": "^8.57.1", "eslint-plugin-jsdoc": "^46.10.1", diff --git a/packages/transport-grpc/package.json b/packages/transport-grpc/package.json index 647716750..c5e952606 100644 --- a/packages/transport-grpc/package.json +++ b/packages/transport-grpc/package.json @@ -13,7 +13,7 @@ "url": "https://github.com/onflow/fcl-js/issues" }, "devDependencies": { - "@onflow/fcl-bundle": "1.7.0-cross-vm.0", + "@onflow/fcl-bundle": "1.6.0", "@onflow/sdk": "1.5.6", "jest": "^29.7.0" }, diff --git a/packages/transport-http/package.json b/packages/transport-http/package.json index fe7c7134d..a02b8180d 100644 --- a/packages/transport-http/package.json +++ b/packages/transport-http/package.json @@ -13,7 +13,7 @@ "url": "https://github.com/onflow/fcl-js/issues" }, "devDependencies": { - "@onflow/fcl-bundle": "1.7.0-cross-vm.0", + "@onflow/fcl-bundle": "1.6.0", "@onflow/rlp": "1.2.3", "@onflow/sdk": "1.5.6", "@onflow/types": "1.4.1", diff --git a/packages/typedefs/package.json b/packages/typedefs/package.json index 9595606dd..4dca8a913 100644 --- a/packages/typedefs/package.json +++ b/packages/typedefs/package.json @@ -13,7 +13,7 @@ "url": "https://github.com/onflow/fcl-js/issues" }, "devDependencies": { - "@onflow/fcl-bundle": "1.7.0-cross-vm.0", + "@onflow/fcl-bundle": "1.6.0", "@types/node": "^18.19.57", "eslint": "^8.57.1", "eslint-plugin-jsdoc": "^46.10.1", diff --git a/packages/types/package.json b/packages/types/package.json index 7626ba0ec..a37cd739d 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.7.0-cross-vm.0", + "@onflow/fcl-bundle": "1.6.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", diff --git a/packages/util-actor/package.json b/packages/util-actor/package.json index 79806d3e4..8f26146c0 100644 --- a/packages/util-actor/package.json +++ b/packages/util-actor/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.7.0-cross-vm.0", + "@onflow/fcl-bundle": "1.6.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", diff --git a/packages/util-address/package.json b/packages/util-address/package.json index 4b06f7c4c..b4b352e0d 100644 --- a/packages/util-address/package.json +++ b/packages/util-address/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.7.0-cross-vm.0", + "@onflow/fcl-bundle": "1.6.0", "@onflow/types": "1.4.1", "@types/jest": "^29.5.13", "@types/node": "^18.19.57", diff --git a/packages/util-encode-key/package.json b/packages/util-encode-key/package.json index cee0f1f88..ad3cf927a 100644 --- a/packages/util-encode-key/package.json +++ b/packages/util-encode-key/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.7.0-cross-vm.0", + "@onflow/fcl-bundle": "1.6.0", "@onflow/types": "1.4.1", "@types/jest": "^29.5.13", "@types/node": "^18.19.57", diff --git a/packages/util-invariant/package.json b/packages/util-invariant/package.json index fdd2ef2ec..859cf64ca 100644 --- a/packages/util-invariant/package.json +++ b/packages/util-invariant/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.7.0-cross-vm.0", + "@onflow/fcl-bundle": "1.6.0", "@onflow/types": "1.4.1", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", diff --git a/packages/util-logger/package.json b/packages/util-logger/package.json index 7ef915255..b2875a1f3 100644 --- a/packages/util-logger/package.json +++ b/packages/util-logger/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.7.0-cross-vm.0", + "@onflow/fcl-bundle": "1.6.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", diff --git a/packages/util-rpc/package.json b/packages/util-rpc/package.json index 2c220b95a..81e426ed4 100644 --- a/packages/util-rpc/package.json +++ b/packages/util-rpc/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.7.0-cross-vm.0", + "@onflow/fcl-bundle": "1.6.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", diff --git a/packages/util-semver/package.json b/packages/util-semver/package.json index 49be5c06a..93e4c829e 100644 --- a/packages/util-semver/package.json +++ b/packages/util-semver/package.json @@ -14,7 +14,7 @@ "start": "fcl-bundle --watch" }, "devDependencies": { - "@onflow/fcl-bundle": "1.7.0-cross-vm.0", + "@onflow/fcl-bundle": "1.6.0", "jest": "^29.7.0" }, "dependencies": { diff --git a/packages/util-template/package.json b/packages/util-template/package.json index e75a7ef90..c9be82f05 100644 --- a/packages/util-template/package.json +++ b/packages/util-template/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.7.0-cross-vm.0", + "@onflow/fcl-bundle": "1.6.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", diff --git a/packages/util-uid/package.json b/packages/util-uid/package.json index 7be9538ff..a89611dfc 100644 --- a/packages/util-uid/package.json +++ b/packages/util-uid/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.7.0-cross-vm.0", + "@onflow/fcl-bundle": "1.6.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", From 1790278e6524cb480e1eaad282d812e10907a6f6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Feb 2025 10:25:19 -0800 Subject: [PATCH 44/56] Version Packages (cross-vm) (#2157) * Version Packages (cross-vm) * Switch to v0.0.1 release --------- Co-authored-by: github-actions[bot] Co-authored-by: Jordan Ribbink --- .changeset/pre.json | 5 ++++- packages/config/package.json | 2 +- packages/fcl-bundle/CHANGELOG.md | 6 ++++++ packages/fcl-bundle/package.json | 2 +- packages/fcl-core/CHANGELOG.md | 10 ++++++++++ packages/fcl-core/package.json | 4 ++-- packages/fcl-ethereum-provider/CHANGELOG.md | 10 ++++++++++ packages/fcl-ethereum-provider/package.json | 8 ++++---- packages/fcl-rainbowkit-adapter/CHANGELOG.md | 11 +++++++++++ packages/fcl-rainbowkit-adapter/package.json | 10 +++++----- packages/fcl-react-native/CHANGELOG.md | 7 +++++++ packages/fcl-react-native/package.json | 6 +++--- packages/fcl-wagmi-adapter/CHANGELOG.md | 10 ++++++++++ packages/fcl-wagmi-adapter/package.json | 8 ++++---- packages/fcl-wc/CHANGELOG.md | 11 +++++++++++ packages/fcl-wc/package.json | 6 +++--- packages/fcl/CHANGELOG.md | 12 ++++++++++++ packages/fcl/package.json | 8 ++++---- packages/rlp/package.json | 2 +- packages/sdk/package.json | 2 +- packages/transport-grpc/package.json | 2 +- packages/transport-http/package.json | 2 +- packages/typedefs/package.json | 2 +- packages/types/package.json | 2 +- packages/util-actor/package.json | 2 +- packages/util-address/package.json | 2 +- packages/util-encode-key/package.json | 2 +- packages/util-invariant/package.json | 2 +- packages/util-logger/package.json | 2 +- packages/util-rpc/package.json | 2 +- packages/util-semver/package.json | 2 +- packages/util-template/package.json | 2 +- packages/util-uid/package.json | 2 +- 33 files changed, 123 insertions(+), 43 deletions(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index 560d6f545..dac5bb37b 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -28,5 +28,8 @@ "@onflow/util-template": "1.2.3", "@onflow/util-uid": "1.2.3" }, - "changesets": [] + "changesets": [ + "chilled-wombats-reply", + "shaggy-carpets-eat" + ] } diff --git a/packages/config/package.json b/packages/config/package.json index 6285ced3a..ceca38918 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@types/estree": "^1.0.6", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", diff --git a/packages/fcl-bundle/CHANGELOG.md b/packages/fcl-bundle/CHANGELOG.md index 0a0d1b3aa..2d7ca5faf 100644 --- a/packages/fcl-bundle/CHANGELOG.md +++ b/packages/fcl-bundle/CHANGELOG.md @@ -1,5 +1,11 @@ # @onflow/fcl-bundle +## 1.7.0-cross-vm.0 + +### Minor Changes + +- [#2154](https://github.com/onflow/fcl-js/pull/2154) [`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724) Thanks [@jribbink](https://github.com/jribbink)! - FCL Ethereum Provider & Cross VM Support Alpha Release + ## 1.6.0 ### Minor Changes diff --git a/packages/fcl-bundle/package.json b/packages/fcl-bundle/package.json index a46cc1bc9..7ec3cbf1e 100644 --- a/packages/fcl-bundle/package.json +++ b/packages/fcl-bundle/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl-bundle", - "version": "1.6.0", + "version": "1.7.0-cross-vm.0", "description": "FCL Bundler Tool", "license": "Apache-2.0", "author": "Flow Foundation", diff --git a/packages/fcl-core/CHANGELOG.md b/packages/fcl-core/CHANGELOG.md index 74e40741e..1a91f1497 100644 --- a/packages/fcl-core/CHANGELOG.md +++ b/packages/fcl-core/CHANGELOG.md @@ -1,5 +1,15 @@ # @onflow/fcl +## 1.14.0-cross-vm.0 + +### Minor Changes + +- [#2154](https://github.com/onflow/fcl-js/pull/2154) [`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724) Thanks [@jribbink](https://github.com/jribbink)! - FCL Ethereum Provider & Cross VM Support Alpha Release + +### Patch Changes + +- [#2065](https://github.com/onflow/fcl-js/pull/2065) [`3fccbef7bbf985f19d9a9bae2638e538f126f754`](https://github.com/onflow/fcl-js/commit/3fccbef7bbf985f19d9a9bae2638e538f126f754) Thanks [@jribbink](https://github.com/jribbink)! - Fix args type for `fcl.query` & `fcl.mutate` + ## 1.13.4 ### Patch Changes diff --git a/packages/fcl-core/package.json b/packages/fcl-core/package.json index 7356e3f82..cc15f535b 100644 --- a/packages/fcl-core/package.json +++ b/packages/fcl-core/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl-core", - "version": "1.13.4", + "version": "1.14.0-cross-vm.0", "description": "Flow Client Library", "license": "Apache-2.0", "author": "Flow Foundation", @@ -19,7 +19,7 @@ } }, "devDependencies": { - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@onflow/typedefs": "1.4.0", "@types/estree": "^1.0.6", "@types/jest": "^29.5.13", diff --git a/packages/fcl-ethereum-provider/CHANGELOG.md b/packages/fcl-ethereum-provider/CHANGELOG.md index fdb6897bf..18ef374bf 100644 --- a/packages/fcl-ethereum-provider/CHANGELOG.md +++ b/packages/fcl-ethereum-provider/CHANGELOG.md @@ -1 +1,11 @@ # @onflow/fcl-ethereum-provider + +## 0.0.1-cross-vm.0 + +### Patch Changes + +- [#2154](https://github.com/onflow/fcl-js/pull/2154) [`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724) Thanks [@jribbink](https://github.com/jribbink)! - FCL Ethereum Provider & Cross VM Support Alpha Release + +- Updated dependencies [[`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724)]: + - @onflow/fcl-wc@6.0.0-cross-vm.0 + - @onflow/fcl@1.14.0-cross-vm.0 diff --git a/packages/fcl-ethereum-provider/package.json b/packages/fcl-ethereum-provider/package.json index d690cd37f..b744e6d3f 100644 --- a/packages/fcl-ethereum-provider/package.json +++ b/packages/fcl-ethereum-provider/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl-ethereum-provider", - "version": "0.0.0", + "version": "0.0.1-cross-vm.0", "description": "Ethereum provider for FCL-compatible wallets", "license": "Apache-2.0", "author": "Dapper Labs ", @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@onflow/typedefs": "^1.4.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", @@ -40,7 +40,7 @@ "@ethersproject/bytes": "^5.7.0", "@ethersproject/hash": "^5.7.0", "@noble/hashes": "^1.7.1", - "@onflow/fcl-wc": "^5.5.4", + "@onflow/fcl-wc": "^6.0.0-cross-vm.0", "@onflow/rlp": "^1.2.3", "@walletconnect/ethereum-provider": "^2.18.0", "@walletconnect/jsonrpc-http-connection": "^1.0.8", @@ -50,6 +50,6 @@ "@walletconnect/utils": "^2.18.0" }, "peerDependencies": { - "@onflow/fcl": "^1.13.4" + "@onflow/fcl": "^1.14.0-cross-vm.0" } } diff --git a/packages/fcl-rainbowkit-adapter/CHANGELOG.md b/packages/fcl-rainbowkit-adapter/CHANGELOG.md index fdb6897bf..5f1e3fae8 100644 --- a/packages/fcl-rainbowkit-adapter/CHANGELOG.md +++ b/packages/fcl-rainbowkit-adapter/CHANGELOG.md @@ -1 +1,12 @@ # @onflow/fcl-ethereum-provider + +## 0.0.1-cross-vm.0 + +### Patch Changes + +- [#2154](https://github.com/onflow/fcl-js/pull/2154) [`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724) Thanks [@jribbink](https://github.com/jribbink)! - FCL Ethereum Provider & Cross VM Support Alpha Release + +- Updated dependencies [[`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724)]: + - @onflow/fcl@1.14.0-cross-vm.0 + - @onflow/fcl-ethereum-provider@0.0.1-cross-vm.0 + - @onflow/fcl-wagmi-adapter@0.0.1-cross-vm.0 diff --git a/packages/fcl-rainbowkit-adapter/package.json b/packages/fcl-rainbowkit-adapter/package.json index ef4ea572a..4cac0e335 100644 --- a/packages/fcl-rainbowkit-adapter/package.json +++ b/packages/fcl-rainbowkit-adapter/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl-rainbowkit-adapter", - "version": "0.0.0", + "version": "0.0.1-cross-vm.0", "description": "Rainbowkit adapter for FCL-compatible wallets", "license": "Apache-2.0", "author": "Dapper Labs ", @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@onflow/typedefs": "^1.4.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", @@ -39,8 +39,8 @@ "@babel/runtime": "^7.25.7", "@ethersproject/bytes": "^5.7.0", "@ethersproject/hash": "^5.7.0", - "@onflow/fcl-ethereum-provider": "^0.0.0", - "@onflow/fcl-wagmi-adapter": "^0.0.0", + "@onflow/fcl-ethereum-provider": "^0.0.1-cross-vm.0", + "@onflow/fcl-wagmi-adapter": "^0.0.1-cross-vm.0", "@onflow/rlp": "^1.2.3", "@wagmi/core": "^2.16.3", "@walletconnect/jsonrpc-http-connection": "^1.0.8", @@ -50,7 +50,7 @@ "wagmi": "^2.14.11" }, "peerDependencies": { - "@onflow/fcl": "^1.13.4", + "@onflow/fcl": "^1.14.0-cross-vm.0", "@rainbow-me/rainbowkit": "^2.2.3" } } diff --git a/packages/fcl-react-native/CHANGELOG.md b/packages/fcl-react-native/CHANGELOG.md index f3053de4d..e5b5f71f5 100644 --- a/packages/fcl-react-native/CHANGELOG.md +++ b/packages/fcl-react-native/CHANGELOG.md @@ -1,5 +1,12 @@ # @onflow/fcl-react-native +## 1.9.12-cross-vm.0 + +### Patch Changes + +- Updated dependencies [[`3fccbef7bbf985f19d9a9bae2638e538f126f754`](https://github.com/onflow/fcl-js/commit/3fccbef7bbf985f19d9a9bae2638e538f126f754), [`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724)]: + - @onflow/fcl-core@1.14.0-cross-vm.0 + ## 1.9.11 ### Patch Changes diff --git a/packages/fcl-react-native/package.json b/packages/fcl-react-native/package.json index 4e6d7ceb8..374b25670 100644 --- a/packages/fcl-react-native/package.json +++ b/packages/fcl-react-native/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl-react-native", - "version": "1.9.11", + "version": "1.9.12-cross-vm.0", "description": "Flow Client Library", "license": "Apache-2.0", "author": "Flow Foundation", @@ -19,7 +19,7 @@ } }, "devDependencies": { - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@onflow/typedefs": "1.4.0", "@types/estree": "^1.0.6", "@types/node": "^18.19.57", @@ -48,7 +48,7 @@ "dependencies": { "@babel/runtime": "^7.25.7", "@onflow/config": "1.5.1", - "@onflow/fcl-core": "1.13.4", + "@onflow/fcl-core": "1.14.0-cross-vm.0", "@onflow/interaction": "0.0.11", "@onflow/rlp": "1.2.3", "@onflow/sdk": "1.5.6", diff --git a/packages/fcl-wagmi-adapter/CHANGELOG.md b/packages/fcl-wagmi-adapter/CHANGELOG.md index fdb6897bf..3f08f9adf 100644 --- a/packages/fcl-wagmi-adapter/CHANGELOG.md +++ b/packages/fcl-wagmi-adapter/CHANGELOG.md @@ -1 +1,11 @@ # @onflow/fcl-ethereum-provider + +## 0.0.1-cross-vm.0 + +### Patch Changes + +- [#2154](https://github.com/onflow/fcl-js/pull/2154) [`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724) Thanks [@jribbink](https://github.com/jribbink)! - FCL Ethereum Provider & Cross VM Support Alpha Release + +- Updated dependencies [[`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724)]: + - @onflow/fcl@1.14.0-cross-vm.0 + - @onflow/fcl-ethereum-provider@0.0.1-cross-vm.0 diff --git a/packages/fcl-wagmi-adapter/package.json b/packages/fcl-wagmi-adapter/package.json index 43022de41..998f3bfb6 100644 --- a/packages/fcl-wagmi-adapter/package.json +++ b/packages/fcl-wagmi-adapter/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl-wagmi-adapter", - "version": "0.0.0", + "version": "0.0.1-cross-vm.0", "description": "Wagmi adapter for FCL-compatible wallets", "license": "Apache-2.0", "author": "Dapper Labs ", @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@onflow/typedefs": "^1.4.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", @@ -39,7 +39,7 @@ "@babel/runtime": "^7.25.7", "@ethersproject/bytes": "^5.7.0", "@ethersproject/hash": "^5.7.0", - "@onflow/fcl-ethereum-provider": "^0.0.0", + "@onflow/fcl-ethereum-provider": "^0.0.1-cross-vm.0", "@onflow/rlp": "^1.2.3", "@walletconnect/jsonrpc-http-connection": "^1.0.8", "@walletconnect/jsonrpc-provider": "^1.0.14", @@ -50,7 +50,7 @@ "viem": "^2.22.21" }, "peerDependencies": { - "@onflow/fcl": "^1.13.4", + "@onflow/fcl": "^1.14.0-cross-vm.0", "@wagmi/core": "^2.16.3" } } diff --git a/packages/fcl-wc/CHANGELOG.md b/packages/fcl-wc/CHANGELOG.md index e306a0d7f..e8e24009f 100644 --- a/packages/fcl-wc/CHANGELOG.md +++ b/packages/fcl-wc/CHANGELOG.md @@ -1,5 +1,16 @@ # @onflow/fcl-wc +## 6.0.0-cross-vm.0 + +### Major Changes + +- [#2154](https://github.com/onflow/fcl-js/pull/2154) [`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724) Thanks [@jribbink](https://github.com/jribbink)! - FCL Ethereum Provider & Cross VM Support Alpha Release + +### Patch Changes + +- Updated dependencies [[`3fccbef7bbf985f19d9a9bae2638e538f126f754`](https://github.com/onflow/fcl-js/commit/3fccbef7bbf985f19d9a9bae2638e538f126f754), [`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724)]: + - @onflow/fcl-core@1.14.0-cross-vm.0 + ## 5.5.4 ### Patch Changes diff --git a/packages/fcl-wc/package.json b/packages/fcl-wc/package.json index d7768e53e..56846d4ad 100644 --- a/packages/fcl-wc/package.json +++ b/packages/fcl-wc/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl-wc", - "version": "5.5.4", + "version": "6.0.0-cross-vm.0", "description": "WalletConnect adapter for FCL", "license": "Apache-2.0", "author": "Flow Foundation", @@ -30,7 +30,7 @@ "devDependencies": { "@babel/plugin-transform-react-jsx": "^7.25.9", "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@onflow/typedefs": "^1.4.0", "autoprefixer": "^10.4.20", "eslint": "^8.57.1", @@ -55,6 +55,6 @@ "tailwindcss": "^3.4.14" }, "peerDependencies": { - "@onflow/fcl-core": "1.13.4" + "@onflow/fcl-core": "1.14.0-cross-vm.0" } } diff --git a/packages/fcl/CHANGELOG.md b/packages/fcl/CHANGELOG.md index c07884309..a784108cc 100644 --- a/packages/fcl/CHANGELOG.md +++ b/packages/fcl/CHANGELOG.md @@ -1,5 +1,17 @@ # @onflow/fcl +## 1.14.0-cross-vm.0 + +### Minor Changes + +- [#2154](https://github.com/onflow/fcl-js/pull/2154) [`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724) Thanks [@jribbink](https://github.com/jribbink)! - FCL Ethereum Provider & Cross VM Support Alpha Release + +### Patch Changes + +- Updated dependencies [[`3fccbef7bbf985f19d9a9bae2638e538f126f754`](https://github.com/onflow/fcl-js/commit/3fccbef7bbf985f19d9a9bae2638e538f126f754), [`e8f5585cd029716940c55e72fc53dc9563fec724`](https://github.com/onflow/fcl-js/commit/e8f5585cd029716940c55e72fc53dc9563fec724)]: + - @onflow/fcl-core@1.14.0-cross-vm.0 + - @onflow/fcl-wc@6.0.0-cross-vm.0 + ## 1.13.4 ### Patch Changes diff --git a/packages/fcl/package.json b/packages/fcl/package.json index 92abace9b..963cee385 100644 --- a/packages/fcl/package.json +++ b/packages/fcl/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl", - "version": "1.13.4", + "version": "1.14.0-cross-vm.0", "description": "Flow Client Library", "license": "Apache-2.0", "author": "Flow Foundation", @@ -19,7 +19,7 @@ } }, "devDependencies": { - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@onflow/typedefs": "1.4.0", "@types/estree": "^1.0.6", "@types/jest": "^29.5.13", @@ -49,8 +49,8 @@ "dependencies": { "@babel/runtime": "^7.25.7", "@onflow/config": "1.5.1", - "@onflow/fcl-core": "1.13.4", - "@onflow/fcl-wc": "5.5.4", + "@onflow/fcl-core": "1.14.0-cross-vm.0", + "@onflow/fcl-wc": "6.0.0-cross-vm.0", "@onflow/interaction": "0.0.11", "@onflow/rlp": "1.2.3", "@onflow/sdk": "1.5.6", diff --git a/packages/rlp/package.json b/packages/rlp/package.json index 12a74f103..476f4d359 100644 --- a/packages/rlp/package.json +++ b/packages/rlp/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 82b189a7c..bc03a0f1a 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -18,7 +18,7 @@ } }, "devDependencies": { - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@types/uuid": "^9.0.8", "eslint": "^8.57.1", "eslint-plugin-jsdoc": "^46.10.1", diff --git a/packages/transport-grpc/package.json b/packages/transport-grpc/package.json index c5e952606..647716750 100644 --- a/packages/transport-grpc/package.json +++ b/packages/transport-grpc/package.json @@ -13,7 +13,7 @@ "url": "https://github.com/onflow/fcl-js/issues" }, "devDependencies": { - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@onflow/sdk": "1.5.6", "jest": "^29.7.0" }, diff --git a/packages/transport-http/package.json b/packages/transport-http/package.json index a02b8180d..fe7c7134d 100644 --- a/packages/transport-http/package.json +++ b/packages/transport-http/package.json @@ -13,7 +13,7 @@ "url": "https://github.com/onflow/fcl-js/issues" }, "devDependencies": { - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@onflow/rlp": "1.2.3", "@onflow/sdk": "1.5.6", "@onflow/types": "1.4.1", diff --git a/packages/typedefs/package.json b/packages/typedefs/package.json index 4dca8a913..9595606dd 100644 --- a/packages/typedefs/package.json +++ b/packages/typedefs/package.json @@ -13,7 +13,7 @@ "url": "https://github.com/onflow/fcl-js/issues" }, "devDependencies": { - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@types/node": "^18.19.57", "eslint": "^8.57.1", "eslint-plugin-jsdoc": "^46.10.1", diff --git a/packages/types/package.json b/packages/types/package.json index a37cd739d..7626ba0ec 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", diff --git a/packages/util-actor/package.json b/packages/util-actor/package.json index 8f26146c0..79806d3e4 100644 --- a/packages/util-actor/package.json +++ b/packages/util-actor/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", diff --git a/packages/util-address/package.json b/packages/util-address/package.json index b4b352e0d..4b06f7c4c 100644 --- a/packages/util-address/package.json +++ b/packages/util-address/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@onflow/types": "1.4.1", "@types/jest": "^29.5.13", "@types/node": "^18.19.57", diff --git a/packages/util-encode-key/package.json b/packages/util-encode-key/package.json index ad3cf927a..cee0f1f88 100644 --- a/packages/util-encode-key/package.json +++ b/packages/util-encode-key/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@onflow/types": "1.4.1", "@types/jest": "^29.5.13", "@types/node": "^18.19.57", diff --git a/packages/util-invariant/package.json b/packages/util-invariant/package.json index 859cf64ca..fdd2ef2ec 100644 --- a/packages/util-invariant/package.json +++ b/packages/util-invariant/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@onflow/types": "1.4.1", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", diff --git a/packages/util-logger/package.json b/packages/util-logger/package.json index b2875a1f3..7ef915255 100644 --- a/packages/util-logger/package.json +++ b/packages/util-logger/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", diff --git a/packages/util-rpc/package.json b/packages/util-rpc/package.json index 81e426ed4..2c220b95a 100644 --- a/packages/util-rpc/package.json +++ b/packages/util-rpc/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", diff --git a/packages/util-semver/package.json b/packages/util-semver/package.json index 93e4c829e..49be5c06a 100644 --- a/packages/util-semver/package.json +++ b/packages/util-semver/package.json @@ -14,7 +14,7 @@ "start": "fcl-bundle --watch" }, "devDependencies": { - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "jest": "^29.7.0" }, "dependencies": { diff --git a/packages/util-template/package.json b/packages/util-template/package.json index c9be82f05..e75a7ef90 100644 --- a/packages/util-template/package.json +++ b/packages/util-template/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", diff --git a/packages/util-uid/package.json b/packages/util-uid/package.json index a89611dfc..7be9538ff 100644 --- a/packages/util-uid/package.json +++ b/packages/util-uid/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", From 542b87c1fc7a2207902698844a58e676d0e3dafe Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Fri, 21 Feb 2025 10:56:13 -0800 Subject: [PATCH 45/56] Disable version bump check --- .github/workflows/integrate.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/integrate.yml b/.github/workflows/integrate.yml index f0c62eaa4..25c485792 100644 --- a/.github/workflows/integrate.yml +++ b/.github/workflows/integrate.yml @@ -23,7 +23,7 @@ jobs: # https://github.com/changesets/changesets/issues/1011 # https://github.com/changesets/changesets/issues/960 # https://github.com/changesets/changesets/issues/822 - - name: Prevent Major Version Bumps - run: node ./.github/scripts/prevent-major-bumps.js + #- name: Prevent Major Version Bumps + # run: node ./.github/scripts/prevent-major-bumps.js - run: make ci From 68362363ad9200ccc9e69c125ad4305a7ddaf85d Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Fri, 21 Feb 2025 11:01:46 -0800 Subject: [PATCH 46/56] Fix tests --- .../src/fcl-rainbowkit-adapter.test.ts | 5 +++++ packages/fcl-wagmi-adapter/src/fcl-wagmi-adapter.test.ts | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 packages/fcl-rainbowkit-adapter/src/fcl-rainbowkit-adapter.test.ts create mode 100644 packages/fcl-wagmi-adapter/src/fcl-wagmi-adapter.test.ts diff --git a/packages/fcl-rainbowkit-adapter/src/fcl-rainbowkit-adapter.test.ts b/packages/fcl-rainbowkit-adapter/src/fcl-rainbowkit-adapter.test.ts new file mode 100644 index 000000000..6cd532865 --- /dev/null +++ b/packages/fcl-rainbowkit-adapter/src/fcl-rainbowkit-adapter.test.ts @@ -0,0 +1,5 @@ +describe("fcl rainbowkit adapter", () => { + it("should be tested", () => { + expect(true).toBe(true) + }) +}) diff --git a/packages/fcl-wagmi-adapter/src/fcl-wagmi-adapter.test.ts b/packages/fcl-wagmi-adapter/src/fcl-wagmi-adapter.test.ts new file mode 100644 index 000000000..d82bac0ee --- /dev/null +++ b/packages/fcl-wagmi-adapter/src/fcl-wagmi-adapter.test.ts @@ -0,0 +1,5 @@ +describe("fcl wagmi adapter", () => { + it("should be tested", () => { + expect(true).toBe(true) + }) +}) From ea6d6d474eb732a0ef824fd28e3a871e3a157be6 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Fri, 21 Feb 2025 12:52:35 -0800 Subject: [PATCH 47/56] Fix code formatting --- .changeset/pre.json | 5 +-- package-lock.json | 84 ++++++++++++++++++++++----------------------- 2 files changed, 43 insertions(+), 46 deletions(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index dac5bb37b..5bc4aec75 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -28,8 +28,5 @@ "@onflow/util-template": "1.2.3", "@onflow/util-uid": "1.2.3" }, - "changesets": [ - "chilled-wombats-reply", - "shaggy-carpets-eat" - ] + "changesets": ["chilled-wombats-reply", "shaggy-carpets-eat"] } diff --git a/package-lock.json b/package-lock.json index 7190656c0..9b9ea9f1d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31200,7 +31200,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@types/estree": "^1.0.6", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", @@ -31225,13 +31225,13 @@ }, "packages/fcl": { "name": "@onflow/fcl", - "version": "1.13.4", + "version": "1.14.0-cross-vm.0", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.25.7", "@onflow/config": "1.5.1", - "@onflow/fcl-core": "1.13.4", - "@onflow/fcl-wc": "5.5.4", + "@onflow/fcl-core": "1.14.0-cross-vm.0", + "@onflow/fcl-wc": "6.0.0-cross-vm.0", "@onflow/interaction": "0.0.11", "@onflow/rlp": "1.2.3", "@onflow/sdk": "1.5.6", @@ -31251,7 +31251,7 @@ "sha3": "^2.1.4" }, "devDependencies": { - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@onflow/typedefs": "1.4.0", "@types/estree": "^1.0.6", "@types/jest": "^29.5.13", @@ -31265,7 +31265,7 @@ }, "packages/fcl-bundle": { "name": "@onflow/fcl-bundle", - "version": "1.6.0", + "version": "1.7.0-cross-vm.0", "license": "Apache-2.0", "dependencies": { "@babel/plugin-transform-runtime": "^7.25.7", @@ -31299,7 +31299,7 @@ }, "packages/fcl-core": { "name": "@onflow/fcl-core", - "version": "1.13.4", + "version": "1.14.0-cross-vm.0", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.25.7", @@ -31321,7 +31321,7 @@ "cross-fetch": "^4.0.0" }, "devDependencies": { - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@onflow/typedefs": "1.4.0", "@types/estree": "^1.0.6", "@types/jest": "^29.5.13", @@ -31355,14 +31355,14 @@ }, "packages/fcl-ethereum-provider": { "name": "@onflow/fcl-ethereum-provider", - "version": "0.0.0", + "version": "0.0.1-cross-vm.0", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.25.7", "@ethersproject/bytes": "^5.7.0", "@ethersproject/hash": "^5.7.0", "@noble/hashes": "^1.7.1", - "@onflow/fcl-wc": "^5.5.4", + "@onflow/fcl-wc": "^6.0.0-cross-vm.0", "@onflow/rlp": "^1.2.3", "@walletconnect/ethereum-provider": "^2.18.0", "@walletconnect/jsonrpc-http-connection": "^1.0.8", @@ -31373,7 +31373,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@onflow/typedefs": "^1.4.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", @@ -31383,7 +31383,7 @@ "jest": "^29.7.0" }, "peerDependencies": { - "@onflow/fcl": "^1.13.4" + "@onflow/fcl": "^1.14.0-cross-vm.0" } }, "packages/fcl-ethereum-provider/node_modules/@walletconnect/ethereum-provider": { @@ -31427,14 +31427,14 @@ }, "packages/fcl-rainbowkit-adapter": { "name": "@onflow/fcl-rainbowkit-adapter", - "version": "0.0.0", + "version": "0.0.1-cross-vm.0", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.25.7", "@ethersproject/bytes": "^5.7.0", "@ethersproject/hash": "^5.7.0", - "@onflow/fcl-ethereum-provider": "^0.0.0", - "@onflow/fcl-wagmi-adapter": "^0.0.0", + "@onflow/fcl-ethereum-provider": "^0.0.1-cross-vm.0", + "@onflow/fcl-wagmi-adapter": "^0.0.1-cross-vm.0", "@onflow/rlp": "^1.2.3", "@wagmi/core": "^2.16.3", "@walletconnect/jsonrpc-http-connection": "^1.0.8", @@ -31445,7 +31445,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@onflow/typedefs": "^1.4.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", @@ -31455,7 +31455,7 @@ "jest": "^29.7.0" }, "peerDependencies": { - "@onflow/fcl": "^1.13.4", + "@onflow/fcl": "^1.14.0-cross-vm.0", "@rainbow-me/rainbowkit": "^2.2.3" } }, @@ -31662,12 +31662,12 @@ }, "packages/fcl-react-native": { "name": "@onflow/fcl-react-native", - "version": "1.9.11", + "version": "1.9.12-cross-vm.0", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.25.7", "@onflow/config": "1.5.1", - "@onflow/fcl-core": "1.13.4", + "@onflow/fcl-core": "1.14.0-cross-vm.0", "@onflow/interaction": "0.0.11", "@onflow/rlp": "1.2.3", "@onflow/sdk": "1.5.6", @@ -31682,7 +31682,7 @@ "cross-fetch": "^4.0.0" }, "devDependencies": { - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@onflow/typedefs": "1.4.0", "@types/estree": "^1.0.6", "@types/node": "^18.19.57", @@ -31722,13 +31722,13 @@ }, "packages/fcl-wagmi-adapter": { "name": "@onflow/fcl-wagmi-adapter", - "version": "0.0.0", + "version": "0.0.1-cross-vm.0", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.25.7", "@ethersproject/bytes": "^5.7.0", "@ethersproject/hash": "^5.7.0", - "@onflow/fcl-ethereum-provider": "^0.0.0", + "@onflow/fcl-ethereum-provider": "^0.0.1-cross-vm.0", "@onflow/rlp": "^1.2.3", "@walletconnect/jsonrpc-http-connection": "^1.0.8", "@walletconnect/jsonrpc-provider": "^1.0.14", @@ -31740,7 +31740,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@onflow/typedefs": "^1.4.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", @@ -31750,7 +31750,7 @@ "jest": "^29.7.0" }, "peerDependencies": { - "@onflow/fcl": "^1.13.4", + "@onflow/fcl": "^1.14.0-cross-vm.0", "@wagmi/core": "^2.16.3" } }, @@ -31776,7 +31776,7 @@ }, "packages/fcl-wc": { "name": "@onflow/fcl-wc", - "version": "5.5.4", + "version": "6.0.0-cross-vm.0", "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.25.7", @@ -31797,7 +31797,7 @@ "devDependencies": { "@babel/plugin-transform-react-jsx": "^7.25.9", "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@onflow/typedefs": "^1.4.0", "autoprefixer": "^10.4.20", "eslint": "^8.57.1", @@ -31806,7 +31806,7 @@ "jest-preset-preact": "^4.1.1" }, "peerDependencies": { - "@onflow/fcl-core": "1.13.4" + "@onflow/fcl-core": "1.14.0-cross-vm.0" } }, "packages/fcl-wc/node_modules/@walletconnect/ethereum-provider": { @@ -31925,7 +31925,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", @@ -31955,7 +31955,7 @@ "uuid": "^9.0.1" }, "devDependencies": { - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@types/uuid": "^9.0.8", "eslint": "^8.57.1", "eslint-plugin-jsdoc": "^46.10.1", @@ -31990,7 +31990,7 @@ "@onflow/util-template": "1.2.3" }, "devDependencies": { - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@onflow/sdk": "1.5.6", "jest": "^29.7.0" } @@ -32012,7 +32012,7 @@ "ws": "^8.18.0" }, "devDependencies": { - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@onflow/rlp": "1.2.3", "@onflow/sdk": "1.5.6", "@onflow/types": "1.4.1", @@ -32035,7 +32035,7 @@ "@babel/runtime": "^7.25.7" }, "devDependencies": { - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@types/node": "^18.19.57", "eslint": "^8.57.1", "eslint-plugin-jsdoc": "^46.10.1", @@ -32066,7 +32066,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", @@ -32085,7 +32085,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", @@ -32103,7 +32103,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@onflow/types": "1.4.1", "@types/jest": "^29.5.13", "@types/node": "^18.19.57", @@ -32138,7 +32138,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@onflow/types": "1.4.1", "@types/jest": "^29.5.13", "@types/node": "^18.19.57", @@ -32171,7 +32171,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@onflow/types": "1.4.1", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", @@ -32190,7 +32190,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", @@ -32216,7 +32216,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", @@ -32233,7 +32233,7 @@ "@babel/runtime": "^7.25.7" }, "devDependencies": { - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "jest": "^29.7.0" } }, @@ -32247,7 +32247,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", @@ -32265,7 +32265,7 @@ }, "devDependencies": { "@babel/preset-typescript": "^7.25.7", - "@onflow/fcl-bundle": "1.6.0", + "@onflow/fcl-bundle": "1.7.0-cross-vm.0", "@types/jest": "^29.5.13", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", From 2697118b30a2a77b3208f3cc47ab0b2283128a79 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Fri, 21 Feb 2025 23:48:13 -0800 Subject: [PATCH 48/56] Fix `eth_signTypedData` --- .../src/accounts/account-manager.ts | 5 +-- .../src/rpc/handlers/eth-signtypeddata.ts | 33 ++++++++++++++----- .../src/rpc/rpc-processor.ts | 23 +------------ .../fcl-ethereum-provider/src/types/eth.ts | 8 ++--- .../{hash-utils.test.ts => util/hash.test.ts} | 8 ++--- .../src/{hash-utils.ts => util/hash.ts} | 13 ++++---- 6 files changed, 42 insertions(+), 48 deletions(-) rename packages/fcl-ethereum-provider/src/{hash-utils.test.ts => util/hash.test.ts} (96%) rename packages/fcl-ethereum-provider/src/{hash-utils.ts => util/hash.ts} (82%) diff --git a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts index 6a2cc1051..019a5d10e 100644 --- a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts +++ b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts @@ -334,8 +334,9 @@ export class AccountManager { } try { - const response: CompositeSignature[] = - await this.user.signUserMessage(message) + const response: CompositeSignature[] = await this.user.signUserMessage( + fcl.sansPrefix(message) + ) if (!response || response.length === 0) { throw new Error("Failed to sign message") diff --git a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-signtypeddata.ts b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-signtypeddata.ts index 4a3350cfa..aa45c1a31 100644 --- a/packages/fcl-ethereum-provider/src/rpc/handlers/eth-signtypeddata.ts +++ b/packages/fcl-ethereum-provider/src/rpc/handlers/eth-signtypeddata.ts @@ -1,21 +1,17 @@ import {AccountManager} from "../../accounts/account-manager" -import {SignTypedDataParams} from "../../types/eth" +import {SignTypedDataParams, TypedData} from "../../types/eth" import { hashTypedDataLegacy, hashTypedDataV3, hashTypedDataV4, -} from "../../hash-utils" +} from "../../util/hash" export async function signTypedData( accountManager: AccountManager, - params: SignTypedDataParams, + params: unknown, version: "eth_signTypedData" | "eth_signTypedData_v3" | "eth_signTypedData_v4" ) { - const {address, data} = params - - if (!address || !data) { - throw new Error("Missing signer address or typed data") - } + const {address, data} = parseParams(params, version) let hashedMessage: string if (version === "eth_signTypedData_v3") { @@ -28,3 +24,24 @@ export async function signTypedData( return await accountManager.signMessage(hashedMessage, address) } + +function parseParams( + params: unknown, + version: "eth_signTypedData" | "eth_signTypedData_v3" | "eth_signTypedData_v4" +): {address: string; data: TypedData} { + try { + if (!params || typeof params !== "object") { + throw new Error(`${version} requires valid parameters.`) + } + + const [address, data] = params as [unknown, unknown] + + if (typeof data !== "object") { + return {address: address as string, data: JSON.parse(data as string)} + } else { + return {address: address as string, data: data as TypedData} + } + } catch (error) { + throw new Error(`${version} requires valid parameters.`) + } +} diff --git a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts index 4cdfac456..60c69aa68 100644 --- a/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts +++ b/packages/fcl-ethereum-provider/src/rpc/rpc-processor.ts @@ -44,28 +44,7 @@ export class RpcProcessor { case "eth_signTypedData": case "eth_signTypedData_v3": case "eth_signTypedData_v4": { - if (!params || typeof params !== "object") { - throw new Error(`${method} requires valid parameters.`) - } - - const {address, data} = params as {address?: unknown; data?: unknown} - - if ( - typeof address !== "string" || - typeof data !== "object" || - data === null - ) { - throw new Error( - `${method} requires 'address' (string) and a valid 'data' object.` - ) - } - - const validParams: SignTypedDataParams = { - address, - data: data as TypedData, - } - - return await signTypedData(this.accountManager, validParams, method) + return await signTypedData(this.accountManager, params, method) } case "personal_sign": return await personalSign( diff --git a/packages/fcl-ethereum-provider/src/types/eth.ts b/packages/fcl-ethereum-provider/src/types/eth.ts index 5094dc7ca..ebbf95510 100644 --- a/packages/fcl-ethereum-provider/src/types/eth.ts +++ b/packages/fcl-ethereum-provider/src/types/eth.ts @@ -2,10 +2,10 @@ export type EthSignatureResponse = string export type PersonalSignParams = [string, string] -export interface SignTypedDataParams { - address: string - data: TypedData // This represents the EIP-712 structured data -} +export type SignTypedDataParams = [ + address: string, + data: string | TypedData, // This represents the EIP-712 structured data, may be serialized or not +] export interface TypedDataField { name: string diff --git a/packages/fcl-ethereum-provider/src/hash-utils.test.ts b/packages/fcl-ethereum-provider/src/util/hash.test.ts similarity index 96% rename from packages/fcl-ethereum-provider/src/hash-utils.test.ts rename to packages/fcl-ethereum-provider/src/util/hash.test.ts index e57afbca1..fe1375f20 100644 --- a/packages/fcl-ethereum-provider/src/hash-utils.test.ts +++ b/packages/fcl-ethereum-provider/src/util/hash.test.ts @@ -2,12 +2,8 @@ import {keccak_256} from "@noble/hashes/sha3" import {bytesToHex} from "@noble/hashes/utils" import {concat, arrayify} from "@ethersproject/bytes" import {_TypedDataEncoder as TypedDataEncoder} from "@ethersproject/hash" -import {TypedData} from "./types/eth" -import { - hashTypedDataLegacy, - hashTypedDataV3, - hashTypedDataV4, -} from "./hash-utils" +import {TypedData} from "../types/eth" +import {hashTypedDataLegacy, hashTypedDataV3, hashTypedDataV4} from "./hash" jest.mock("@noble/hashes/sha3", () => ({ keccak_256: jest.fn(() => diff --git a/packages/fcl-ethereum-provider/src/hash-utils.ts b/packages/fcl-ethereum-provider/src/util/hash.ts similarity index 82% rename from packages/fcl-ethereum-provider/src/hash-utils.ts rename to packages/fcl-ethereum-provider/src/util/hash.ts index a986dc6f1..125083870 100644 --- a/packages/fcl-ethereum-provider/src/hash-utils.ts +++ b/packages/fcl-ethereum-provider/src/util/hash.ts @@ -2,7 +2,7 @@ import {keccak_256} from "@noble/hashes/sha3" import {bytesToHex} from "@noble/hashes/utils" import {arrayify, concat} from "@ethersproject/bytes" import {_TypedDataEncoder as TypedDataEncoder} from "@ethersproject/hash" -import {TypedData} from "./types/eth" +import {TypedData} from "../types/eth" export function hashTypedDataLegacy(data: TypedData): string { throw new Error( @@ -18,11 +18,12 @@ export function hashTypedDataLegacy(data: TypedData): string { */ export function hashTypedDataV3(data: TypedData): string { const domainSeparator = TypedDataEncoder.hashDomain(data.domain) - const messageHash = TypedDataEncoder.hash( - data.domain, - data.types, - data.message - ) + + // Clone the types and remove the EIP712Domain entry if it exists. + const types = {...data.types} + delete types.EIP712Domain + + const messageHash = TypedDataEncoder.hash(data.domain, types, data.message) // The EIP‑191 prefix is "0x1901". const prefix = "0x1901" const digest = keccak_256( From 449d566be225ac57323a0b031715764648ddf793 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink <17958158+jribbink@users.noreply.github.com> Date: Wed, 26 Feb 2025 08:57:21 -0700 Subject: [PATCH 49/56] Fix gas estimation fallback in `eth_sendTransaction` (#2163) --- .changeset/big-ravens-smell.md | 5 + .../src/accounts/account-manager.test.ts | 160 +++++++++++++++--- .../src/accounts/account-manager.ts | 38 +++-- .../fcl-ethereum-provider/src/constants.ts | 4 - .../src/create-provider.ts | 9 +- 5 files changed, 175 insertions(+), 41 deletions(-) create mode 100644 .changeset/big-ravens-smell.md diff --git a/.changeset/big-ravens-smell.md b/.changeset/big-ravens-smell.md new file mode 100644 index 000000000..69d49ccb5 --- /dev/null +++ b/.changeset/big-ravens-smell.md @@ -0,0 +1,5 @@ +--- +"@onflow/fcl-ethereum-provider": patch +--- + +Fix eth_sendTranscation gas estimation diff --git a/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts b/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts index f8f898449..242fe2db7 100644 --- a/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts +++ b/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts @@ -6,6 +6,7 @@ import {NetworkManager} from "../network/network-manager" import {BehaviorSubject} from "../util/observable" import {TransactionError} from "@onflow/fcl" import * as notifications from "../notifications" +import {Gateway} from "../gateway/gateway" jest.mock("@onflow/fcl", () => { const fcl = jest.requireActual("@onflow/fcl") @@ -31,6 +32,7 @@ const mockQuery = jest.mocked(fcl.query) describe("AccountManager", () => { let networkManager: jest.Mocked let userMock: ReturnType + let gatewayMock: jest.Mocked beforeEach(() => { jest.resetAllMocks() @@ -41,16 +43,27 @@ describe("AccountManager", () => { getChainId: () => chainId$.getValue(), } as any as jest.Mocked userMock = mockUser() + gatewayMock = { + request: jest.fn(), + } as any as jest.Mocked }) it("should initialize with null COA address", async () => { - const accountManager = new AccountManager(userMock.mock, networkManager) + const accountManager = new AccountManager( + userMock.mock, + networkManager, + gatewayMock + ) expect(await accountManager.getCOAAddress()).toBeNull() expect(await accountManager.getAccounts()).toEqual([]) }) it("should reset state when the user is not logged in", async () => { - const accountManager = new AccountManager(userMock.mock, networkManager) + const accountManager = new AccountManager( + userMock.mock, + networkManager, + gatewayMock + ) expect(await accountManager.getCOAAddress()).toBeNull() expect(await accountManager.getAccounts()).toEqual([]) @@ -59,7 +72,11 @@ describe("AccountManager", () => { it("should fetch and update COA address when user logs in", async () => { mockQuery.mockResolvedValue("0x123") - const accountManager = new AccountManager(userMock.mock, networkManager) + const accountManager = new AccountManager( + userMock.mock, + networkManager, + gatewayMock + ) expect(await accountManager.getCOAAddress()).toBe(null) @@ -76,7 +93,11 @@ describe("AccountManager", () => { it("should not update COA address if user has not changed", async () => { mockQuery.mockResolvedValue("0x123") - const accountManager = new AccountManager(userMock.mock, networkManager) + const accountManager = new AccountManager( + userMock.mock, + networkManager, + gatewayMock + ) userMock.set!({addr: "0x1"} as CurrentUser) @@ -102,7 +123,11 @@ describe("AccountManager", () => { // 2nd fetch: immediate .mockResolvedValueOnce("0x456") - const accountManager = new AccountManager(userMock.mock, networkManager) + const accountManager = new AccountManager( + userMock.mock, + networkManager, + gatewayMock + ) await userMock.set!({addr: "0x1"} as CurrentUser) await userMock.set!({addr: "0x2"} as CurrentUser) @@ -114,7 +139,11 @@ describe("AccountManager", () => { it("should throw if COA address fetch fails", async () => { mockQuery.mockRejectedValueOnce(new Error("Fetch failed")) - const accountManager = new AccountManager(userMock.mock, networkManager) + const accountManager = new AccountManager( + userMock.mock, + networkManager, + gatewayMock + ) await userMock.set!({addr: "0x1"} as CurrentUser) @@ -124,7 +153,11 @@ describe("AccountManager", () => { it("getAndCreateAccounts should get a COA address if it already exists", async () => { mockQuery.mockResolvedValue("0x123") - const accountManager = new AccountManager(userMock.mock, networkManager) + const accountManager = new AccountManager( + userMock.mock, + networkManager, + gatewayMock + ) // Trigger the state update await userMock.set!({addr: "0x1"} as CurrentUser) @@ -157,7 +190,11 @@ describe("AccountManager", () => { // For the subscription, simulate that initially no COA is found, then after creation the query returns "0x123" mockQuery.mockResolvedValueOnce(null).mockResolvedValueOnce("0x123") - const accountManager = new AccountManager(userMock.mock, networkManager) + const accountManager = new AccountManager( + userMock.mock, + networkManager, + gatewayMock + ) await userMock.set!({addr: "0x1"} as CurrentUser) @@ -187,7 +224,11 @@ describe("AccountManager", () => { jest.mocked(TransactionError.fromErrorMessage).mockReturnValue(storageError) - const accountManager = new AccountManager(userMock.mock, networkManager) + const accountManager = new AccountManager( + userMock.mock, + networkManager, + gatewayMock + ) await userMock.set!({addr: "0x1"} as CurrentUser) @@ -205,7 +246,11 @@ describe("AccountManager", () => { .mockResolvedValueOnce("0x123") // for user 0x1 .mockResolvedValueOnce("0x456") // for user 0x2 - const accountManager = new AccountManager(userMock.mock, networkManager) + const accountManager = new AccountManager( + userMock.mock, + networkManager, + gatewayMock + ) await userMock.set({addr: "0x1"} as CurrentUser) expect(await accountManager.getCOAAddress()).toBe("0x123") @@ -218,7 +263,11 @@ describe("AccountManager", () => { it("should call the callback with updated accounts in subscribe", async () => { mockQuery.mockResolvedValue("0x123") - const accountManager = new AccountManager(userMock.mock, networkManager) + const accountManager = new AccountManager( + userMock.mock, + networkManager, + gatewayMock + ) const callback = jest.fn() accountManager.subscribe(callback) @@ -233,7 +282,11 @@ describe("AccountManager", () => { const callback = jest.fn() - const accountManager = new AccountManager(userMock.mock, networkManager) + const accountManager = new AccountManager( + userMock.mock, + networkManager, + gatewayMock + ) accountManager.subscribe(callback) @@ -247,7 +300,11 @@ describe("AccountManager", () => { mockQuery.mockResolvedValueOnce("0x123") - const accountManager = new AccountManager(userMock.mock, networkManager) + const accountManager = new AccountManager( + userMock.mock, + networkManager, + gatewayMock + ) userMock.set({addr: "0x1"} as CurrentUser) @@ -259,7 +316,11 @@ describe("AccountManager", () => { }) it("should return an empty array when COA address is null", async () => { - const accountManager = new AccountManager(userMock.mock, networkManager) + const accountManager = new AccountManager( + userMock.mock, + networkManager, + gatewayMock + ) expect(await accountManager.getAccounts()).toEqual([]) }) @@ -267,7 +328,11 @@ describe("AccountManager", () => { mockQuery.mockResolvedValueOnce("0x123") userMock.set({addr: "0x1"} as CurrentUser) - const accountManager = new AccountManager(userMock.mock, networkManager) + const accountManager = new AccountManager( + userMock.mock, + networkManager, + gatewayMock + ) expect(await accountManager.getAccounts()).toEqual(["0x123"]) }) @@ -330,6 +395,7 @@ describe("AccountManager", () => { describe("sendTransaction", () => { let networkManager: jest.Mocked let $mockChainId: BehaviorSubject + let gatewayMock: jest.Mocked beforeEach(() => { $mockChainId = new BehaviorSubject(747) @@ -337,6 +403,9 @@ describe("sendTransaction", () => { $chainId: $mockChainId, getChainId: () => $mockChainId.getValue(), } as any as jest.Mocked + gatewayMock = { + request: jest.fn(), + } as any as jest.Mocked jest.resetAllMocks() }) @@ -361,7 +430,7 @@ describe("sendTransaction", () => { .mockResolvedValueOnce("0x0") const user = mockUser({addr: "0x1234"} as CurrentUser).mock - const accountManager = new AccountManager(user, networkManager) + const accountManager = new AccountManager(user, networkManager, gatewayMock) // Numbers maxed out to test edge cases const txInput = { @@ -400,7 +469,7 @@ describe("sendTransaction", () => { jest.mocked(fcl.query).mockResolvedValue("0x1234") const user = mockUser({addr: "0x4444"} as CurrentUser).mock - const accountManager = new AccountManager(user, networkManager) + const accountManager = new AccountManager(user, networkManager, gatewayMock) const tx = { to: "0x1234", @@ -448,7 +517,11 @@ describe("sendTransaction", () => { jest.mocked(fcl.query).mockResolvedValue("0x1234") const user = mockUser({addr: "0x4444"} as CurrentUser) - const accountManager = new AccountManager(user.mock, networkManager) + const accountManager = new AccountManager( + user.mock, + networkManager, + gatewayMock + ) const tx = { to: "0x1234", @@ -475,7 +548,11 @@ describe("sendTransaction", () => { test("throws error if from address does not match user address", async () => { jest.mocked(fcl.query).mockResolvedValue("0x1234") const user = mockUser({addr: "0x4444"} as CurrentUser) - const accountManager = new AccountManager(user.mock, networkManager) + const accountManager = new AccountManager( + user.mock, + networkManager, + gatewayMock + ) const tx = { to: "0x1234", @@ -493,4 +570,49 @@ describe("sendTransaction", () => { expect(fcl.mutate).not.toHaveBeenCalled() }) + + test("falls back to estimated gas limit if not provided", async () => { + const mockTxResult = { + onceExecuted: jest.fn().mockResolvedValue({ + events: [ + { + type: "A.e467b9dd11fa00df.EVM.TransactionExecuted", + data: {hash: ["12", "34"]}, + }, + ], + }), + } as any as jest.Mocked> + + jest.mocked(fcl.tx).mockReturnValue(mockTxResult) + jest.mocked(fcl.mutate).mockResolvedValue("1111") + jest + .mocked(fcl.query) + .mockResolvedValueOnce("0x1234") + .mockResolvedValueOnce("0x0") + + jest.mocked(gatewayMock.request).mockResolvedValueOnce("0x1234") + + const user = mockUser({addr: "0x1234"} as CurrentUser).mock + const accountManager = new AccountManager(user, networkManager, gatewayMock) + + const txInput = { + to: "0xffffffffffffffffffffffffffffffffffffffff", + from: "0x1234", + value: + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + data: "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + nonce: "0xffffffffffffffff", + chainId: "747", + } + + await accountManager.sendTransaction(txInput) + + expect(fcl.mutate).toHaveBeenCalledWith( + expect.objectContaining({ + cadence: expect.any(String), + args: expect.any(Function), + limit: 9999, + }) + ) + }) }) diff --git a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts index 6a2cc1051..d2c36e348 100644 --- a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts +++ b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts @@ -6,12 +6,7 @@ import { Service, FvmErrorCode, } from "@onflow/typedefs" -import { - DEFAULT_EVM_GAS_LIMIT, - EVENT_IDENTIFIERS, - EventType, - FlowNetwork, -} from "../constants" +import {EVENT_IDENTIFIERS, EventType, FlowNetwork} from "../constants" import { BehaviorSubject, concat, @@ -38,6 +33,7 @@ import {displayErrorNotification} from "../notifications" import {AddressStoreState} from "../types/account" import {getFlowNetwork} from "../util/chain" import {precalculateTxHash} from "../util/transaction" +import {Gateway} from "../gateway/gateway" export class AccountManager { private $addressStore = new BehaviorSubject({ @@ -49,6 +45,7 @@ export class AccountManager { constructor( private user: typeof fcl.currentUser, private networkManager: NetworkManager, + private gateway: Gateway, private service?: Service ) { this.initializeUserSubscription() @@ -261,19 +258,32 @@ export class AccountManager { gas?: string data?: string }) { - const { - to, - from, - value = "0", - data = "", - gas = DEFAULT_EVM_GAS_LIMIT, - chainId, - } = params + const {to, from, value = "0", data = "", chainId} = params const parsedChainId = parseInt(chainId) this.getFlowNetworkOrThrow(parsedChainId) await this.validateChainId(parsedChainId) + // Determine gas limit + let gas = params.gas + if (!gas) { + const gasLimit = await this.gateway.request({ + method: "eth_estimateGas", + params: [ + { + from, + to, + value, + data, + }, + ], + chainId: parsedChainId, + }) + + // Add a 20% buffer to the estimate + gas = ((BigInt(gasLimit) * BigInt(12)) / BigInt(10)).toString() + } + // Check if the from address matches the authenticated COA address const expectedCOAAddress = await this.getCOAAddress() if ( diff --git a/packages/fcl-ethereum-provider/src/constants.ts b/packages/fcl-ethereum-provider/src/constants.ts index ad428f642..a0b530b1a 100644 --- a/packages/fcl-ethereum-provider/src/constants.ts +++ b/packages/fcl-ethereum-provider/src/constants.ts @@ -58,7 +58,3 @@ export interface TransactionExecutedEvent { } export const ACCESS_NODE_API_KEY = "accessNode.api" - -// TODO: This is a fixed value matching what is used by Flow Wallet right now. -// We should investigate whether eth_estimateGas can be used (& should be used) to get a more accurate value. -export const DEFAULT_EVM_GAS_LIMIT = "0x76c0" diff --git a/packages/fcl-ethereum-provider/src/create-provider.ts b/packages/fcl-ethereum-provider/src/create-provider.ts index d8eab92f8..7af19e61b 100644 --- a/packages/fcl-ethereum-provider/src/create-provider.ts +++ b/packages/fcl-ethereum-provider/src/create-provider.ts @@ -46,15 +46,16 @@ export function createProvider(config: FclProviderConfig): Eip1193Provider { ) const networkManager = new NetworkManager(config.config) + const gateway = new Gateway({ + ...defaultRpcUrls, + ...(config.rpcUrls || {}), + }) const accountManager = new AccountManager( config.user, networkManager, + gateway, config.service ) - const gateway = new Gateway({ - ...defaultRpcUrls, - ...(config.rpcUrls || {}), - }) const rpcProcessor = new RpcProcessor(gateway, accountManager, networkManager) const eventProcessor = new EventDispatcher(accountManager, networkManager) const provider = new FclEthereumProvider( From 1bdcde3065b975e629c0eac96f830cd3ca5a2440 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 26 Feb 2025 09:03:37 -0700 Subject: [PATCH 50/56] Version Packages (cross-vm) (#2164) Co-authored-by: github-actions[bot] --- .changeset/pre.json | 6 +++++- packages/fcl-ethereum-provider/CHANGELOG.md | 6 ++++++ packages/fcl-ethereum-provider/package.json | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index 5bc4aec75..0025b569d 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -28,5 +28,9 @@ "@onflow/util-template": "1.2.3", "@onflow/util-uid": "1.2.3" }, - "changesets": ["chilled-wombats-reply", "shaggy-carpets-eat"] + "changesets": [ + "big-ravens-smell", + "chilled-wombats-reply", + "shaggy-carpets-eat" + ] } diff --git a/packages/fcl-ethereum-provider/CHANGELOG.md b/packages/fcl-ethereum-provider/CHANGELOG.md index 18ef374bf..0069025dc 100644 --- a/packages/fcl-ethereum-provider/CHANGELOG.md +++ b/packages/fcl-ethereum-provider/CHANGELOG.md @@ -1,5 +1,11 @@ # @onflow/fcl-ethereum-provider +## 0.0.1-cross-vm.1 + +### Patch Changes + +- [#2163](https://github.com/onflow/fcl-js/pull/2163) [`449d566be225ac57323a0b031715764648ddf793`](https://github.com/onflow/fcl-js/commit/449d566be225ac57323a0b031715764648ddf793) Thanks [@jribbink](https://github.com/jribbink)! - Fix eth_sendTranscation gas estimation + ## 0.0.1-cross-vm.0 ### Patch Changes diff --git a/packages/fcl-ethereum-provider/package.json b/packages/fcl-ethereum-provider/package.json index b744e6d3f..a74f45d1e 100644 --- a/packages/fcl-ethereum-provider/package.json +++ b/packages/fcl-ethereum-provider/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl-ethereum-provider", - "version": "0.0.1-cross-vm.0", + "version": "0.0.1-cross-vm.1", "description": "Ethereum provider for FCL-compatible wallets", "license": "Apache-2.0", "author": "Dapper Labs ", From 86bb54961f38d11790d5c14145d4ce0e9c3a88aa Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Wed, 26 Feb 2025 09:06:45 -0700 Subject: [PATCH 51/56] Pin package versions --- packages/fcl-ethereum-provider/package.json | 4 ++-- packages/fcl-rainbowkit-adapter/package.json | 6 +++--- packages/fcl-wagmi-adapter/package.json | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/fcl-ethereum-provider/package.json b/packages/fcl-ethereum-provider/package.json index a74f45d1e..708920df1 100644 --- a/packages/fcl-ethereum-provider/package.json +++ b/packages/fcl-ethereum-provider/package.json @@ -40,7 +40,7 @@ "@ethersproject/bytes": "^5.7.0", "@ethersproject/hash": "^5.7.0", "@noble/hashes": "^1.7.1", - "@onflow/fcl-wc": "^6.0.0-cross-vm.0", + "@onflow/fcl-wc": "6.0.0-cross-vm.0", "@onflow/rlp": "^1.2.3", "@walletconnect/ethereum-provider": "^2.18.0", "@walletconnect/jsonrpc-http-connection": "^1.0.8", @@ -50,6 +50,6 @@ "@walletconnect/utils": "^2.18.0" }, "peerDependencies": { - "@onflow/fcl": "^1.14.0-cross-vm.0" + "@onflow/fcl": "1.14.0-cross-vm.0" } } diff --git a/packages/fcl-rainbowkit-adapter/package.json b/packages/fcl-rainbowkit-adapter/package.json index 4cac0e335..9a3aecad3 100644 --- a/packages/fcl-rainbowkit-adapter/package.json +++ b/packages/fcl-rainbowkit-adapter/package.json @@ -39,8 +39,8 @@ "@babel/runtime": "^7.25.7", "@ethersproject/bytes": "^5.7.0", "@ethersproject/hash": "^5.7.0", - "@onflow/fcl-ethereum-provider": "^0.0.1-cross-vm.0", - "@onflow/fcl-wagmi-adapter": "^0.0.1-cross-vm.0", + "@onflow/fcl-ethereum-provider": "0.0.1-cross-vm.0", + "@onflow/fcl-wagmi-adapter": "0.0.1-cross-vm.0", "@onflow/rlp": "^1.2.3", "@wagmi/core": "^2.16.3", "@walletconnect/jsonrpc-http-connection": "^1.0.8", @@ -50,7 +50,7 @@ "wagmi": "^2.14.11" }, "peerDependencies": { - "@onflow/fcl": "^1.14.0-cross-vm.0", + "@onflow/fcl": "1.14.0-cross-vm.0", "@rainbow-me/rainbowkit": "^2.2.3" } } diff --git a/packages/fcl-wagmi-adapter/package.json b/packages/fcl-wagmi-adapter/package.json index 998f3bfb6..7fa502b12 100644 --- a/packages/fcl-wagmi-adapter/package.json +++ b/packages/fcl-wagmi-adapter/package.json @@ -39,7 +39,7 @@ "@babel/runtime": "^7.25.7", "@ethersproject/bytes": "^5.7.0", "@ethersproject/hash": "^5.7.0", - "@onflow/fcl-ethereum-provider": "^0.0.1-cross-vm.0", + "@onflow/fcl-ethereum-provider": "0.0.1-cross-vm.1", "@onflow/rlp": "^1.2.3", "@walletconnect/jsonrpc-http-connection": "^1.0.8", "@walletconnect/jsonrpc-provider": "^1.0.14", @@ -50,7 +50,7 @@ "viem": "^2.22.21" }, "peerDependencies": { - "@onflow/fcl": "^1.14.0-cross-vm.0", + "@onflow/fcl": "1.14.0-cross-vm.0", "@wagmi/core": "^2.16.3" } } From 24a39f39354f819cb7e59193c44f825a7dd18f32 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Wed, 26 Feb 2025 09:07:02 -0700 Subject: [PATCH 52/56] changeset --- .changeset/thin-lobsters-hope.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/thin-lobsters-hope.md diff --git a/.changeset/thin-lobsters-hope.md b/.changeset/thin-lobsters-hope.md new file mode 100644 index 000000000..798b34f77 --- /dev/null +++ b/.changeset/thin-lobsters-hope.md @@ -0,0 +1,7 @@ +--- +"@onflow/fcl-rainbowkit-adapter": patch +"@onflow/fcl-ethereum-provider": patch +"@onflow/fcl-wagmi-adapter": patch +--- + +Pin package versions From a2dfa38535cd557bfe9c2f42bd222c347e749791 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 26 Feb 2025 09:28:41 -0700 Subject: [PATCH 53/56] Version Packages (cross-vm) (#2165) Co-authored-by: github-actions[bot] --- .changeset/pre.json | 3 ++- packages/fcl-ethereum-provider/CHANGELOG.md | 6 ++++++ packages/fcl-ethereum-provider/package.json | 2 +- packages/fcl-rainbowkit-adapter/CHANGELOG.md | 10 ++++++++++ packages/fcl-rainbowkit-adapter/package.json | 6 +++--- packages/fcl-wagmi-adapter/CHANGELOG.md | 9 +++++++++ packages/fcl-wagmi-adapter/package.json | 4 ++-- 7 files changed, 33 insertions(+), 7 deletions(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index 0025b569d..00da5e850 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -31,6 +31,7 @@ "changesets": [ "big-ravens-smell", "chilled-wombats-reply", - "shaggy-carpets-eat" + "shaggy-carpets-eat", + "thin-lobsters-hope" ] } diff --git a/packages/fcl-ethereum-provider/CHANGELOG.md b/packages/fcl-ethereum-provider/CHANGELOG.md index 0069025dc..771b3282c 100644 --- a/packages/fcl-ethereum-provider/CHANGELOG.md +++ b/packages/fcl-ethereum-provider/CHANGELOG.md @@ -1,5 +1,11 @@ # @onflow/fcl-ethereum-provider +## 0.0.1-cross-vm.2 + +### Patch Changes + +- [#2076](https://github.com/onflow/fcl-js/pull/2076) [`24a39f39354f819cb7e59193c44f825a7dd18f32`](https://github.com/onflow/fcl-js/commit/24a39f39354f819cb7e59193c44f825a7dd18f32) Thanks [@jribbink](https://github.com/jribbink)! - Pin package versions + ## 0.0.1-cross-vm.1 ### Patch Changes diff --git a/packages/fcl-ethereum-provider/package.json b/packages/fcl-ethereum-provider/package.json index 708920df1..fd8cfe593 100644 --- a/packages/fcl-ethereum-provider/package.json +++ b/packages/fcl-ethereum-provider/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl-ethereum-provider", - "version": "0.0.1-cross-vm.1", + "version": "0.0.1-cross-vm.2", "description": "Ethereum provider for FCL-compatible wallets", "license": "Apache-2.0", "author": "Dapper Labs ", diff --git a/packages/fcl-rainbowkit-adapter/CHANGELOG.md b/packages/fcl-rainbowkit-adapter/CHANGELOG.md index 5f1e3fae8..18f34f7b4 100644 --- a/packages/fcl-rainbowkit-adapter/CHANGELOG.md +++ b/packages/fcl-rainbowkit-adapter/CHANGELOG.md @@ -1,5 +1,15 @@ # @onflow/fcl-ethereum-provider +## 0.0.1-cross-vm.1 + +### Patch Changes + +- [#2076](https://github.com/onflow/fcl-js/pull/2076) [`24a39f39354f819cb7e59193c44f825a7dd18f32`](https://github.com/onflow/fcl-js/commit/24a39f39354f819cb7e59193c44f825a7dd18f32) Thanks [@jribbink](https://github.com/jribbink)! - Pin package versions + +- Updated dependencies [[`24a39f39354f819cb7e59193c44f825a7dd18f32`](https://github.com/onflow/fcl-js/commit/24a39f39354f819cb7e59193c44f825a7dd18f32)]: + - @onflow/fcl-ethereum-provider@0.0.1-cross-vm.2 + - @onflow/fcl-wagmi-adapter@0.0.1-cross-vm.1 + ## 0.0.1-cross-vm.0 ### Patch Changes diff --git a/packages/fcl-rainbowkit-adapter/package.json b/packages/fcl-rainbowkit-adapter/package.json index 9a3aecad3..afbf7c750 100644 --- a/packages/fcl-rainbowkit-adapter/package.json +++ b/packages/fcl-rainbowkit-adapter/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl-rainbowkit-adapter", - "version": "0.0.1-cross-vm.0", + "version": "0.0.1-cross-vm.1", "description": "Rainbowkit adapter for FCL-compatible wallets", "license": "Apache-2.0", "author": "Dapper Labs ", @@ -39,8 +39,8 @@ "@babel/runtime": "^7.25.7", "@ethersproject/bytes": "^5.7.0", "@ethersproject/hash": "^5.7.0", - "@onflow/fcl-ethereum-provider": "0.0.1-cross-vm.0", - "@onflow/fcl-wagmi-adapter": "0.0.1-cross-vm.0", + "@onflow/fcl-ethereum-provider": "0.0.1-cross-vm.2", + "@onflow/fcl-wagmi-adapter": "0.0.1-cross-vm.1", "@onflow/rlp": "^1.2.3", "@wagmi/core": "^2.16.3", "@walletconnect/jsonrpc-http-connection": "^1.0.8", diff --git a/packages/fcl-wagmi-adapter/CHANGELOG.md b/packages/fcl-wagmi-adapter/CHANGELOG.md index 3f08f9adf..da9d875ae 100644 --- a/packages/fcl-wagmi-adapter/CHANGELOG.md +++ b/packages/fcl-wagmi-adapter/CHANGELOG.md @@ -1,5 +1,14 @@ # @onflow/fcl-ethereum-provider +## 0.0.1-cross-vm.1 + +### Patch Changes + +- [#2076](https://github.com/onflow/fcl-js/pull/2076) [`24a39f39354f819cb7e59193c44f825a7dd18f32`](https://github.com/onflow/fcl-js/commit/24a39f39354f819cb7e59193c44f825a7dd18f32) Thanks [@jribbink](https://github.com/jribbink)! - Pin package versions + +- Updated dependencies [[`24a39f39354f819cb7e59193c44f825a7dd18f32`](https://github.com/onflow/fcl-js/commit/24a39f39354f819cb7e59193c44f825a7dd18f32)]: + - @onflow/fcl-ethereum-provider@0.0.1-cross-vm.2 + ## 0.0.1-cross-vm.0 ### Patch Changes diff --git a/packages/fcl-wagmi-adapter/package.json b/packages/fcl-wagmi-adapter/package.json index 7fa502b12..ec0259bd2 100644 --- a/packages/fcl-wagmi-adapter/package.json +++ b/packages/fcl-wagmi-adapter/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl-wagmi-adapter", - "version": "0.0.1-cross-vm.0", + "version": "0.0.1-cross-vm.1", "description": "Wagmi adapter for FCL-compatible wallets", "license": "Apache-2.0", "author": "Dapper Labs ", @@ -39,7 +39,7 @@ "@babel/runtime": "^7.25.7", "@ethersproject/bytes": "^5.7.0", "@ethersproject/hash": "^5.7.0", - "@onflow/fcl-ethereum-provider": "0.0.1-cross-vm.1", + "@onflow/fcl-ethereum-provider": "0.0.1-cross-vm.2", "@onflow/rlp": "^1.2.3", "@walletconnect/jsonrpc-http-connection": "^1.0.8", "@walletconnect/jsonrpc-provider": "^1.0.14", From aa36c67b51394024f0be54b3e46917cfadc9cde8 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Wed, 26 Feb 2025 11:12:26 -0700 Subject: [PATCH 54/56] Fix eth_estimateGas prefixing --- .changeset/few-spiders-sneeze.md | 5 +++++ .../src/accounts/account-manager.test.ts | 14 ++++++++++++++ .../src/accounts/account-manager.ts | 10 +++++----- 3 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 .changeset/few-spiders-sneeze.md diff --git a/.changeset/few-spiders-sneeze.md b/.changeset/few-spiders-sneeze.md new file mode 100644 index 000000000..05b29231d --- /dev/null +++ b/.changeset/few-spiders-sneeze.md @@ -0,0 +1,5 @@ +--- +"@onflow/fcl-ethereum-provider": patch +--- + +Fix eth_estimateGas prefixing issue diff --git a/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts b/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts index 242fe2db7..35564bff9 100644 --- a/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts +++ b/packages/fcl-ethereum-provider/src/accounts/account-manager.test.ts @@ -607,6 +607,20 @@ describe("sendTransaction", () => { await accountManager.sendTransaction(txInput) + expect(gatewayMock.request).toHaveBeenCalledWith({ + chainId: 747, + method: "eth_estimateGas", + params: [ + { + from: "0x1234", + to: "0xffffffffffffffffffffffffffffffffffffffff", + value: + "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + data: "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", + }, + ], + }) + expect(fcl.mutate).toHaveBeenCalledWith( expect.objectContaining({ cadence: expect.any(String), diff --git a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts index d2c36e348..e6890f2bd 100644 --- a/packages/fcl-ethereum-provider/src/accounts/account-manager.ts +++ b/packages/fcl-ethereum-provider/src/accounts/account-manager.ts @@ -271,17 +271,17 @@ export class AccountManager { method: "eth_estimateGas", params: [ { - from, - to, - value, - data, + from: fcl.withPrefix(from), + to: fcl.withPrefix(to), + value: fcl.withPrefix(value), + data: fcl.withPrefix(data), }, ], chainId: parsedChainId, }) // Add a 20% buffer to the estimate - gas = ((BigInt(gasLimit) * BigInt(12)) / BigInt(10)).toString() + gas = ((BigInt(gasLimit) * BigInt(6)) / BigInt(5)).toString() } // Check if the from address matches the authenticated COA address From 225a2c29784ee5de02216dedd36de56460f7a7eb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 26 Feb 2025 11:15:51 -0700 Subject: [PATCH 55/56] Version Packages (cross-vm) (#2166) Co-authored-by: github-actions[bot] --- .changeset/pre.json | 1 + packages/fcl-ethereum-provider/CHANGELOG.md | 6 ++++++ packages/fcl-ethereum-provider/package.json | 2 +- packages/fcl-rainbowkit-adapter/CHANGELOG.md | 8 ++++++++ packages/fcl-rainbowkit-adapter/package.json | 6 +++--- packages/fcl-wagmi-adapter/CHANGELOG.md | 7 +++++++ packages/fcl-wagmi-adapter/package.json | 4 ++-- 7 files changed, 28 insertions(+), 6 deletions(-) diff --git a/.changeset/pre.json b/.changeset/pre.json index 00da5e850..dc31f1f9b 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -31,6 +31,7 @@ "changesets": [ "big-ravens-smell", "chilled-wombats-reply", + "few-spiders-sneeze", "shaggy-carpets-eat", "thin-lobsters-hope" ] diff --git a/packages/fcl-ethereum-provider/CHANGELOG.md b/packages/fcl-ethereum-provider/CHANGELOG.md index 771b3282c..b50a81e0f 100644 --- a/packages/fcl-ethereum-provider/CHANGELOG.md +++ b/packages/fcl-ethereum-provider/CHANGELOG.md @@ -1,5 +1,11 @@ # @onflow/fcl-ethereum-provider +## 0.0.1-cross-vm.3 + +### Patch Changes + +- [#2076](https://github.com/onflow/fcl-js/pull/2076) [`aa36c67b51394024f0be54b3e46917cfadc9cde8`](https://github.com/onflow/fcl-js/commit/aa36c67b51394024f0be54b3e46917cfadc9cde8) Thanks [@jribbink](https://github.com/jribbink)! - Fix eth_estimateGas prefixing issue + ## 0.0.1-cross-vm.2 ### Patch Changes diff --git a/packages/fcl-ethereum-provider/package.json b/packages/fcl-ethereum-provider/package.json index fd8cfe593..b1694c1e6 100644 --- a/packages/fcl-ethereum-provider/package.json +++ b/packages/fcl-ethereum-provider/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl-ethereum-provider", - "version": "0.0.1-cross-vm.2", + "version": "0.0.1-cross-vm.3", "description": "Ethereum provider for FCL-compatible wallets", "license": "Apache-2.0", "author": "Dapper Labs ", diff --git a/packages/fcl-rainbowkit-adapter/CHANGELOG.md b/packages/fcl-rainbowkit-adapter/CHANGELOG.md index 18f34f7b4..b367d4dec 100644 --- a/packages/fcl-rainbowkit-adapter/CHANGELOG.md +++ b/packages/fcl-rainbowkit-adapter/CHANGELOG.md @@ -1,5 +1,13 @@ # @onflow/fcl-ethereum-provider +## 0.0.1-cross-vm.2 + +### Patch Changes + +- Updated dependencies [[`aa36c67b51394024f0be54b3e46917cfadc9cde8`](https://github.com/onflow/fcl-js/commit/aa36c67b51394024f0be54b3e46917cfadc9cde8)]: + - @onflow/fcl-ethereum-provider@0.0.1-cross-vm.3 + - @onflow/fcl-wagmi-adapter@0.0.1-cross-vm.2 + ## 0.0.1-cross-vm.1 ### Patch Changes diff --git a/packages/fcl-rainbowkit-adapter/package.json b/packages/fcl-rainbowkit-adapter/package.json index afbf7c750..de8e3813a 100644 --- a/packages/fcl-rainbowkit-adapter/package.json +++ b/packages/fcl-rainbowkit-adapter/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl-rainbowkit-adapter", - "version": "0.0.1-cross-vm.1", + "version": "0.0.1-cross-vm.2", "description": "Rainbowkit adapter for FCL-compatible wallets", "license": "Apache-2.0", "author": "Dapper Labs ", @@ -39,8 +39,8 @@ "@babel/runtime": "^7.25.7", "@ethersproject/bytes": "^5.7.0", "@ethersproject/hash": "^5.7.0", - "@onflow/fcl-ethereum-provider": "0.0.1-cross-vm.2", - "@onflow/fcl-wagmi-adapter": "0.0.1-cross-vm.1", + "@onflow/fcl-ethereum-provider": "0.0.1-cross-vm.3", + "@onflow/fcl-wagmi-adapter": "0.0.1-cross-vm.2", "@onflow/rlp": "^1.2.3", "@wagmi/core": "^2.16.3", "@walletconnect/jsonrpc-http-connection": "^1.0.8", diff --git a/packages/fcl-wagmi-adapter/CHANGELOG.md b/packages/fcl-wagmi-adapter/CHANGELOG.md index da9d875ae..31a9fb41a 100644 --- a/packages/fcl-wagmi-adapter/CHANGELOG.md +++ b/packages/fcl-wagmi-adapter/CHANGELOG.md @@ -1,5 +1,12 @@ # @onflow/fcl-ethereum-provider +## 0.0.1-cross-vm.2 + +### Patch Changes + +- Updated dependencies [[`aa36c67b51394024f0be54b3e46917cfadc9cde8`](https://github.com/onflow/fcl-js/commit/aa36c67b51394024f0be54b3e46917cfadc9cde8)]: + - @onflow/fcl-ethereum-provider@0.0.1-cross-vm.3 + ## 0.0.1-cross-vm.1 ### Patch Changes diff --git a/packages/fcl-wagmi-adapter/package.json b/packages/fcl-wagmi-adapter/package.json index ec0259bd2..21da383b5 100644 --- a/packages/fcl-wagmi-adapter/package.json +++ b/packages/fcl-wagmi-adapter/package.json @@ -1,6 +1,6 @@ { "name": "@onflow/fcl-wagmi-adapter", - "version": "0.0.1-cross-vm.1", + "version": "0.0.1-cross-vm.2", "description": "Wagmi adapter for FCL-compatible wallets", "license": "Apache-2.0", "author": "Dapper Labs ", @@ -39,7 +39,7 @@ "@babel/runtime": "^7.25.7", "@ethersproject/bytes": "^5.7.0", "@ethersproject/hash": "^5.7.0", - "@onflow/fcl-ethereum-provider": "0.0.1-cross-vm.2", + "@onflow/fcl-ethereum-provider": "0.0.1-cross-vm.3", "@onflow/rlp": "^1.2.3", "@walletconnect/jsonrpc-http-connection": "^1.0.8", "@walletconnect/jsonrpc-provider": "^1.0.14", From 5d538ed28daa0e605aff7896416fed3002e2b150 Mon Sep 17 00:00:00 2001 From: Jordan Ribbink Date: Wed, 5 Mar 2025 16:11:28 -0800 Subject: [PATCH 56/56] Fix test --- packages/fcl-ethereum-provider/src/util/hash.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/fcl-ethereum-provider/src/util/hash.ts b/packages/fcl-ethereum-provider/src/util/hash.ts index 125083870..fafdca2cf 100644 --- a/packages/fcl-ethereum-provider/src/util/hash.ts +++ b/packages/fcl-ethereum-provider/src/util/hash.ts @@ -19,11 +19,11 @@ export function hashTypedDataLegacy(data: TypedData): string { export function hashTypedDataV3(data: TypedData): string { const domainSeparator = TypedDataEncoder.hashDomain(data.domain) - // Clone the types and remove the EIP712Domain entry if it exists. - const types = {...data.types} - delete types.EIP712Domain - - const messageHash = TypedDataEncoder.hash(data.domain, types, data.message) + const messageHash = TypedDataEncoder.hash( + data.domain, + data.types, + data.message + ) // The EIP‑191 prefix is "0x1901". const prefix = "0x1901" const digest = keccak_256( @@ -38,5 +38,9 @@ export function hashTypedDataV3(data: TypedData): string { * For many cases, v3 and v4 yield the same result (if you’re not using arrays or nested dynamic types). */ export function hashTypedDataV4(data: TypedData): string { - return hashTypedDataV3(data) + // Clone the types and remove the EIP712Domain entry if it exists. + const types = {...data.types} + delete types.EIP712Domain + + return hashTypedDataV3({...data, types}) }