From bbeb1c688b96633838df2a96f1b993606be66811 Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Wed, 25 Oct 2023 11:17:35 +0900 Subject: [PATCH 01/50] chore: bump typescript on packages --- .../balances-default-modules/package.json | 2 +- packages/balances-evm-erc20/package.json | 2 +- packages/balances-evm-native/package.json | 2 +- .../balances-substrate-assets/package.json | 2 +- .../package.json | 2 +- .../balances-substrate-native/package.json | 2 +- packages/balances-substrate-orml/package.json | 2 +- .../balances-substrate-psp22/package.json | 2 +- .../balances-substrate-tokens/package.json | 2 +- packages/balances/package.json | 2 +- packages/chain-connector-evm/package.json | 2 +- packages/chain-connector/package.json | 2 +- .../chaindata-provider-extension/package.json | 2 +- packages/chaindata-provider/package.json | 2 +- packages/connection-meta/package.json | 2 +- packages/icons/package.json | 2 +- packages/mutate-metadata/package.json | 2 +- packages/on-chain-id/package.json | 2 +- packages/orb/package.json | 2 +- packages/scale/package.json | 2 +- packages/talisman-ui/package.json | 2 +- packages/token-rates/package.json | 2 +- packages/util/package.json | 2 +- turbo.json | 3 + yarn.lock | 74 +++++++++---------- 25 files changed, 63 insertions(+), 60 deletions(-) diff --git a/packages/balances-default-modules/package.json b/packages/balances-default-modules/package.json index ac61a87a3d..6387193210 100644 --- a/packages/balances-default-modules/package.json +++ b/packages/balances-default-modules/package.json @@ -42,7 +42,7 @@ "eslint": "^8.4.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", - "typescript": "^4.6.4" + "typescript": "^5.2.2" }, "eslintConfig": { "root": true, diff --git a/packages/balances-evm-erc20/package.json b/packages/balances-evm-erc20/package.json index 15ea6a3f76..1710b979a8 100644 --- a/packages/balances-evm-erc20/package.json +++ b/packages/balances-evm-erc20/package.json @@ -42,7 +42,7 @@ "eslint": "^8.4.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", - "typescript": "^4.6.4" + "typescript": "^5.2.2" }, "peerDependencies": { "@polkadot/util": "11.x" diff --git a/packages/balances-evm-native/package.json b/packages/balances-evm-native/package.json index 13565f652f..6f985728d6 100644 --- a/packages/balances-evm-native/package.json +++ b/packages/balances-evm-native/package.json @@ -41,7 +41,7 @@ "eslint": "^8.4.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", - "typescript": "^4.6.4" + "typescript": "^5.2.2" }, "eslintConfig": { "root": true, diff --git a/packages/balances-substrate-assets/package.json b/packages/balances-substrate-assets/package.json index 4ee7857e53..f78b3c231d 100644 --- a/packages/balances-substrate-assets/package.json +++ b/packages/balances-substrate-assets/package.json @@ -42,7 +42,7 @@ "eslint": "^8.4.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", - "typescript": "^4.6.4" + "typescript": "^5.2.2" }, "peerDependencies": { "@polkadot/types": "10.x", diff --git a/packages/balances-substrate-equilibrium/package.json b/packages/balances-substrate-equilibrium/package.json index 87c0808a87..161a9c1848 100644 --- a/packages/balances-substrate-equilibrium/package.json +++ b/packages/balances-substrate-equilibrium/package.json @@ -42,7 +42,7 @@ "eslint": "^8.4.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", - "typescript": "^4.6.4" + "typescript": "^5.2.2" }, "peerDependencies": { "@polkadot/types": "10.x", diff --git a/packages/balances-substrate-native/package.json b/packages/balances-substrate-native/package.json index 24ebaa495c..6d9811d3e7 100644 --- a/packages/balances-substrate-native/package.json +++ b/packages/balances-substrate-native/package.json @@ -46,7 +46,7 @@ "eslint": "^8.4.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", - "typescript": "^4.6.4" + "typescript": "^5.2.2" }, "peerDependencies": { "@polkadot/types": "10.x", diff --git a/packages/balances-substrate-orml/package.json b/packages/balances-substrate-orml/package.json index 643b242f5c..67cf8597d6 100644 --- a/packages/balances-substrate-orml/package.json +++ b/packages/balances-substrate-orml/package.json @@ -41,7 +41,7 @@ "eslint": "^8.4.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", - "typescript": "^4.6.4" + "typescript": "^5.2.2" }, "peerDependencies": { "@polkadot/types": "10.x", diff --git a/packages/balances-substrate-psp22/package.json b/packages/balances-substrate-psp22/package.json index 5787787e88..f36d601e95 100644 --- a/packages/balances-substrate-psp22/package.json +++ b/packages/balances-substrate-psp22/package.json @@ -45,7 +45,7 @@ "eslint": "^8.4.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", - "typescript": "^4.6.4" + "typescript": "^5.2.2" }, "peerDependencies": { "@polkadot/api-contract": "^10.x", diff --git a/packages/balances-substrate-tokens/package.json b/packages/balances-substrate-tokens/package.json index 604d53a48e..ff42bf26ee 100644 --- a/packages/balances-substrate-tokens/package.json +++ b/packages/balances-substrate-tokens/package.json @@ -42,7 +42,7 @@ "eslint": "^8.4.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", - "typescript": "^4.6.4" + "typescript": "^5.2.2" }, "peerDependencies": { "@polkadot/types": "10.x", diff --git a/packages/balances/package.json b/packages/balances/package.json index efa0423331..62b41d065d 100644 --- a/packages/balances/package.json +++ b/packages/balances/package.json @@ -45,7 +45,7 @@ "eslint": "^8.4.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", - "typescript": "^4.6.4" + "typescript": "^5.2.2" }, "peerDependencies": { "@polkadot/types": "10.x" diff --git a/packages/chain-connector-evm/package.json b/packages/chain-connector-evm/package.json index f8be49ee09..f205fe6564 100644 --- a/packages/chain-connector-evm/package.json +++ b/packages/chain-connector-evm/package.json @@ -40,7 +40,7 @@ "eslint": "^8.4.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", - "typescript": "^4.6.4" + "typescript": "^5.2.2" }, "eslintConfig": { "root": true, diff --git a/packages/chain-connector/package.json b/packages/chain-connector/package.json index 6d30597f07..dc058cce8c 100644 --- a/packages/chain-connector/package.json +++ b/packages/chain-connector/package.json @@ -43,7 +43,7 @@ "eslint": "^8.4.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", - "typescript": "^4.6.4" + "typescript": "^5.2.2" }, "peerDependencies": { "@polkadot/rpc-provider": "10.x", diff --git a/packages/chaindata-provider-extension/package.json b/packages/chaindata-provider-extension/package.json index 74a161bc03..b8bdd8ca57 100644 --- a/packages/chaindata-provider-extension/package.json +++ b/packages/chaindata-provider-extension/package.json @@ -45,7 +45,7 @@ "jest": "^28.1.0", "ts-jest": "^28.0.2", "ts-node": "^10.9.1", - "typescript": "^4.6.4" + "typescript": "^5.2.2" }, "eslintConfig": { "root": true, diff --git a/packages/chaindata-provider/package.json b/packages/chaindata-provider/package.json index 8377d3e15f..6a78100159 100644 --- a/packages/chaindata-provider/package.json +++ b/packages/chaindata-provider/package.json @@ -33,7 +33,7 @@ "eslint": "^8.4.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", - "typescript": "^4.6.4" + "typescript": "^5.2.2" }, "preconstruct": { "entrypoints": [ diff --git a/packages/connection-meta/package.json b/packages/connection-meta/package.json index e723b3139a..e0a6f17352 100644 --- a/packages/connection-meta/package.json +++ b/packages/connection-meta/package.json @@ -36,7 +36,7 @@ "eslint": "^8.4.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", - "typescript": "^4.6.4" + "typescript": "^5.2.2" }, "eslintConfig": { "root": true, diff --git a/packages/icons/package.json b/packages/icons/package.json index 67844381a6..3ec214ff45 100644 --- a/packages/icons/package.json +++ b/packages/icons/package.json @@ -37,7 +37,7 @@ "jest": "^28.1.0", "react": "18.2.0", "ts-jest": "^28.0.2", - "typescript": "^4.6.4" + "typescript": "^5.2.2" }, "peerDependencies": { "react": ">=18.2.0" diff --git a/packages/mutate-metadata/package.json b/packages/mutate-metadata/package.json index 8844d32d40..79400f3b89 100644 --- a/packages/mutate-metadata/package.json +++ b/packages/mutate-metadata/package.json @@ -50,7 +50,7 @@ "eslint": "^8.4.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", - "typescript": "^4.6.4" + "typescript": "^5.2.2" }, "preconstruct": { "exports": { diff --git a/packages/on-chain-id/package.json b/packages/on-chain-id/package.json index d1c5c6a0da..fdc6a6cf0a 100644 --- a/packages/on-chain-id/package.json +++ b/packages/on-chain-id/package.json @@ -38,7 +38,7 @@ "eslint": "^8.4.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", - "typescript": "^4.6.4" + "typescript": "^5.2.2" }, "peerDependencies": { "@polkadot/types": "10.x" diff --git a/packages/orb/package.json b/packages/orb/package.json index 0ba39abb74..45365cbfa1 100644 --- a/packages/orb/package.json +++ b/packages/orb/package.json @@ -32,7 +32,7 @@ "eslint": "^8.15.0", "react": "18.2.0", "react-dom": "18.2.0", - "typescript": "4.6.4" + "typescript": "^5.2.2" }, "peerDependencies": { "react": "*", diff --git a/packages/scale/package.json b/packages/scale/package.json index 87d17fd07b..e2be3afb33 100644 --- a/packages/scale/package.json +++ b/packages/scale/package.json @@ -37,7 +37,7 @@ "eslint": "^8.4.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", - "typescript": "^4.6.4" + "typescript": "^5.2.2" }, "eslintConfig": { "root": true, diff --git a/packages/talisman-ui/package.json b/packages/talisman-ui/package.json index 88e48e5c89..a043e25df2 100644 --- a/packages/talisman-ui/package.json +++ b/packages/talisman-ui/package.json @@ -38,7 +38,7 @@ "react-router-dom": "6.14.1", "react-use": "17.4.0", "tailwindcss": "^3.3.2", - "typescript": "4.6.4" + "typescript": "^5.2.2" }, "peerDependencies": { "@floating-ui/react": ">=0.20.1", diff --git a/packages/token-rates/package.json b/packages/token-rates/package.json index 542d8c2599..0c73a86ec0 100644 --- a/packages/token-rates/package.json +++ b/packages/token-rates/package.json @@ -37,7 +37,7 @@ "eslint": "^8.4.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", - "typescript": "^4.6.4" + "typescript": "^5.2.2" }, "eslintConfig": { "root": true, diff --git a/packages/util/package.json b/packages/util/package.json index aa9dc2b063..82b5c7152b 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -39,7 +39,7 @@ "eslint": "^8.4.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", - "typescript": "^4.6.4" + "typescript": "^5.2.2" }, "peerDependencies": { "@polkadot/keyring": "12.x", diff --git a/turbo.json b/turbo.json index cfade89dc3..7872b14605 100644 --- a/turbo.json +++ b/turbo.json @@ -1,6 +1,9 @@ { "$schema": "https://turborepo.org/schema.json", "pipeline": { + "postinstall": { + "cache": false + }, "dev": { "cache": false }, diff --git a/yarn.lock b/yarn.lock index 1c3e51181e..e91626700f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7341,7 +7341,7 @@ __metadata: eslint: ^8.4.0 jest: ^28.1.0 ts-jest: ^28.0.2 - typescript: ^4.6.4 + typescript: ^5.2.2 languageName: unknown linkType: soft @@ -7363,7 +7363,7 @@ __metadata: jest: ^28.1.0 lodash: 4.17.21 ts-jest: ^28.0.2 - typescript: ^4.6.4 + typescript: ^5.2.2 peerDependencies: "@polkadot/util": 11.x languageName: unknown @@ -7386,7 +7386,7 @@ __metadata: jest: ^28.1.0 lodash: 4.17.21 ts-jest: ^28.0.2 - typescript: ^4.6.4 + typescript: ^5.2.2 languageName: unknown linkType: soft @@ -7440,7 +7440,7 @@ __metadata: eslint: ^8.4.0 jest: ^28.1.0 ts-jest: ^28.0.2 - typescript: ^4.6.4 + typescript: ^5.2.2 peerDependencies: "@polkadot/types": 10.x "@polkadot/util": 11.x @@ -7466,7 +7466,7 @@ __metadata: eslint: ^8.4.0 jest: ^28.1.0 ts-jest: ^28.0.2 - typescript: ^4.6.4 + typescript: ^5.2.2 peerDependencies: "@polkadot/types": 10.x "@polkadot/util": 11.x @@ -7496,7 +7496,7 @@ __metadata: lodash: 4.17.21 rxjs: ^7.8.1 ts-jest: ^28.0.2 - typescript: ^4.6.4 + typescript: ^5.2.2 peerDependencies: "@polkadot/types": 10.x "@polkadot/util": 11.x @@ -7522,7 +7522,7 @@ __metadata: eslint: ^8.4.0 jest: ^28.1.0 ts-jest: ^28.0.2 - typescript: ^4.6.4 + typescript: ^5.2.2 peerDependencies: "@polkadot/types": 10.x "@polkadot/util": 11.x @@ -7551,7 +7551,7 @@ __metadata: jest: ^28.1.0 lodash: 4.17.21 ts-jest: ^28.0.2 - typescript: ^4.6.4 + typescript: ^5.2.2 peerDependencies: "@polkadot/api-contract": ^10.x "@polkadot/types": 10.x @@ -7578,7 +7578,7 @@ __metadata: eslint: ^8.4.0 jest: ^28.1.0 ts-jest: ^28.0.2 - typescript: ^4.6.4 + typescript: ^5.2.2 peerDependencies: "@polkadot/types": 10.x "@polkadot/util": 11.x @@ -7606,7 +7606,7 @@ __metadata: jest: ^28.1.0 lodash: 4.17.21 ts-jest: ^28.0.2 - typescript: ^4.6.4 + typescript: ^5.2.2 peerDependencies: "@polkadot/types": 10.x languageName: unknown @@ -7628,7 +7628,7 @@ __metadata: jest: ^28.1.0 lodash: 4.17.21 ts-jest: ^28.0.2 - typescript: ^4.6.4 + typescript: ^5.2.2 languageName: unknown linkType: soft @@ -7651,7 +7651,7 @@ __metadata: eventemitter3: ^5.0.0 jest: ^28.1.0 ts-jest: ^28.0.2 - typescript: ^4.6.4 + typescript: ^5.2.2 peerDependencies: "@polkadot/rpc-provider": 10.x "@polkadot/util": 11.x @@ -7686,7 +7686,7 @@ __metadata: rxjs: ^7.8.1 ts-jest: ^28.0.2 ts-node: ^10.9.1 - typescript: ^4.6.4 + typescript: ^5.2.2 languageName: unknown linkType: soft @@ -7700,7 +7700,7 @@ __metadata: eslint: ^8.4.0 jest: ^28.1.0 ts-jest: ^28.0.2 - typescript: ^4.6.4 + typescript: ^5.2.2 languageName: unknown linkType: soft @@ -7716,7 +7716,7 @@ __metadata: eslint: ^8.4.0 jest: ^28.1.0 ts-jest: ^28.0.2 - typescript: ^4.6.4 + typescript: ^5.2.2 languageName: unknown linkType: soft @@ -7748,7 +7748,7 @@ __metadata: react: 18.2.0 react-icons: ^4.10.1 ts-jest: ^28.0.2 - typescript: ^4.6.4 + typescript: ^5.2.2 peerDependencies: react: ">=18.2.0" languageName: unknown @@ -7767,7 +7767,7 @@ __metadata: eslint: ^8.4.0 jest: ^28.1.0 ts-jest: ^28.0.2 - typescript: ^4.6.4 + typescript: ^5.2.2 languageName: unknown linkType: soft @@ -7785,7 +7785,7 @@ __metadata: eslint: ^8.4.0 jest: ^28.1.0 ts-jest: ^28.0.2 - typescript: ^4.6.4 + typescript: ^5.2.2 peerDependencies: "@polkadot/types": 10.x languageName: unknown @@ -7806,7 +7806,7 @@ __metadata: nanoid: 3.3.6 react: 18.2.0 react-dom: 18.2.0 - typescript: 4.6.4 + typescript: ^5.2.2 peerDependencies: react: "*" react-dom: "*" @@ -7825,7 +7825,7 @@ __metadata: eslint: ^8.4.0 jest: ^28.1.0 ts-jest: ^28.0.2 - typescript: ^4.6.4 + typescript: ^5.2.2 wat-the-crypto: ^0.0.3 languageName: unknown linkType: soft @@ -7850,7 +7850,7 @@ __metadata: eslint: ^8.4.0 jest: ^28.1.0 ts-jest: ^28.0.2 - typescript: ^4.6.4 + typescript: ^5.2.2 languageName: unknown linkType: soft @@ -7875,7 +7875,7 @@ __metadata: jest: ^28.1.0 tailwind-merge: ^1.13.2 ts-jest: ^28.0.2 - typescript: ^4.6.4 + typescript: ^5.2.2 peerDependencies: "@polkadot/keyring": 12.x "@polkadot/util": 12.x @@ -24212,7 +24212,7 @@ __metadata: react-router-dom: 6.14.1 react-use: 17.4.0 tailwindcss: ^3.3.2 - typescript: 4.6.4 + typescript: ^5.2.2 peerDependencies: "@floating-ui/react": ">=0.20.1" "@headlessui/react": ">=1.7.13" @@ -25204,16 +25204,6 @@ __metadata: languageName: node linkType: hard -"typescript@npm:4.6.4": - version: 4.6.4 - resolution: "typescript@npm:4.6.4" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: e7bfcc39cd4571a63a54e5ea21f16b8445268b9900bf55aee0e02ad981be576acc140eba24f1af5e3c1457767c96cea6d12861768fb386cf3ffb34013718631a - languageName: node - linkType: hard - "typescript@npm:4.9.4": version: 4.9.4 resolution: "typescript@npm:4.9.4" @@ -25254,13 +25244,13 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@4.6.4#~builtin": - version: 4.6.4 - resolution: "typescript@patch:typescript@npm%3A4.6.4#~builtin::version=4.6.4&hash=7ad353" +"typescript@npm:^5.2.2": + version: 5.2.2 + resolution: "typescript@npm:5.2.2" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 1cb434fbc637d347be90e3a0c6cd05e33c38f941713c8786d3031faf1842c2c148ba91d2fac01e7276b0ae3249b8633f1660e32686cc7a8c6a8fd5361dc52c66 + checksum: 7912821dac4d962d315c36800fe387cdc0a6298dba7ec171b350b4a6e988b51d7b8f051317786db1094bd7431d526b648aba7da8236607febb26cf5b871d2d3c languageName: node linkType: hard @@ -25304,6 +25294,16 @@ __metadata: languageName: node linkType: hard +"typescript@patch:typescript@^5.2.2#~builtin": + version: 5.2.2 + resolution: "typescript@patch:typescript@npm%3A5.2.2#~builtin::version=5.2.2&hash=7ad353" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 07106822b4305de3f22835cbba949a2b35451cad50888759b6818421290ff95d522b38ef7919e70fb381c5fe9c1c643d7dea22c8b31652a717ddbd57b7f4d554 + languageName: node + linkType: hard + "typeson-registry@npm:^1.0.0-alpha.20": version: 1.0.0-alpha.39 resolution: "typeson-registry@npm:1.0.0-alpha.39" From 8fa5729906b80820687c605a2c3d01c7a3827ed8 Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Wed, 25 Oct 2023 11:30:27 +0900 Subject: [PATCH 02/50] chore: extension upgrade to typescript 5 --- apps/extension/package.json | 10 +- config/eslint-config/package.json | 2 +- package.json | 4 +- .../balances-default-modules/package.json | 2 +- packages/balances-evm-erc20/package.json | 2 +- packages/balances-evm-native/package.json | 2 +- .../balances-substrate-assets/package.json | 2 +- .../package.json | 2 +- .../balances-substrate-native/package.json | 2 +- packages/balances-substrate-orml/package.json | 2 +- .../balances-substrate-psp22/package.json | 2 +- .../balances-substrate-tokens/package.json | 2 +- packages/balances/package.json | 2 +- packages/chain-connector-evm/package.json | 2 +- packages/chain-connector/package.json | 2 +- .../chaindata-provider-extension/package.json | 2 +- packages/chaindata-provider/package.json | 2 +- packages/connection-meta/package.json | 2 +- packages/icons/package.json | 2 +- packages/mutate-metadata/package.json | 2 +- packages/on-chain-id/package.json | 2 +- packages/orb/package.json | 2 +- packages/scale/package.json | 2 +- packages/talisman-ui/package.json | 2 +- packages/token-rates/package.json | 2 +- packages/util/package.json | 2 +- yarn.lock | 521 +++++++++++++++--- 27 files changed, 471 insertions(+), 112 deletions(-) diff --git a/apps/extension/package.json b/apps/extension/package.json index c9c3f72067..406f2269f9 100644 --- a/apps/extension/package.json +++ b/apps/extension/package.json @@ -71,8 +71,6 @@ "@types/react-highlight": "^0.12.5", "@types/uuid": "^8.3.4", "@types/webextension-polyfill": "0.9.1", - "@typescript-eslint/eslint-plugin": "^5.47.0", - "@typescript-eslint/parser": "^5.47.0", "anylogger": "^1.0.11", "anylogger-loglevel": "^1.0.0", "assert": "^2.0.0", @@ -137,7 +135,7 @@ "toml": "^3.0.0", "ts-loader": "9.4.4", "ts-results": "3.3.0", - "typescript": "4.9.4", + "typescript": "^5.2.2", "url-join": "^5.0.0", "uuid": "^8.3.2", "webextension-polyfill": "0.8.0", @@ -163,14 +161,16 @@ "@types/bcryptjs": "^2.4.2", "@types/lz-string": "^1.3.34", "@types/semver": "^7.3.9", + "@typescript-eslint/eslint-plugin": "^6.9.0", + "@typescript-eslint/parser": "^6.9.0", "archiver": "5.3.1", "autoprefixer": "^10.4.12", "babel-jest": "27.5.1", "babel-plugin-module-extension-resolver": "^1.0.0", "browserify-zlib": "^0.2.0", "crypto-browserify": "3.12.0", - "eslint": "^8.15.0", - "eslint-webpack-plugin": "^3.1.1", + "eslint": "^8.52.0", + "eslint-webpack-plugin": "^4.0.1", "fake-indexeddb": "4.0.1", "html-webpack-plugin": "^5.5.3", "https-browserify": "^1.0.0", diff --git a/config/eslint-config/package.json b/config/eslint-config/package.json index c875504961..0af3f6793c 100644 --- a/config/eslint-config/package.json +++ b/config/eslint-config/package.json @@ -8,7 +8,7 @@ "clean": "rm -rf node_modules" }, "dependencies": { - "eslint": "^8.15.0", + "eslint": "^8.52.0", "eslint-config-prettier": "8.5.0", "eslint-plugin-import": "2.27.5", "eslint-plugin-jest": "26.1.5", diff --git a/package.json b/package.json index 7f01e36b33..edc770577f 100644 --- a/package.json +++ b/package.json @@ -34,8 +34,8 @@ "@commitlint/config-conventional": "^17.0.0", "@talismn/eslint-config": "workspace:*", "@testing-library/react": "^14.0.0", - "@typescript-eslint/eslint-plugin": "^5.33.0", - "@typescript-eslint/parser": "^5.33.0", + "@typescript-eslint/eslint-plugin": "^6.9.0", + "@typescript-eslint/parser": "^6.9.0", "concurrently": "^7.6.0", "husky": "^8.0.3", "import-sort-style-module": "^6.0.0", diff --git a/packages/balances-default-modules/package.json b/packages/balances-default-modules/package.json index 6387193210..59b4edbb93 100644 --- a/packages/balances-default-modules/package.json +++ b/packages/balances-default-modules/package.json @@ -39,7 +39,7 @@ "@talismn/eslint-config": "workspace:*", "@talismn/tsconfig": "workspace:*", "@types/jest": "^27.5.1", - "eslint": "^8.4.0", + "eslint": "^8.52.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", "typescript": "^5.2.2" diff --git a/packages/balances-evm-erc20/package.json b/packages/balances-evm-erc20/package.json index 1710b979a8..3f5c44aa7f 100644 --- a/packages/balances-evm-erc20/package.json +++ b/packages/balances-evm-erc20/package.json @@ -39,7 +39,7 @@ "@talismn/tsconfig": "workspace:*", "@types/jest": "^27.5.1", "@types/lodash": "^4.14.180", - "eslint": "^8.4.0", + "eslint": "^8.52.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", "typescript": "^5.2.2" diff --git a/packages/balances-evm-native/package.json b/packages/balances-evm-native/package.json index 6f985728d6..91f8ce6e5c 100644 --- a/packages/balances-evm-native/package.json +++ b/packages/balances-evm-native/package.json @@ -38,7 +38,7 @@ "@talismn/tsconfig": "workspace:*", "@types/jest": "^27.5.1", "@types/lodash": "^4.14.180", - "eslint": "^8.4.0", + "eslint": "^8.52.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", "typescript": "^5.2.2" diff --git a/packages/balances-substrate-assets/package.json b/packages/balances-substrate-assets/package.json index f78b3c231d..8c2cf49a5a 100644 --- a/packages/balances-substrate-assets/package.json +++ b/packages/balances-substrate-assets/package.json @@ -39,7 +39,7 @@ "@talismn/eslint-config": "workspace:*", "@talismn/tsconfig": "workspace:*", "@types/jest": "^27.5.1", - "eslint": "^8.4.0", + "eslint": "^8.52.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", "typescript": "^5.2.2" diff --git a/packages/balances-substrate-equilibrium/package.json b/packages/balances-substrate-equilibrium/package.json index 161a9c1848..d6485178c1 100644 --- a/packages/balances-substrate-equilibrium/package.json +++ b/packages/balances-substrate-equilibrium/package.json @@ -39,7 +39,7 @@ "@talismn/eslint-config": "workspace:*", "@talismn/tsconfig": "workspace:*", "@types/jest": "^27.5.1", - "eslint": "^8.4.0", + "eslint": "^8.52.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", "typescript": "^5.2.2" diff --git a/packages/balances-substrate-native/package.json b/packages/balances-substrate-native/package.json index 6d9811d3e7..ec7ab8c407 100644 --- a/packages/balances-substrate-native/package.json +++ b/packages/balances-substrate-native/package.json @@ -43,7 +43,7 @@ "@talismn/tsconfig": "workspace:*", "@types/jest": "^27.5.1", "@types/lodash": "^4.14.180", - "eslint": "^8.4.0", + "eslint": "^8.52.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", "typescript": "^5.2.2" diff --git a/packages/balances-substrate-orml/package.json b/packages/balances-substrate-orml/package.json index 67cf8597d6..d52569ecc5 100644 --- a/packages/balances-substrate-orml/package.json +++ b/packages/balances-substrate-orml/package.json @@ -38,7 +38,7 @@ "@talismn/eslint-config": "workspace:*", "@talismn/tsconfig": "workspace:*", "@types/jest": "^27.5.1", - "eslint": "^8.4.0", + "eslint": "^8.52.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", "typescript": "^5.2.2" diff --git a/packages/balances-substrate-psp22/package.json b/packages/balances-substrate-psp22/package.json index f36d601e95..cc299ffc04 100644 --- a/packages/balances-substrate-psp22/package.json +++ b/packages/balances-substrate-psp22/package.json @@ -42,7 +42,7 @@ "@talismn/tsconfig": "workspace:*", "@types/jest": "^27.5.1", "@types/lodash": "^4.14.180", - "eslint": "^8.4.0", + "eslint": "^8.52.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", "typescript": "^5.2.2" diff --git a/packages/balances-substrate-tokens/package.json b/packages/balances-substrate-tokens/package.json index ff42bf26ee..734955fb36 100644 --- a/packages/balances-substrate-tokens/package.json +++ b/packages/balances-substrate-tokens/package.json @@ -39,7 +39,7 @@ "@talismn/eslint-config": "workspace:*", "@talismn/tsconfig": "workspace:*", "@types/jest": "^27.5.1", - "eslint": "^8.4.0", + "eslint": "^8.52.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", "typescript": "^5.2.2" diff --git a/packages/balances/package.json b/packages/balances/package.json index 62b41d065d..8fae08cc0b 100644 --- a/packages/balances/package.json +++ b/packages/balances/package.json @@ -42,7 +42,7 @@ "@talismn/tsconfig": "workspace:*", "@types/jest": "^27.5.1", "@types/lodash": "^4.14.180", - "eslint": "^8.4.0", + "eslint": "^8.52.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", "typescript": "^5.2.2" diff --git a/packages/chain-connector-evm/package.json b/packages/chain-connector-evm/package.json index f205fe6564..1c348f34bc 100644 --- a/packages/chain-connector-evm/package.json +++ b/packages/chain-connector-evm/package.json @@ -37,7 +37,7 @@ "@talismn/tsconfig": "workspace:*", "@types/jest": "^27.5.1", "@types/lodash": "^4.14.180", - "eslint": "^8.4.0", + "eslint": "^8.52.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", "typescript": "^5.2.2" diff --git a/packages/chain-connector/package.json b/packages/chain-connector/package.json index dc058cce8c..974a254b19 100644 --- a/packages/chain-connector/package.json +++ b/packages/chain-connector/package.json @@ -40,7 +40,7 @@ "@talismn/eslint-config": "workspace:*", "@talismn/tsconfig": "workspace:*", "@types/jest": "^27.5.1", - "eslint": "^8.4.0", + "eslint": "^8.52.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", "typescript": "^5.2.2" diff --git a/packages/chaindata-provider-extension/package.json b/packages/chaindata-provider-extension/package.json index b8bdd8ca57..21b4172c80 100644 --- a/packages/chaindata-provider-extension/package.json +++ b/packages/chaindata-provider-extension/package.json @@ -41,7 +41,7 @@ "@talismn/eslint-config": "workspace:*", "@talismn/tsconfig": "workspace:*", "@types/jest": "^27.5.1", - "eslint": "^8.4.0", + "eslint": "^8.52.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", "ts-node": "^10.9.1", diff --git a/packages/chaindata-provider/package.json b/packages/chaindata-provider/package.json index 6a78100159..ec93ad623a 100644 --- a/packages/chaindata-provider/package.json +++ b/packages/chaindata-provider/package.json @@ -30,7 +30,7 @@ "@talismn/eslint-config": "workspace:*", "@talismn/tsconfig": "workspace:*", "@types/jest": "^27.5.1", - "eslint": "^8.4.0", + "eslint": "^8.52.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", "typescript": "^5.2.2" diff --git a/packages/connection-meta/package.json b/packages/connection-meta/package.json index e0a6f17352..3e7b3f2228 100644 --- a/packages/connection-meta/package.json +++ b/packages/connection-meta/package.json @@ -33,7 +33,7 @@ "@talismn/eslint-config": "workspace:*", "@talismn/tsconfig": "workspace:*", "@types/jest": "^27.5.1", - "eslint": "^8.4.0", + "eslint": "^8.52.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", "typescript": "^5.2.2" diff --git a/packages/icons/package.json b/packages/icons/package.json index 3ec214ff45..d782c701a8 100644 --- a/packages/icons/package.json +++ b/packages/icons/package.json @@ -33,7 +33,7 @@ "@talismn/tsconfig": "workspace:*", "@types/jest": "^27.5.1", "@types/react": "18.0.14", - "eslint": "^8.4.0", + "eslint": "^8.52.0", "jest": "^28.1.0", "react": "18.2.0", "ts-jest": "^28.0.2", diff --git a/packages/mutate-metadata/package.json b/packages/mutate-metadata/package.json index 79400f3b89..46f5ed8f12 100644 --- a/packages/mutate-metadata/package.json +++ b/packages/mutate-metadata/package.json @@ -47,7 +47,7 @@ "@talismn/eslint-config": "workspace:*", "@talismn/tsconfig": "workspace:*", "@types/jest": "^27.5.1", - "eslint": "^8.4.0", + "eslint": "^8.52.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", "typescript": "^5.2.2" diff --git a/packages/on-chain-id/package.json b/packages/on-chain-id/package.json index fdc6a6cf0a..d71185a3b9 100644 --- a/packages/on-chain-id/package.json +++ b/packages/on-chain-id/package.json @@ -35,7 +35,7 @@ "@talismn/eslint-config": "workspace:*", "@talismn/tsconfig": "workspace:*", "@types/jest": "^27.5.1", - "eslint": "^8.4.0", + "eslint": "^8.52.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", "typescript": "^5.2.2" diff --git a/packages/orb/package.json b/packages/orb/package.json index 45365cbfa1..0c25c05a17 100644 --- a/packages/orb/package.json +++ b/packages/orb/package.json @@ -29,7 +29,7 @@ "@talismn/tsconfig": "workspace:*", "@types/react": "18.0.14", "@types/react-dom": "18.0.5", - "eslint": "^8.15.0", + "eslint": "^8.52.0", "react": "18.2.0", "react-dom": "18.2.0", "typescript": "^5.2.2" diff --git a/packages/scale/package.json b/packages/scale/package.json index e2be3afb33..b7b6625c08 100644 --- a/packages/scale/package.json +++ b/packages/scale/package.json @@ -34,7 +34,7 @@ "@talismn/eslint-config": "workspace:*", "@talismn/tsconfig": "workspace:*", "@types/jest": "^27.5.1", - "eslint": "^8.4.0", + "eslint": "^8.52.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", "typescript": "^5.2.2" diff --git a/packages/talisman-ui/package.json b/packages/talisman-ui/package.json index a043e25df2..ea445d2c9a 100644 --- a/packages/talisman-ui/package.json +++ b/packages/talisman-ui/package.json @@ -29,7 +29,7 @@ "@types/react-dom": "18.0.5", "autoprefixer": "^10.4.12", "color": "4.2.3", - "eslint": "^8.15.0", + "eslint": "^8.52.0", "framer-motion": "10.12.18", "lottie-react": "2.4.0", "postcss": "^8.4.20", diff --git a/packages/token-rates/package.json b/packages/token-rates/package.json index 0c73a86ec0..8559f29e49 100644 --- a/packages/token-rates/package.json +++ b/packages/token-rates/package.json @@ -34,7 +34,7 @@ "@talismn/eslint-config": "workspace:*", "@talismn/tsconfig": "workspace:*", "@types/jest": "^27.5.1", - "eslint": "^8.4.0", + "eslint": "^8.52.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", "typescript": "^5.2.2" diff --git a/packages/util/package.json b/packages/util/package.json index 82b5c7152b..9cc28af89e 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -36,7 +36,7 @@ "@talismn/eslint-config": "workspace:*", "@talismn/tsconfig": "workspace:*", "@types/jest": "^27.5.1", - "eslint": "^8.4.0", + "eslint": "^8.52.0", "jest": "^28.1.0", "ts-jest": "^28.0.2", "typescript": "^5.2.2" diff --git a/yarn.lock b/yarn.lock index e91626700f..d9b231b3ff 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,6 +5,13 @@ __metadata: version: 6 cacheKey: 8 +"@aashutoshrathi/word-wrap@npm:^1.2.3": + version: 1.2.6 + resolution: "@aashutoshrathi/word-wrap@npm:1.2.6" + checksum: ada901b9e7c680d190f1d012c84217ce0063d8f5c5a7725bb91ec3c5ed99bb7572680eb2d2938a531ccbaec39a95422fcd8a6b4a13110c7d98dd75402f66a0cd + languageName: node + linkType: hard + "@acala-network/type-definitions@npm:5.1.1": version: 5.1.1 resolution: "@acala-network/type-definitions@npm:5.1.1" @@ -2727,6 +2734,24 @@ __metadata: languageName: node linkType: hard +"@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0": + version: 4.4.0 + resolution: "@eslint-community/eslint-utils@npm:4.4.0" + dependencies: + eslint-visitor-keys: ^3.3.0 + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + checksum: cdfe3ae42b4f572cbfb46d20edafe6f36fc5fb52bf2d90875c58aefe226892b9677fef60820e2832caf864a326fe4fc225714c46e8389ccca04d5f9288aabd22 + languageName: node + linkType: hard + +"@eslint-community/regexpp@npm:^4.5.1, @eslint-community/regexpp@npm:^4.6.1": + version: 4.9.1 + resolution: "@eslint-community/regexpp@npm:4.9.1" + checksum: 06fb839e9c756f6375cc545c2f2e05a0a64576bd6370e8e3c07983fd29a3d6e164ef4aa48a361f7d27e6713ab79c83053ff6a2ccb78748bc955e344279c4a3b6 + languageName: node + linkType: hard + "@eslint/eslintrc@npm:^0.4.3": version: 0.4.3 resolution: "@eslint/eslintrc@npm:0.4.3" @@ -2761,6 +2786,30 @@ __metadata: languageName: node linkType: hard +"@eslint/eslintrc@npm:^2.1.2": + version: 2.1.2 + resolution: "@eslint/eslintrc@npm:2.1.2" + dependencies: + ajv: ^6.12.4 + debug: ^4.3.2 + espree: ^9.6.0 + globals: ^13.19.0 + ignore: ^5.2.0 + import-fresh: ^3.2.1 + js-yaml: ^4.1.0 + minimatch: ^3.1.2 + strip-json-comments: ^3.1.1 + checksum: bc742a1e3b361f06fedb4afb6bf32cbd27171292ef7924f61c62f2aed73048367bcc7ac68f98c06d4245cd3fabc43270f844e3c1699936d4734b3ac5398814a7 + languageName: node + linkType: hard + +"@eslint/js@npm:8.52.0": + version: 8.52.0 + resolution: "@eslint/js@npm:8.52.0" + checksum: 490893b8091a66415f4ac98b963d23eb287264ea3bd6af7ec788f0570705cf64fd6ab84b717785980f55e39d08ff5c7fde6d8e4391ccb507169370ce3a6d091a + languageName: node + linkType: hard + "@ethereumjs/rlp@npm:^4.0.0-beta.2, @ethereumjs/rlp@npm:^4.0.1": version: 4.0.1 resolution: "@ethereumjs/rlp@npm:4.0.1" @@ -3885,6 +3934,17 @@ __metadata: languageName: node linkType: hard +"@humanwhocodes/config-array@npm:^0.11.13": + version: 0.11.13 + resolution: "@humanwhocodes/config-array@npm:0.11.13" + dependencies: + "@humanwhocodes/object-schema": ^2.0.1 + debug: ^4.1.1 + minimatch: ^3.0.5 + checksum: f8ea57b0d7ed7f2d64cd3944654976829d9da91c04d9c860e18804729a33f7681f78166ef4c761850b8c324d362f7d53f14c5c44907a6b38b32c703ff85e4805 + languageName: node + linkType: hard + "@humanwhocodes/config-array@npm:^0.5.0": version: 0.5.0 resolution: "@humanwhocodes/config-array@npm:0.5.0" @@ -3903,6 +3963,13 @@ __metadata: languageName: node linkType: hard +"@humanwhocodes/module-importer@npm:^1.0.1": + version: 1.0.1 + resolution: "@humanwhocodes/module-importer@npm:1.0.1" + checksum: 0fd22007db8034a2cdf2c764b140d37d9020bbfce8a49d3ec5c05290e77d4b0263b1b972b752df8c89e5eaa94073408f2b7d977aed131faf6cf396ebb5d7fb61 + languageName: node + linkType: hard + "@humanwhocodes/object-schema@npm:^1.2.0, @humanwhocodes/object-schema@npm:^1.2.1": version: 1.2.1 resolution: "@humanwhocodes/object-schema@npm:1.2.1" @@ -3910,6 +3977,13 @@ __metadata: languageName: node linkType: hard +"@humanwhocodes/object-schema@npm:^2.0.1": + version: 2.0.1 + resolution: "@humanwhocodes/object-schema@npm:2.0.1" + checksum: 24929487b1ed48795d2f08346a0116cc5ee4634848bce64161fb947109352c562310fd159fc64dda0e8b853307f5794605191a9547f7341158559ca3c8262a45 + languageName: node + linkType: hard + "@interlay/interbtc-types@npm:1.12.0": version: 1.12.0 resolution: "@interlay/interbtc-types@npm:1.12.0" @@ -4235,6 +4309,15 @@ __metadata: languageName: node linkType: hard +"@jest/schemas@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/schemas@npm:29.6.3" + dependencies: + "@sinclair/typebox": ^0.27.8 + checksum: 910040425f0fc93cd13e68c750b7885590b8839066dfa0cd78e7def07bbb708ad869381f725945d66f2284de5663bbecf63e8fdd856e2ae6e261ba30b1687e93 + languageName: node + linkType: hard + "@jest/source-map@npm:^27.5.1": version: 27.5.1 resolution: "@jest/source-map@npm:27.5.1" @@ -4378,6 +4461,20 @@ __metadata: languageName: node linkType: hard +"@jest/types@npm:^29.6.3": + version: 29.6.3 + resolution: "@jest/types@npm:29.6.3" + dependencies: + "@jest/schemas": ^29.6.3 + "@types/istanbul-lib-coverage": ^2.0.0 + "@types/istanbul-reports": ^3.0.0 + "@types/node": "*" + "@types/yargs": ^17.0.8 + chalk: ^4.0.0 + checksum: a0bcf15dbb0eca6bdd8ce61a3fb055349d40268622a7670a3b2eb3c3dbafe9eb26af59938366d520b86907b9505b0f9b29b85cec11579a9e580694b87cd90fcc + languageName: node + linkType: hard + "@jridgewell/gen-mapping@npm:^0.3.0, @jridgewell/gen-mapping@npm:^0.3.2": version: 0.3.2 resolution: "@jridgewell/gen-mapping@npm:0.3.2" @@ -4991,7 +5088,7 @@ __metadata: languageName: node linkType: hard -"@nodelib/fs.walk@npm:^1.2.3": +"@nodelib/fs.walk@npm:^1.2.3, @nodelib/fs.walk@npm:^1.2.8": version: 1.2.8 resolution: "@nodelib/fs.walk@npm:1.2.8" dependencies: @@ -6574,6 +6671,13 @@ __metadata: languageName: node linkType: hard +"@sinclair/typebox@npm:^0.27.8": + version: 0.27.8 + resolution: "@sinclair/typebox@npm:0.27.8" + checksum: 00bd7362a3439021aa1ea51b0e0d0a0e8ca1351a3d54c606b115fdcc49b51b16db6e5f43b4fe7a28c38688523e22a94d49dd31168868b655f0d4d50f032d07a1 + languageName: node + linkType: hard + "@sindresorhus/df@npm:^1.0.1": version: 1.0.1 resolution: "@sindresorhus/df@npm:1.0.1" @@ -7338,7 +7442,7 @@ __metadata: "@talismn/eslint-config": "workspace:*" "@talismn/tsconfig": "workspace:*" "@types/jest": ^27.5.1 - eslint: ^8.4.0 + eslint: ^8.52.0 jest: ^28.1.0 ts-jest: ^28.0.2 typescript: ^5.2.2 @@ -7358,7 +7462,7 @@ __metadata: "@types/jest": ^27.5.1 "@types/lodash": ^4.14.180 anylogger: ^1.0.11 - eslint: ^8.4.0 + eslint: ^8.52.0 ethers: 5.7.2 jest: ^28.1.0 lodash: 4.17.21 @@ -7381,7 +7485,7 @@ __metadata: "@types/jest": ^27.5.1 "@types/lodash": ^4.14.180 anylogger: ^1.0.11 - eslint: ^8.4.0 + eslint: ^8.52.0 ethers: 5.7.2 jest: ^28.1.0 lodash: 4.17.21 @@ -7437,7 +7541,7 @@ __metadata: "@talismn/util": "workspace:*" "@types/jest": ^27.5.1 anylogger: ^1.0.11 - eslint: ^8.4.0 + eslint: ^8.52.0 jest: ^28.1.0 ts-jest: ^28.0.2 typescript: ^5.2.2 @@ -7463,7 +7567,7 @@ __metadata: "@talismn/util": "workspace:*" "@types/jest": ^27.5.1 anylogger: ^1.0.11 - eslint: ^8.4.0 + eslint: ^8.52.0 jest: ^28.1.0 ts-jest: ^28.0.2 typescript: ^5.2.2 @@ -7491,7 +7595,7 @@ __metadata: "@types/jest": ^27.5.1 "@types/lodash": ^4.14.180 anylogger: ^1.0.11 - eslint: ^8.4.0 + eslint: ^8.52.0 jest: ^28.1.0 lodash: 4.17.21 rxjs: ^7.8.1 @@ -7519,7 +7623,7 @@ __metadata: "@talismn/util": "workspace:*" "@types/jest": ^27.5.1 anylogger: ^1.0.11 - eslint: ^8.4.0 + eslint: ^8.52.0 jest: ^28.1.0 ts-jest: ^28.0.2 typescript: ^5.2.2 @@ -7547,7 +7651,7 @@ __metadata: "@types/jest": ^27.5.1 "@types/lodash": ^4.14.180 anylogger: ^1.0.11 - eslint: ^8.4.0 + eslint: ^8.52.0 jest: ^28.1.0 lodash: 4.17.21 ts-jest: ^28.0.2 @@ -7575,7 +7679,7 @@ __metadata: "@talismn/util": "workspace:*" "@types/jest": ^27.5.1 anylogger: ^1.0.11 - eslint: ^8.4.0 + eslint: ^8.52.0 jest: ^28.1.0 ts-jest: ^28.0.2 typescript: ^5.2.2 @@ -7602,7 +7706,7 @@ __metadata: "@types/lodash": ^4.14.180 anylogger: ^1.0.11 dexie: ^3.2.4 - eslint: ^8.4.0 + eslint: ^8.52.0 jest: ^28.1.0 lodash: 4.17.21 ts-jest: ^28.0.2 @@ -7623,7 +7727,7 @@ __metadata: "@types/jest": ^27.5.1 "@types/lodash": ^4.14.180 anylogger: ^1.0.11 - eslint: ^8.4.0 + eslint: ^8.52.0 ethers: 5.7.2 jest: ^28.1.0 lodash: 4.17.21 @@ -7647,7 +7751,7 @@ __metadata: "@talismn/util": "workspace:*" "@types/jest": ^27.5.1 anylogger: ^1.0.11 - eslint: ^8.4.0 + eslint: ^8.52.0 eventemitter3: ^5.0.0 jest: ^28.1.0 ts-jest: ^28.0.2 @@ -7679,7 +7783,7 @@ __metadata: "@types/jest": ^27.5.1 anylogger: ^1.0.11 dexie: ^3.2.4 - eslint: ^8.4.0 + eslint: ^8.52.0 graphql: ^16.7.1 graphql-request: ^6.1.0 jest: ^28.1.0 @@ -7697,7 +7801,7 @@ __metadata: "@talismn/eslint-config": "workspace:*" "@talismn/tsconfig": "workspace:*" "@types/jest": ^27.5.1 - eslint: ^8.4.0 + eslint: ^8.52.0 jest: ^28.1.0 ts-jest: ^28.0.2 typescript: ^5.2.2 @@ -7713,7 +7817,7 @@ __metadata: "@talismn/tsconfig": "workspace:*" "@types/jest": ^27.5.1 dexie: ^3.2.4 - eslint: ^8.4.0 + eslint: ^8.52.0 jest: ^28.1.0 ts-jest: ^28.0.2 typescript: ^5.2.2 @@ -7724,7 +7828,7 @@ __metadata: version: 0.0.0-use.local resolution: "@talismn/eslint-config@workspace:config/eslint-config" dependencies: - eslint: ^8.15.0 + eslint: ^8.52.0 eslint-config-prettier: 8.5.0 eslint-plugin-import: 2.27.5 eslint-plugin-jest: 26.1.5 @@ -7743,7 +7847,7 @@ __metadata: "@talismn/tsconfig": "workspace:*" "@types/jest": ^27.5.1 "@types/react": 18.0.14 - eslint: ^8.4.0 + eslint: ^8.52.0 jest: ^28.1.0 react: 18.2.0 react-icons: ^4.10.1 @@ -7764,7 +7868,7 @@ __metadata: "@talismn/tsconfig": "workspace:*" "@types/jest": ^27.5.1 anylogger: ^1.0.11 - eslint: ^8.4.0 + eslint: ^8.52.0 jest: ^28.1.0 ts-jest: ^28.0.2 typescript: ^5.2.2 @@ -7782,7 +7886,7 @@ __metadata: "@talismn/util": "workspace:*" "@types/jest": ^27.5.1 anylogger: ^1.0.11 - eslint: ^8.4.0 + eslint: ^8.52.0 jest: ^28.1.0 ts-jest: ^28.0.2 typescript: ^5.2.2 @@ -7802,7 +7906,7 @@ __metadata: "@types/react-dom": 18.0.5 blueimp-md5: 2.19.0 color: 4.2.3 - eslint: ^8.15.0 + eslint: ^8.52.0 nanoid: 3.3.6 react: 18.2.0 react-dom: 18.2.0 @@ -7822,7 +7926,7 @@ __metadata: "@talismn/tsconfig": "workspace:*" "@types/jest": ^27.5.1 anylogger: ^1.0.11 - eslint: ^8.4.0 + eslint: ^8.52.0 jest: ^28.1.0 ts-jest: ^28.0.2 typescript: ^5.2.2 @@ -7847,7 +7951,7 @@ __metadata: "@types/jest": ^27.5.1 axios: ^0.27.2 dexie: ^3.2.4 - eslint: ^8.4.0 + eslint: ^8.52.0 jest: ^28.1.0 ts-jest: ^28.0.2 typescript: ^5.2.2 @@ -7871,7 +7975,7 @@ __metadata: "@talismn/tsconfig": "workspace:*" "@types/jest": ^27.5.1 bignumber.js: ^9.1.1 - eslint: ^8.4.0 + eslint: ^8.52.0 jest: ^28.1.0 tailwind-merge: ^1.13.2 ts-jest: ^28.0.2 @@ -8266,7 +8370,7 @@ __metadata: languageName: node linkType: hard -"@types/eslint@npm:*, @types/eslint@npm:^7.29.0 || ^8.4.1": +"@types/eslint@npm:*": version: 8.4.5 resolution: "@types/eslint@npm:8.4.5" dependencies: @@ -8276,6 +8380,16 @@ __metadata: languageName: node linkType: hard +"@types/eslint@npm:^8.37.0": + version: 8.44.6 + resolution: "@types/eslint@npm:8.44.6" + dependencies: + "@types/estree": "*" + "@types/json-schema": "*" + checksum: ed8de582ab3dbd7ec0bf97d41f4f3de28dd8a37fc48bc423e1c406bbb70d1fd8c4175ba17ad6495ef9ef99a43df71421277b7a2a0355097489c4c4cf6bb266ff + languageName: node + linkType: hard + "@types/estree@npm:*, @types/estree@npm:^1.0.0": version: 1.0.0 resolution: "@types/estree@npm:1.0.0" @@ -8424,6 +8538,13 @@ __metadata: languageName: node linkType: hard +"@types/json-schema@npm:^7.0.12": + version: 7.0.14 + resolution: "@types/json-schema@npm:7.0.14" + checksum: 4b3dd99616c7c808201c56f6c7f6552eb67b5c0c753ab3fa03a6cb549aae950da537e9558e53fa65fba23d1be624a1e4e8d20c15027efbe41e03ca56f2b04fb0 + languageName: node + linkType: hard + "@types/json-stable-stringify@npm:^1.0.32": version: 1.0.34 resolution: "@types/json-stable-stringify@npm:1.0.34" @@ -8636,6 +8757,13 @@ __metadata: languageName: node linkType: hard +"@types/semver@npm:^7.5.0": + version: 7.5.4 + resolution: "@types/semver@npm:7.5.4" + checksum: 120c0189f6fec5f2d12d0d71ac8a4cfa952dc17fa3d842e8afddb82bba8828a4052f8799c1653e2b47ae1977435f38e8985658fde971905ce5afb8e23ee97ecf + languageName: node + linkType: hard + "@types/source-list-map@npm:*": version: 0.1.2 resolution: "@types/source-list-map@npm:0.1.2" @@ -8773,43 +8901,46 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:^5.33.0, @typescript-eslint/eslint-plugin@npm:^5.47.0": - version: 5.47.1 - resolution: "@typescript-eslint/eslint-plugin@npm:5.47.1" +"@typescript-eslint/eslint-plugin@npm:^6.9.0": + version: 6.9.0 + resolution: "@typescript-eslint/eslint-plugin@npm:6.9.0" dependencies: - "@typescript-eslint/scope-manager": 5.47.1 - "@typescript-eslint/type-utils": 5.47.1 - "@typescript-eslint/utils": 5.47.1 + "@eslint-community/regexpp": ^4.5.1 + "@typescript-eslint/scope-manager": 6.9.0 + "@typescript-eslint/type-utils": 6.9.0 + "@typescript-eslint/utils": 6.9.0 + "@typescript-eslint/visitor-keys": 6.9.0 debug: ^4.3.4 - ignore: ^5.2.0 - natural-compare-lite: ^1.4.0 - regexpp: ^3.2.0 - semver: ^7.3.7 - tsutils: ^3.21.0 + graphemer: ^1.4.0 + ignore: ^5.2.4 + natural-compare: ^1.4.0 + semver: ^7.5.4 + ts-api-utils: ^1.0.1 peerDependencies: - "@typescript-eslint/parser": ^5.0.0 - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + "@typescript-eslint/parser": ^6.0.0 || ^6.0.0-alpha + eslint: ^7.0.0 || ^8.0.0 peerDependenciesMeta: typescript: optional: true - checksum: 59fe719a8fbad14c37b8ce0dd292f6b8066bba370090f5e40eeab03033b97a12df1f1d0963c7070ac8cf4f7f319974fa6747e70932660055d222fa993c239b6a + checksum: 51d7afc18bab711e20147f7163083f05435b6860174169eae56de217ed2cb1b3c08cba01b7d338315c2d8ecb982e83ce8f2ca7d2108c1c7c04faff2001b094d3 languageName: node linkType: hard -"@typescript-eslint/parser@npm:^5.33.0, @typescript-eslint/parser@npm:^5.47.0": - version: 5.47.1 - resolution: "@typescript-eslint/parser@npm:5.47.1" +"@typescript-eslint/parser@npm:^6.9.0": + version: 6.9.0 + resolution: "@typescript-eslint/parser@npm:6.9.0" dependencies: - "@typescript-eslint/scope-manager": 5.47.1 - "@typescript-eslint/types": 5.47.1 - "@typescript-eslint/typescript-estree": 5.47.1 + "@typescript-eslint/scope-manager": 6.9.0 + "@typescript-eslint/types": 6.9.0 + "@typescript-eslint/typescript-estree": 6.9.0 + "@typescript-eslint/visitor-keys": 6.9.0 debug: ^4.3.4 peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + eslint: ^7.0.0 || ^8.0.0 peerDependenciesMeta: typescript: optional: true - checksum: 36806686a2c5cc60558c09b13e885861aa21ec6250539d8d3d3c8abb90b321662e57dacec44915c87726a5a0d74187b58a65880a0613024eaeeb7ad0197a345d + checksum: d8ff69d236d6495474ab93c67e2785cc94bf9c098f25c33f1c5958a4b2b5af2b70edf1cdd0c23ee3436df454a769e80eb47d2d34df8382a826fcdb79bac4382d languageName: node linkType: hard @@ -8823,20 +8954,30 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:5.47.1": - version: 5.47.1 - resolution: "@typescript-eslint/type-utils@npm:5.47.1" +"@typescript-eslint/scope-manager@npm:6.9.0": + version: 6.9.0 + resolution: "@typescript-eslint/scope-manager@npm:6.9.0" dependencies: - "@typescript-eslint/typescript-estree": 5.47.1 - "@typescript-eslint/utils": 5.47.1 + "@typescript-eslint/types": 6.9.0 + "@typescript-eslint/visitor-keys": 6.9.0 + checksum: b7ddcea11bdb95107659800bdf3b33eae22a4dc5c28dc0f8dc5507aa9affaae0e332b6d8c7d5286a7ec75e7c4abd211eb9fdf9647a9a796689cdcc11f6ab40c6 + languageName: node + linkType: hard + +"@typescript-eslint/type-utils@npm:6.9.0": + version: 6.9.0 + resolution: "@typescript-eslint/type-utils@npm:6.9.0" + dependencies: + "@typescript-eslint/typescript-estree": 6.9.0 + "@typescript-eslint/utils": 6.9.0 debug: ^4.3.4 - tsutils: ^3.21.0 + ts-api-utils: ^1.0.1 peerDependencies: - eslint: "*" + eslint: ^7.0.0 || ^8.0.0 peerDependenciesMeta: typescript: optional: true - checksum: 84a1e6c8fd47d419dc66430e31b818774d4c0329a5f355a5a9e8af94378be4c0c24a89916d5cc1380fdbb640693527b906c2e6adee486a2e6786cb5e08bd9eb3 + checksum: 279b0000cd2fe7ccfbd2f311736b14c8bb9287081f553c9452c95966650c822e67442ba5a62d7fea3ee2f61ccc0fcb218f21e1ee7ccf3984c52e5942d2bbb065 languageName: node linkType: hard @@ -8847,6 +8988,13 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/types@npm:6.9.0": + version: 6.9.0 + resolution: "@typescript-eslint/types@npm:6.9.0" + checksum: e0444afa1f2ebca746c72b3d0bf95982eb1e8b4fb91bcba465c1345c35fa13b36c589bfd91c776b864f223bc50817b2613df5892185c2e34332bf4cc57cc865d + languageName: node + linkType: hard + "@typescript-eslint/typescript-estree@npm:5.47.1": version: 5.47.1 resolution: "@typescript-eslint/typescript-estree@npm:5.47.1" @@ -8865,7 +9013,42 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/utils@npm:5.47.1, @typescript-eslint/utils@npm:^5.10.0, @typescript-eslint/utils@npm:^5.13.0": +"@typescript-eslint/typescript-estree@npm:6.9.0": + version: 6.9.0 + resolution: "@typescript-eslint/typescript-estree@npm:6.9.0" + dependencies: + "@typescript-eslint/types": 6.9.0 + "@typescript-eslint/visitor-keys": 6.9.0 + debug: ^4.3.4 + globby: ^11.1.0 + is-glob: ^4.0.3 + semver: ^7.5.4 + ts-api-utils: ^1.0.1 + peerDependenciesMeta: + typescript: + optional: true + checksum: 51088c23cca608a6e5c195b0a2d8a17ad00ca47199ba4df0c1013912a80194bff9f5bd4d035d6ab2596788491e9a3e04bbf6cad6494a3b1bbd59fea442750268 + languageName: node + linkType: hard + +"@typescript-eslint/utils@npm:6.9.0": + version: 6.9.0 + resolution: "@typescript-eslint/utils@npm:6.9.0" + dependencies: + "@eslint-community/eslint-utils": ^4.4.0 + "@types/json-schema": ^7.0.12 + "@types/semver": ^7.5.0 + "@typescript-eslint/scope-manager": 6.9.0 + "@typescript-eslint/types": 6.9.0 + "@typescript-eslint/typescript-estree": 6.9.0 + semver: ^7.5.4 + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + checksum: 973c24d7858f224934958ee58c21ff21dfe54dbb1d0e0c5f889298fadcd7ee2dbfd49cf86ccafab74d428c31de66cd9beee7c39d2b64f9edcc9e941573bac175 + languageName: node + linkType: hard + +"@typescript-eslint/utils@npm:^5.10.0, @typescript-eslint/utils@npm:^5.13.0": version: 5.47.1 resolution: "@typescript-eslint/utils@npm:5.47.1" dependencies: @@ -8893,6 +9076,23 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/visitor-keys@npm:6.9.0": + version: 6.9.0 + resolution: "@typescript-eslint/visitor-keys@npm:6.9.0" + dependencies: + "@typescript-eslint/types": 6.9.0 + eslint-visitor-keys: ^3.4.1 + checksum: de8e2e363df41e5ae9774a5ebd1c49d29c771ea8b3869917f65a74cd4d14a67417c79916f456ee81ef5b0d947b7b8975385fc6eea3f1812d53a2eaaea832459e + languageName: node + linkType: hard + +"@ungap/structured-clone@npm:^1.2.0": + version: 1.2.0 + resolution: "@ungap/structured-clone@npm:1.2.0" + checksum: 4f656b7b4672f2ce6e272f2427d8b0824ed11546a601d8d5412b9d7704e83db38a8d9f402ecdf2b9063fc164af842ad0ec4a55819f621ed7e7ea4d1efcc74524 + languageName: node + linkType: hard + "@unique-nft/opal-testnet-types@npm:942.57.0": version: 942.57.0 resolution: "@unique-nft/opal-testnet-types@npm:942.57.0" @@ -9862,6 +10062,15 @@ __metadata: languageName: node linkType: hard +"acorn@npm:^8.9.0": + version: 8.10.0 + resolution: "acorn@npm:8.10.0" + bin: + acorn: bin/acorn + checksum: 538ba38af0cc9e5ef983aee196c4b8b4d87c0c94532334fa7e065b2c8a1f85863467bb774231aae91613fcda5e68740c15d97b1967ae3394d20faddddd8af61d + languageName: node + linkType: hard + "adm-zip@npm:^0.4.16": version: 0.4.16 resolution: "adm-zip@npm:0.4.16" @@ -13940,6 +14149,16 @@ __metadata: languageName: node linkType: hard +"eslint-scope@npm:^7.2.2": + version: 7.2.2 + resolution: "eslint-scope@npm:7.2.2" + dependencies: + esrecurse: ^4.3.0 + estraverse: ^5.2.0 + checksum: ec97dbf5fb04b94e8f4c5a91a7f0a6dd3c55e46bfc7bbcd0e3138c3a76977570e02ed89a1810c778dcd72072ff0e9621ba1379b4babe53921d71e2e4486fda3e + languageName: node + linkType: hard + "eslint-utils@npm:^2.1.0": version: 2.1.0 resolution: "eslint-utils@npm:2.1.0" @@ -13981,19 +14200,26 @@ __metadata: languageName: node linkType: hard -"eslint-webpack-plugin@npm:^3.1.1": - version: 3.2.0 - resolution: "eslint-webpack-plugin@npm:3.2.0" +"eslint-visitor-keys@npm:^3.4.1, eslint-visitor-keys@npm:^3.4.3": + version: 3.4.3 + resolution: "eslint-visitor-keys@npm:3.4.3" + checksum: 36e9ef87fca698b6fd7ca5ca35d7b2b6eeaaf106572e2f7fd31c12d3bfdaccdb587bba6d3621067e5aece31c8c3a348b93922ab8f7b2cbc6aaab5e1d89040c60 + languageName: node + linkType: hard + +"eslint-webpack-plugin@npm:^4.0.1": + version: 4.0.1 + resolution: "eslint-webpack-plugin@npm:4.0.1" dependencies: - "@types/eslint": ^7.29.0 || ^8.4.1 - jest-worker: ^28.0.2 + "@types/eslint": ^8.37.0 + jest-worker: ^29.5.0 micromatch: ^4.0.5 normalize-path: ^3.0.0 schema-utils: ^4.0.0 peerDependencies: - eslint: ^7.0.0 || ^8.0.0 + eslint: ^8.0.0 webpack: ^5.0.0 - checksum: 095034c35e773fdb21ec7e597ae1f8a6899679c290db29d8568ca94619e8c7f4971f0f9edccc8a965322ab8af9286c87205985a38f4fdcf17654aee7cd8bb7b5 + checksum: 94fe0817fc580729dc6edc4a31c9f7dbeb97ad134c6c4192feab66aae711ed5afe35c5bc8d5e99b39626b469300196df72e631f5e53c55723adbf850765924e5 languageName: node linkType: hard @@ -14096,6 +14322,54 @@ __metadata: languageName: node linkType: hard +"eslint@npm:^8.52.0": + version: 8.52.0 + resolution: "eslint@npm:8.52.0" + dependencies: + "@eslint-community/eslint-utils": ^4.2.0 + "@eslint-community/regexpp": ^4.6.1 + "@eslint/eslintrc": ^2.1.2 + "@eslint/js": 8.52.0 + "@humanwhocodes/config-array": ^0.11.13 + "@humanwhocodes/module-importer": ^1.0.1 + "@nodelib/fs.walk": ^1.2.8 + "@ungap/structured-clone": ^1.2.0 + ajv: ^6.12.4 + chalk: ^4.0.0 + cross-spawn: ^7.0.2 + debug: ^4.3.2 + doctrine: ^3.0.0 + escape-string-regexp: ^4.0.0 + eslint-scope: ^7.2.2 + eslint-visitor-keys: ^3.4.3 + espree: ^9.6.1 + esquery: ^1.4.2 + esutils: ^2.0.2 + fast-deep-equal: ^3.1.3 + file-entry-cache: ^6.0.1 + find-up: ^5.0.0 + glob-parent: ^6.0.2 + globals: ^13.19.0 + graphemer: ^1.4.0 + ignore: ^5.2.0 + imurmurhash: ^0.1.4 + is-glob: ^4.0.0 + is-path-inside: ^3.0.3 + js-yaml: ^4.1.0 + json-stable-stringify-without-jsonify: ^1.0.1 + levn: ^0.4.1 + lodash.merge: ^4.6.2 + minimatch: ^3.1.2 + natural-compare: ^1.4.0 + optionator: ^0.9.3 + strip-ansi: ^6.0.1 + text-table: ^0.2.0 + bin: + eslint: bin/eslint.js + checksum: fd22d1e9bd7090e31b00cbc7a3b98f3b76020a4c4641f987ae7d0c8f52e1b88c3b268bdfdabac2e1a93513e5d11339b718ff45cbff48a44c35d7e52feba510ed + languageName: node + linkType: hard + "espree@npm:^7.3.0, espree@npm:^7.3.1": version: 7.3.1 resolution: "espree@npm:7.3.1" @@ -14118,6 +14392,17 @@ __metadata: languageName: node linkType: hard +"espree@npm:^9.6.0, espree@npm:^9.6.1": + version: 9.6.1 + resolution: "espree@npm:9.6.1" + dependencies: + acorn: ^8.9.0 + acorn-jsx: ^5.3.2 + eslint-visitor-keys: ^3.4.1 + checksum: eb8c149c7a2a77b3f33a5af80c10875c3abd65450f60b8af6db1bfcfa8f101e21c1e56a561c6dc13b848e18148d43469e7cd208506238554fb5395a9ea5a1ab9 + languageName: node + linkType: hard + "esprima@npm:^4.0.0, esprima@npm:^4.0.1": version: 4.0.1 resolution: "esprima@npm:4.0.1" @@ -14137,6 +14422,15 @@ __metadata: languageName: node linkType: hard +"esquery@npm:^1.4.2": + version: 1.5.0 + resolution: "esquery@npm:1.5.0" + dependencies: + estraverse: ^5.1.0 + checksum: aefb0d2596c230118656cd4ec7532d447333a410a48834d80ea648b1e7b5c9bc9ed8b5e33a89cb04e487b60d622f44cf5713bf4abed7c97343edefdc84a35900 + languageName: node + linkType: hard + "esrecurse@npm:^4.3.0": version: 4.3.0 resolution: "esrecurse@npm:4.3.0" @@ -14602,8 +14896,8 @@ __metadata: "@types/semver": ^7.3.9 "@types/uuid": ^8.3.4 "@types/webextension-polyfill": 0.9.1 - "@typescript-eslint/eslint-plugin": ^5.47.0 - "@typescript-eslint/parser": ^5.47.0 + "@typescript-eslint/eslint-plugin": ^6.9.0 + "@typescript-eslint/parser": ^6.9.0 anylogger: ^1.0.11 anylogger-loglevel: ^1.0.0 archiver: 5.3.1 @@ -14631,8 +14925,8 @@ __metadata: dotenv: ^16.0.3 dotenv-webpack: ^7.1.1 downshift: ^6.1.12 - eslint: ^8.15.0 - eslint-webpack-plugin: ^3.1.1 + eslint: ^8.52.0 + eslint-webpack-plugin: ^4.0.1 eth-phishing-detect: latest ethers: 5.7.2 fake-indexeddb: 4.0.1 @@ -14691,7 +14985,7 @@ __metadata: ts-jest: 27.1.3 ts-loader: 9.4.4 ts-results: 3.3.0 - typescript: 4.9.4 + typescript: ^5.2.2 url: ^0.11.0 url-join: ^5.0.0 uuid: ^8.3.2 @@ -15659,6 +15953,15 @@ __metadata: languageName: node linkType: hard +"globals@npm:^13.19.0": + version: 13.23.0 + resolution: "globals@npm:13.23.0" + dependencies: + type-fest: ^0.20.2 + checksum: 194c97cf8d1ef6ba59417234c2386549c4103b6e5f24b1ff1952de61a4753e5d2069435ba629de711a6480b1b1d114a98e2ab27f85e966d5a10c319c3bbd3dc3 + languageName: node + linkType: hard + "globalthis@npm:^1.0.3": version: 1.0.3 resolution: "globalthis@npm:1.0.3" @@ -15746,6 +16049,13 @@ __metadata: languageName: node linkType: hard +"graphemer@npm:^1.4.0": + version: 1.4.0 + resolution: "graphemer@npm:1.4.0" + checksum: bab8f0be9b568857c7bec9fda95a89f87b783546d02951c40c33f84d05bb7da3fd10f863a9beb901463669b6583173a8c8cc6d6b306ea2b9b9d5d3d943c3a673 + languageName: node + linkType: hard + "graphql-config@npm:^4.5.0": version: 4.5.0 resolution: "graphql-config@npm:4.5.0" @@ -16470,6 +16780,13 @@ __metadata: languageName: node linkType: hard +"ignore@npm:^5.2.4": + version: 5.2.4 + resolution: "ignore@npm:5.2.4" + checksum: 3d4c309c6006e2621659311783eaea7ebcd41fe4ca1d78c91c473157ad6666a57a2df790fe0d07a12300d9aac2888204d7be8d59f9aaf665b1c7fcdb432517ef + languageName: node + linkType: hard + "immediate@npm:~3.0.5": version: 3.0.6 resolution: "immediate@npm:3.0.6" @@ -17078,7 +17395,7 @@ __metadata: languageName: node linkType: hard -"is-path-inside@npm:^3.0.2": +"is-path-inside@npm:^3.0.2, is-path-inside@npm:^3.0.3": version: 3.0.3 resolution: "is-path-inside@npm:3.0.3" checksum: abd50f06186a052b349c15e55b182326f1936c89a78bf6c8f2b707412517c097ce04bc49a0ca221787bc44e1049f51f09a2ffb63d22899051988d3a618ba13e9 @@ -18240,6 +18557,20 @@ __metadata: languageName: node linkType: hard +"jest-util@npm:^29.7.0": + version: 29.7.0 + resolution: "jest-util@npm:29.7.0" + dependencies: + "@jest/types": ^29.6.3 + "@types/node": "*" + chalk: ^4.0.0 + ci-info: ^3.2.0 + graceful-fs: ^4.2.9 + picomatch: ^2.2.3 + checksum: 042ab4980f4ccd4d50226e01e5c7376a8556b472442ca6091a8f102488c0f22e6e8b89ea874111d2328a2080083bf3225c86f3788c52af0bd0345a00eb57a3ca + languageName: node + linkType: hard + "jest-validate@npm:^27.5.1": version: 27.5.1 resolution: "jest-validate@npm:27.5.1" @@ -18328,7 +18659,7 @@ __metadata: languageName: node linkType: hard -"jest-worker@npm:^28.0.2, jest-worker@npm:^28.1.3": +"jest-worker@npm:^28.1.3": version: 28.1.3 resolution: "jest-worker@npm:28.1.3" dependencies: @@ -18339,6 +18670,18 @@ __metadata: languageName: node linkType: hard +"jest-worker@npm:^29.5.0": + version: 29.7.0 + resolution: "jest-worker@npm:29.7.0" + dependencies: + "@types/node": "*" + jest-util: ^29.7.0 + merge-stream: ^2.0.0 + supports-color: ^8.0.0 + checksum: 30fff60af49675273644d408b650fc2eb4b5dcafc5a0a455f238322a8f9d8a98d847baca9d51ff197b6747f54c7901daa2287799230b856a0f48287d131f8c13 + languageName: node + linkType: hard + "jest@npm:27.5.1": version: 27.5.1 resolution: "jest@npm:27.5.1" @@ -19652,7 +19995,7 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^3.0.2, minimatch@npm:^3.0.4, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": +"minimatch@npm:^3.0.2, minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" dependencies: @@ -20036,13 +20379,6 @@ __metadata: languageName: node linkType: hard -"natural-compare-lite@npm:^1.4.0": - version: 1.4.0 - resolution: "natural-compare-lite@npm:1.4.0" - checksum: 5222ac3986a2b78dd6069ac62cbb52a7bf8ffc90d972ab76dfe7b01892485d229530ed20d0c62e79a6b363a663b273db3bde195a1358ce9e5f779d4453887225 - languageName: node - linkType: hard - "natural-compare@npm:^1.4.0": version: 1.4.0 resolution: "natural-compare@npm:1.4.0" @@ -20610,6 +20946,20 @@ __metadata: languageName: node linkType: hard +"optionator@npm:^0.9.3": + version: 0.9.3 + resolution: "optionator@npm:0.9.3" + dependencies: + "@aashutoshrathi/word-wrap": ^1.2.3 + deep-is: ^0.1.3 + fast-levenshtein: ^2.0.6 + levn: ^0.4.1 + prelude-ls: ^1.2.1 + type-check: ^0.4.0 + checksum: 09281999441f2fe9c33a5eeab76700795365a061563d66b098923eb719251a42bdbe432790d35064d0816ead9296dbeb1ad51a733edf4167c96bd5d0882e428a + languageName: node + linkType: hard + "ora@npm:^5.4.1": version: 5.4.1 resolution: "ora@npm:5.4.1" @@ -24203,7 +24553,7 @@ __metadata: "@types/react-dom": 18.0.5 autoprefixer: ^10.4.12 color: 4.2.3 - eslint: ^8.15.0 + eslint: ^8.52.0 framer-motion: 10.12.18 lottie-react: 2.4.0 postcss: ^8.4.20 @@ -24241,8 +24591,8 @@ __metadata: "@commitlint/config-conventional": ^17.0.0 "@talismn/eslint-config": "workspace:*" "@testing-library/react": ^14.0.0 - "@typescript-eslint/eslint-plugin": ^5.33.0 - "@typescript-eslint/parser": ^5.33.0 + "@typescript-eslint/eslint-plugin": ^6.9.0 + "@typescript-eslint/parser": ^6.9.0 concurrently: ^7.6.0 husky: ^8.0.3 import-sort-style-module: ^6.0.0 @@ -24685,6 +25035,15 @@ __metadata: languageName: node linkType: hard +"ts-api-utils@npm:^1.0.1": + version: 1.0.3 + resolution: "ts-api-utils@npm:1.0.3" + peerDependencies: + typescript: ">=4.2.0" + checksum: 441cc4489d65fd515ae6b0f4eb8690057add6f3b6a63a36073753547fb6ce0c9ea0e0530220a0b282b0eec535f52c4dfc315d35f8a4c9a91c0def0707a714ca6 + languageName: node + linkType: hard + "ts-easing@npm:^0.2.0": version: 0.2.0 resolution: "ts-easing@npm:0.2.0" From ba79078fbcb6eb4581c7b12d6b486d267b6452a1 Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Wed, 25 Oct 2023 11:36:35 +0900 Subject: [PATCH 03/50] chore: changeset --- .changeset/neat-kiwis-look.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .changeset/neat-kiwis-look.md diff --git a/.changeset/neat-kiwis-look.md b/.changeset/neat-kiwis-look.md new file mode 100644 index 0000000000..56c276ed9f --- /dev/null +++ b/.changeset/neat-kiwis-look.md @@ -0,0 +1,28 @@ +--- +"@talismn/balances-substrate-equilibrium": patch +"@talismn/chaindata-provider-extension": patch +"@talismn/balances-substrate-assets": patch +"@talismn/balances-substrate-native": patch +"@talismn/balances-substrate-tokens": patch +"@talismn/balances-default-modules": patch +"@talismn/balances-substrate-psp22": patch +"@talismn/balances-substrate-orml": patch +"@talismn/balances-evm-native": patch +"@talismn/chain-connector-evm": patch +"@talismn/balances-evm-erc20": patch +"@talismn/chaindata-provider": patch +"@talismn/chain-connector": patch +"@talismn/connection-meta": patch +"@talismn/mutate-metadata": patch +"@talismn/eslint-config": patch +"@talismn/on-chain-id": patch +"talisman-ui": patch +"@talismn/token-rates": patch +"@talismn/balances": patch +"@talismn/icons": patch +"@talismn/scale": patch +"@talismn/util": patch +"@talismn/orb": patch +--- + +bump typescript version From 65da3930dfcfa4f16bd2c13904e4706e3817e99a Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Wed, 25 Oct 2023 15:32:57 +0900 Subject: [PATCH 04/50] feat: viem public client for balances libraries --- .changeset/moody-moons-teach.md | 8 + .../src/core/rpcs/chain-connector-evm.ts | 2 +- packages/balances-evm-erc20/package.json | 4 +- .../balances-evm-erc20/src/EvmErc20Module.ts | 64 +++++--- packages/balances-evm-erc20/src/erc20.json | 155 ------------------ packages/balances-evm-erc20/src/erc20Abi.ts | 155 ++++++++++++++++++ packages/balances-evm-native/package.json | 4 +- .../src/EvmNativeModule.ts | 30 ++-- packages/chain-connector-evm/package.json | 3 +- .../src/ChainConnectorEvm.ts | 24 ++- .../src/getChainFromEvmNetwork.ts | 49 ++++++ .../src/getEvmNetworkClient.ts | 42 +++++ packages/util/src/isEthereumAddress.ts | 2 +- yarn.lock | 101 +++++++++++- 14 files changed, 441 insertions(+), 202 deletions(-) create mode 100644 .changeset/moody-moons-teach.md delete mode 100644 packages/balances-evm-erc20/src/erc20.json create mode 100644 packages/balances-evm-erc20/src/erc20Abi.ts create mode 100644 packages/chain-connector-evm/src/getChainFromEvmNetwork.ts create mode 100644 packages/chain-connector-evm/src/getEvmNetworkClient.ts diff --git a/.changeset/moody-moons-teach.md b/.changeset/moody-moons-teach.md new file mode 100644 index 0000000000..2e4e77e27f --- /dev/null +++ b/.changeset/moody-moons-teach.md @@ -0,0 +1,8 @@ +--- +"@talismn/balances-evm-native": patch +"@talismn/chain-connector-evm": patch +"@talismn/balances-evm-erc20": patch +"@talismn/util": patch +--- + +replace ethers by viem diff --git a/apps/extension/src/core/rpcs/chain-connector-evm.ts b/apps/extension/src/core/rpcs/chain-connector-evm.ts index 9ffbdb1d13..09894c5aa6 100644 --- a/apps/extension/src/core/rpcs/chain-connector-evm.ts +++ b/apps/extension/src/core/rpcs/chain-connector-evm.ts @@ -1,4 +1,4 @@ import { chaindataProvider } from "@core/rpcs/chaindata" import { ChainConnectorEvm } from "@talismn/chain-connector-evm" -export const chainConnectorEvm = new ChainConnectorEvm(chaindataProvider) +export const chainConnectorEvm = new ChainConnectorEvm(chaindataProvider, chaindataProvider) diff --git a/packages/balances-evm-erc20/package.json b/packages/balances-evm-erc20/package.json index 3f5c44aa7f..8fb55b804b 100644 --- a/packages/balances-evm-erc20/package.json +++ b/packages/balances-evm-erc20/package.json @@ -30,8 +30,8 @@ "@talismn/chaindata-provider": "workspace:*", "@talismn/util": "workspace:*", "anylogger": "^1.0.11", - "ethers": "5.7.2", - "lodash": "4.17.21" + "lodash": "4.17.21", + "viem": "^1.16.6" }, "devDependencies": { "@polkadot/util": "^11.1.1", diff --git a/packages/balances-evm-erc20/src/EvmErc20Module.ts b/packages/balances-evm-erc20/src/EvmErc20Module.ts index 9bd56fbe59..e7db568465 100644 --- a/packages/balances-evm-erc20/src/EvmErc20Module.ts +++ b/packages/balances-evm-erc20/src/EvmErc20Module.ts @@ -1,6 +1,5 @@ import { assert } from "@polkadot/util" import { - Address, AddressesByToken, Amount, Balance, @@ -18,10 +17,10 @@ import { githubTokenLogoUrl, } from "@talismn/chaindata-provider" import { hasOwnProperty, isEthereumAddress } from "@talismn/util" -import { ethers } from "ethers" import isEqual from "lodash/isEqual" +import { PublicClient } from "viem" -import erc20Abi from "./erc20.json" +import { erc20Abi } from "./erc20Abi" import log from "./log" export { erc20Abi } @@ -116,16 +115,19 @@ export const EvmErc20Module: NewBalanceModule< if (!contractAddress) continue const [contractSymbol, contractDecimals] = await (async () => { - const evmNetwork = await chaindataProvider.getEvmNetwork(chainId) - if (!evmNetwork) return [] + const publicClient = await chainConnector.getPublicClientForEvmNetwork(chainId) + if (!publicClient) return [] - const provider = await chainConnector.getProviderForEvmNetwork(evmNetwork) - if (!provider) return [] - - const contract = new ethers.Contract(contractAddress, erc20Abi, provider) + const contract = { + abi: erc20Abi, + address: contractAddress as `0x${string}`, + } try { - return [await contract.symbol(), await contract.decimals()] + return Promise.all([ + publicClient.readContract({ ...contract, functionName: "symbol" }), + publicClient.readContract({ ...contract, functionName: "decimals" }), + ]) } catch (error) { log.error(`Failed to retrieve contract symbol and decimals`, String(error)) return [] @@ -245,10 +247,8 @@ export const EvmErc20Module: NewBalanceModule< const evmNetwork = evmNetworks[evmNetworkId] if (!evmNetwork) throw new Error(`Evm network ${evmNetworkId} not found`) - const provider = await chainConnectors.evm.getProviderForEvmNetwork(evmNetwork, { - batch: true, - }) - if (!provider) + const publicClient = await chainConnector.getPublicClientForEvmNetwork(evmNetworkId) + if (!publicClient) throw new Error(`Could not get rpc provider for evm network ${evmNetworkId}`) const tokensAndAddresses = Object.entries(addressesByToken).reduce( @@ -277,8 +277,6 @@ export const EvmErc20Module: NewBalanceModule< // fetch all balances const balanceRequests = tokensAndAddresses.flatMap(([token, addresses]) => { - const contract = new ethers.Contract(token.contractAddress, erc20Abi, provider) - return addresses.map( async (address) => new Balance({ @@ -291,7 +289,11 @@ export const EvmErc20Module: NewBalanceModule< evmNetworkId, tokenId: token.id, - free: await getFreeBalance(contract, address), + free: await getFreeBalance( + publicClient, + token.contractAddress as `0x${string}`, + address as `0x${string}` + ), }) ) }) @@ -354,18 +356,34 @@ function groupAddressesByTokenByEvmNetwork( }, {} as Record>) } -async function getFreeBalance(contract: ethers.Contract, address: Address): Promise { - if (!isEthereumAddress(address)) return "0" +async function getFreeBalance( + publicClient: PublicClient, + contractAddress: `0x${string}`, + accountAddress: `0x${string}` +): Promise { + if (!isEthereumAddress(accountAddress)) return "0" try { - return ((await contract.balanceOf(address)).toBigInt() ?? 0n).toString() + const res = await publicClient.readContract({ + abi: erc20Abi, + address: contractAddress, + functionName: "balanceOf", + args: [accountAddress], + }) + + return res.toString() + //((await contract.balanceOf(accountAddress)).toBigInt() ?? 0n).toString() } catch (error) { - const errorMessage = hasOwnProperty(error, "message") ? error.message : error + const errorMessage = hasOwnProperty(error, "shortMessage") + ? error.shortMessage + : hasOwnProperty(error, "message") + ? error.message + : error log.warn( - `Failed to get balance from contract ${contract.address} for address ${address}: ${errorMessage}` + `Failed to get balance from contract ${contractAddress} (chain ${publicClient.chain?.id}) for address ${accountAddress}: ${errorMessage}` ) throw new Error( - `Failed to get balance from contract ${contract.address} for address ${address}`, + `Failed to get balance from contract ${contractAddress} (chain ${publicClient.chain?.id}) for address ${accountAddress}`, { cause: error as Error } ) } diff --git a/packages/balances-evm-erc20/src/erc20.json b/packages/balances-evm-erc20/src/erc20.json deleted file mode 100644 index be7f18d343..0000000000 --- a/packages/balances-evm-erc20/src/erc20.json +++ /dev/null @@ -1,155 +0,0 @@ -[ - { - "constant": true, - "inputs": [], - "name": "name", - "outputs": [{ "name": "", "type": "string" }], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { "name": "_spender", "type": "address" }, - { "name": "_value", "type": "uint256" } - ], - "name": "approve", - "outputs": [{ "name": "", "type": "bool" }], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "totalSupply", - "outputs": [{ "name": "", "type": "uint256" }], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { "name": "_from", "type": "address" }, - { "name": "_to", "type": "address" }, - { "name": "_value", "type": "uint256" } - ], - "name": "transferFrom", - "outputs": [{ "name": "", "type": "bool" }], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "decimals", - "outputs": [{ "name": "", "type": "uint8" }], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { "name": "_to", "type": "address" }, - { "name": "_value", "type": "uint256" }, - { "name": "_data", "type": "bytes" } - ], - "name": "transferAndCall", - "outputs": [{ "name": "success", "type": "bool" }], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { "name": "_spender", "type": "address" }, - { "name": "_subtractedValue", "type": "uint256" } - ], - "name": "decreaseApproval", - "outputs": [{ "name": "success", "type": "bool" }], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [{ "name": "_owner", "type": "address" }], - "name": "balanceOf", - "outputs": [{ "name": "balance", "type": "uint256" }], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "symbol", - "outputs": [{ "name": "", "type": "string" }], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { "name": "_to", "type": "address" }, - { "name": "_value", "type": "uint256" } - ], - "name": "transfer", - "outputs": [{ "name": "success", "type": "bool" }], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { "name": "_spender", "type": "address" }, - { "name": "_addedValue", "type": "uint256" } - ], - "name": "increaseApproval", - "outputs": [{ "name": "success", "type": "bool" }], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { "name": "_owner", "type": "address" }, - { "name": "_spender", "type": "address" } - ], - "name": "allowance", - "outputs": [{ "name": "remaining", "type": "uint256" }], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { "inputs": [], "payable": false, "stateMutability": "nonpayable", "type": "constructor" }, - { - "anonymous": false, - "inputs": [ - { "indexed": true, "name": "from", "type": "address" }, - { "indexed": true, "name": "to", "type": "address" }, - { "indexed": false, "name": "value", "type": "uint256" }, - { "indexed": false, "name": "data", "type": "bytes" } - ], - "name": "Transfer", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { "indexed": true, "name": "owner", "type": "address" }, - { "indexed": true, "name": "spender", "type": "address" }, - { "indexed": false, "name": "value", "type": "uint256" } - ], - "name": "Approval", - "type": "event" - } -] diff --git a/packages/balances-evm-erc20/src/erc20Abi.ts b/packages/balances-evm-erc20/src/erc20Abi.ts new file mode 100644 index 0000000000..d31a33f711 --- /dev/null +++ b/packages/balances-evm-erc20/src/erc20Abi.ts @@ -0,0 +1,155 @@ +export const erc20Abi = [ + { + constant: true, + inputs: [], + name: "name", + outputs: [{ name: "", type: "string" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: false, + inputs: [ + { name: "_spender", type: "address" }, + { name: "_value", type: "uint256" }, + ], + name: "approve", + outputs: [{ name: "", type: "bool" }], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: true, + inputs: [], + name: "totalSupply", + outputs: [{ name: "", type: "uint256" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: false, + inputs: [ + { name: "_from", type: "address" }, + { name: "_to", type: "address" }, + { name: "_value", type: "uint256" }, + ], + name: "transferFrom", + outputs: [{ name: "", type: "bool" }], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: true, + inputs: [], + name: "decimals", + outputs: [{ name: "", type: "uint8" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: false, + inputs: [ + { name: "_to", type: "address" }, + { name: "_value", type: "uint256" }, + { name: "_data", type: "bytes" }, + ], + name: "transferAndCall", + outputs: [{ name: "success", type: "bool" }], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: false, + inputs: [ + { name: "_spender", type: "address" }, + { name: "_subtractedValue", type: "uint256" }, + ], + name: "decreaseApproval", + outputs: [{ name: "success", type: "bool" }], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: true, + inputs: [{ name: "_owner", type: "address" }], + name: "balanceOf", + outputs: [{ name: "balance", type: "uint256" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: true, + inputs: [], + name: "symbol", + outputs: [{ name: "", type: "string" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { + constant: false, + inputs: [ + { name: "_to", type: "address" }, + { name: "_value", type: "uint256" }, + ], + name: "transfer", + outputs: [{ name: "success", type: "bool" }], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: false, + inputs: [ + { name: "_spender", type: "address" }, + { name: "_addedValue", type: "uint256" }, + ], + name: "increaseApproval", + outputs: [{ name: "success", type: "bool" }], + payable: false, + stateMutability: "nonpayable", + type: "function", + }, + { + constant: true, + inputs: [ + { name: "_owner", type: "address" }, + { name: "_spender", type: "address" }, + ], + name: "allowance", + outputs: [{ name: "remaining", type: "uint256" }], + payable: false, + stateMutability: "view", + type: "function", + }, + { inputs: [], payable: false, stateMutability: "nonpayable", type: "constructor" }, + { + anonymous: false, + inputs: [ + { indexed: true, name: "from", type: "address" }, + { indexed: true, name: "to", type: "address" }, + { indexed: false, name: "value", type: "uint256" }, + { indexed: false, name: "data", type: "bytes" }, + ], + name: "Transfer", + type: "event", + }, + { + anonymous: false, + inputs: [ + { indexed: true, name: "owner", type: "address" }, + { indexed: true, name: "spender", type: "address" }, + { indexed: false, name: "value", type: "uint256" }, + ], + name: "Approval", + type: "event", + }, +] as const diff --git a/packages/balances-evm-native/package.json b/packages/balances-evm-native/package.json index 91f8ce6e5c..ecc32bcfcc 100644 --- a/packages/balances-evm-native/package.json +++ b/packages/balances-evm-native/package.json @@ -30,8 +30,8 @@ "@talismn/chaindata-provider": "workspace:*", "@talismn/util": "workspace:*", "anylogger": "^1.0.11", - "ethers": "5.7.2", - "lodash": "4.17.21" + "lodash": "4.17.21", + "viem": "^1.16.6" }, "devDependencies": { "@talismn/eslint-config": "workspace:*", diff --git a/packages/balances-evm-native/src/EvmNativeModule.ts b/packages/balances-evm-native/src/EvmNativeModule.ts index 9447745f44..ab63a70259 100644 --- a/packages/balances-evm-native/src/EvmNativeModule.ts +++ b/packages/balances-evm-native/src/EvmNativeModule.ts @@ -15,8 +15,8 @@ import { githubTokenLogoUrl, } from "@talismn/chaindata-provider" import { hasOwnProperty, isEthereumAddress } from "@talismn/util" -import { ethers } from "ethers" import isEqual from "lodash/isEqual" +import { PublicClient } from "viem" import log from "./log" @@ -193,10 +193,11 @@ export const EvmNativeModule: NewBalanceModule< const evmNetwork = evmNetworks[evmNetworkId] if (!evmNetwork) throw new Error(`Evm network ${evmNetworkId} not found`) - const provider = await chainConnectors.evm.getProviderForEvmNetwork(evmNetwork, { - batch: true, - }) - if (!provider) + const publicClient = await chainConnectors.evm.getPublicClientForEvmNetwork( + evmNetworkId + ) + + if (!publicClient) throw new Error(`Could not get rpc provider for evm network ${evmNetworkId}`) // fetch all balances @@ -212,7 +213,7 @@ export const EvmNativeModule: NewBalanceModule< evmNetworkId, tokenId, - free: await getFreeBalance(provider, address), + free: await getFreeBalance(publicClient, address), }) ) @@ -249,21 +250,22 @@ export const EvmNativeModule: NewBalanceModule< } } -async function getFreeBalance( - provider: ethers.providers.JsonRpcProvider, - address: Address -): Promise { +async function getFreeBalance(publicClient: PublicClient, address: Address): Promise { if (!isEthereumAddress(address)) return "0" try { - return ((await provider.getBalance(address)).toBigInt() ?? 0n).toString() + return (await publicClient.getBalance({ address })).toString() } catch (error) { - const errorMessage = hasOwnProperty(error, "message") ? error.message : error + const errorMessage = hasOwnProperty(error, "shortMessage") + ? error.shortMessage + : hasOwnProperty(error, "message") + ? error.message + : error log.warn( - `Failed to get balance from chain ${provider.network.chainId} for address ${address}: ${errorMessage}` + `Failed to get balance from chain ${publicClient.chain?.id} for address ${address}: ${errorMessage}` ) throw new Error( - `Failed to get balance from chain ${provider.network.chainId} for address ${address}`, + `Failed to get balance from chain ${publicClient.chain?.id} for address ${address}`, { cause: error as Error } ) } diff --git a/packages/chain-connector-evm/package.json b/packages/chain-connector-evm/package.json index 1c348f34bc..531484bd96 100644 --- a/packages/chain-connector-evm/package.json +++ b/packages/chain-connector-evm/package.json @@ -30,7 +30,8 @@ "@talismn/util": "workspace:*", "anylogger": "^1.0.11", "ethers": "5.7.2", - "lodash": "4.17.21" + "lodash": "4.17.21", + "viem": "^1.16.6" }, "devDependencies": { "@talismn/eslint-config": "workspace:*", diff --git a/packages/chain-connector-evm/src/ChainConnectorEvm.ts b/packages/chain-connector-evm/src/ChainConnectorEvm.ts index 566a9e209d..7ffda89ddd 100644 --- a/packages/chain-connector-evm/src/ChainConnectorEvm.ts +++ b/packages/chain-connector-evm/src/ChainConnectorEvm.ts @@ -1,8 +1,15 @@ -import { CustomEvmNetwork, EvmNetwork, EvmNetworkId } from "@talismn/chaindata-provider" +import { + ChaindataTokenProvider, + CustomEvmNetwork, + EvmNetwork, + EvmNetworkId, +} from "@talismn/chaindata-provider" import { ChaindataEvmNetworkProvider } from "@talismn/chaindata-provider" import { ethers } from "ethers" +import { PublicClient } from "viem" import { RPC_CALL_TIMEOUT } from "./constants" +import { clearPublicClientCache, getEvmNetworkPublicClient } from "./getEvmNetworkClient" import log from "./log" import { BatchRpcProvider, StandardRpcProvider, addOnfinalityApiKey, getHealthyRpc } from "./util" @@ -22,6 +29,7 @@ export type ChainConnectorEvmOptions = { export class ChainConnectorEvm { #chaindataEvmNetworkProvider: ChaindataEvmNetworkProvider + #chaindataTokenProvider: ChaindataTokenProvider #onfinalityApiKey?: string // cache for providers @@ -46,9 +54,11 @@ export class ChainConnectorEvm { constructor( chaindataEvmNetworkProvider: ChaindataEvmNetworkProvider, + chaindataTokenProvider: ChaindataTokenProvider, options?: ChainConnectorEvmOptions ) { this.#chaindataEvmNetworkProvider = chaindataEvmNetworkProvider + this.#chaindataTokenProvider = chaindataTokenProvider this.#onfinalityApiKey = options?.onfinalityApiKey ?? undefined } @@ -94,6 +104,17 @@ export class ChainConnectorEvm { return (await this.#providerCache.get(cacheKey)) ?? null } + public async getPublicClientForEvmNetwork( + evmNetworkId: EvmNetworkId + ): Promise { + const network = await this.#chaindataEvmNetworkProvider.getEvmNetwork(evmNetworkId) + if (!network?.nativeToken?.id) return null + const nativeToken = await this.#chaindataTokenProvider.getToken(network.nativeToken.id) + if (!nativeToken) return null + + return getEvmNetworkPublicClient(network, nativeToken) + } + clearRpcProvidersCache(evmNetworkId?: EvmNetworkId, clearRpcUrlsCache = true) { if (evmNetworkId) { this.#providerCache.delete(getEvmNetworkProviderCacheKey(evmNetworkId, false)) @@ -103,6 +124,7 @@ export class ChainConnectorEvm { this.#providerCache.clear() if (clearRpcUrlsCache) this.#rpcUrlsCache.clear() } + clearPublicClientCache(evmNetworkId) } private rotateRpcUrls(evmNetworkId: EvmNetworkId) { diff --git a/packages/chain-connector-evm/src/getChainFromEvmNetwork.ts b/packages/chain-connector-evm/src/getChainFromEvmNetwork.ts new file mode 100644 index 0000000000..70c0fc72a4 --- /dev/null +++ b/packages/chain-connector-evm/src/getChainFromEvmNetwork.ts @@ -0,0 +1,49 @@ +import { EvmNetwork, Token } from "@talismn/chaindata-provider" +import { Chain } from "viem" +import * as chains from "viem/chains" + +// viem chains benefit from multicall config & other viem goodies +const VIEM_CHAINS = Object.keys(chains).reduce((acc, curr) => { + const chain = chains[curr as keyof typeof chains] + acc[chain.id] = chain + return acc +}, {} as Record) + +const chainsCache = new Map() + +export const clearChainsCache = (evmNetworkId?: string) => { + if (evmNetworkId) chainsCache.delete(evmNetworkId) + else chainsCache.clear() +} + +export const getChainFromEvmNetwork = (evmNetwork: EvmNetwork, nativeToken: Token): Chain => { + if (!evmNetwork?.nativeToken?.id) throw new Error("Undefined native token") + if (evmNetwork.nativeToken.id !== nativeToken.id) throw new Error("Native token mismatch") + + const { symbol, decimals } = nativeToken + + if (!chainsCache.has(evmNetwork.id)) { + const chainRpcs = evmNetwork.rpcs?.map((rpc) => rpc.url) ?? [] + + const viemChain = VIEM_CHAINS[Number(evmNetwork.id)] ?? {} + + const chain: Chain = { + ...viemChain, + id: Number(evmNetwork.id), + name: evmNetwork.name ?? `EVM Chain ${evmNetwork.id}`, + rpcUrls: { + public: { http: chainRpcs }, + default: { http: chainRpcs }, + }, + nativeCurrency: { + symbol, + decimals, + name: symbol, + }, + } + + chainsCache.set(evmNetwork.id, chain) + } + + return chainsCache.get(evmNetwork.id) as Chain +} diff --git a/packages/chain-connector-evm/src/getEvmNetworkClient.ts b/packages/chain-connector-evm/src/getEvmNetworkClient.ts new file mode 100644 index 0000000000..5d26f79f70 --- /dev/null +++ b/packages/chain-connector-evm/src/getEvmNetworkClient.ts @@ -0,0 +1,42 @@ +import { EvmNetwork, Token } from "@talismn/chaindata-provider" +import { PublicClient, createPublicClient, fallback, http } from "viem" + +import { clearChainsCache, getChainFromEvmNetwork } from "./getChainFromEvmNetwork" + +const BATCH_WAIT = 25 +const BATCH_SIZE = 30 + +// create clients as needed, to prevent unnecessary health checks +const publicClientCache = new Map() + +export const clearPublicClientCache = (evmNetworkId?: string) => { + clearChainsCache(evmNetworkId) + + if (evmNetworkId) publicClientCache.delete(evmNetworkId) + else publicClientCache.clear() +} + +export const getEvmNetworkPublicClient = ( + evmNetwork: EvmNetwork, + nativeToken: Token +): PublicClient => { + const chain = getChainFromEvmNetwork(evmNetwork, nativeToken) + + if (!publicClientCache.has(evmNetwork.id)) { + if (!evmNetwork.rpcs?.length) throw new Error("No RPCs found for EVM network") + + const batch = chain.contracts?.multicall3 ? { multicall: { wait: BATCH_WAIT } } : undefined + const transport = chain.contracts?.multicall3 + ? http(undefined, { + batch: { + batchSize: BATCH_SIZE, + wait: BATCH_WAIT, + }, + }) + : fallback(evmNetwork.rpcs.map((rpc) => http(rpc.url, { batch: { wait: BATCH_WAIT } }))) + + publicClientCache.set(evmNetwork.id, createPublicClient({ chain, transport, batch })) + } + + return publicClientCache.get(evmNetwork.id) as PublicClient +} diff --git a/packages/util/src/isEthereumAddress.ts b/packages/util/src/isEthereumAddress.ts index 291da24be9..a7fd77293d 100644 --- a/packages/util/src/isEthereumAddress.ts +++ b/packages/util/src/isEthereumAddress.ts @@ -1,2 +1,2 @@ -export const isEthereumAddress = (address: string) => +export const isEthereumAddress = (address: string): address is `0x${string}` => address.startsWith("0x") && address.length === 42 diff --git a/yarn.lock b/yarn.lock index d9b231b3ff..9f374de770 100644 --- a/yarn.lock +++ b/yarn.lock @@ -28,6 +28,13 @@ __metadata: languageName: node linkType: hard +"@adraffy/ens-normalize@npm:1.9.4": + version: 1.9.4 + resolution: "@adraffy/ens-normalize@npm:1.9.4" + checksum: 7d7fff58ebe2c4961f7e5e61dad123aa6a63fec0df5c84af1fa41079dc05d398599690be4427b3a94d2baa94084544bcfdf2d51cbed7504b9b0583b0960ad550 + languageName: node + linkType: hard + "@alectalisman/preconstruct-cli@npm:2.3.0-e": version: 2.3.0-e resolution: "@alectalisman/preconstruct-cli@npm:2.3.0-e" @@ -5036,6 +5043,15 @@ __metadata: languageName: node linkType: hard +"@noble/curves@npm:1.2.0, @noble/curves@npm:~1.2.0": + version: 1.2.0 + resolution: "@noble/curves@npm:1.2.0" + dependencies: + "@noble/hashes": 1.3.2 + checksum: bb798d7a66d8e43789e93bc3c2ddff91a1e19fdb79a99b86cd98f1e5eff0ee2024a2672902c2576ef3577b6f282f3b5c778bebd55761ddbb30e36bf275e83dd0 + languageName: node + linkType: hard + "@noble/ed25519@npm:^1.7.0": version: 1.7.2 resolution: "@noble/ed25519@npm:1.7.2" @@ -5064,6 +5080,13 @@ __metadata: languageName: node linkType: hard +"@noble/hashes@npm:1.3.2, @noble/hashes@npm:~1.3.2": + version: 1.3.2 + resolution: "@noble/hashes@npm:1.3.2" + checksum: fe23536b436539d13f90e4b9be843cc63b1b17666a07634a2b1259dded6f490be3d050249e6af98076ea8f2ea0d56f578773c2197f2aa0eeaa5fba5bc18ba474 + languageName: node + linkType: hard + "@noble/secp256k1@npm:1.7.1, @noble/secp256k1@npm:^1.6.3, @noble/secp256k1@npm:~1.7.0": version: 1.7.1 resolution: "@noble/secp256k1@npm:1.7.1" @@ -6412,6 +6435,13 @@ __metadata: languageName: node linkType: hard +"@scure/base@npm:~1.1.2": + version: 1.1.3 + resolution: "@scure/base@npm:1.1.3" + checksum: 1606ab8a4db898cb3a1ada16c15437c3bce4e25854fadc8eb03ae93cbbbac1ed90655af4b0be3da37e12056fef11c0374499f69b9e658c9e5b7b3e06353c630c + languageName: node + linkType: hard + "@scure/bip32@npm:1.1.5": version: 1.1.5 resolution: "@scure/bip32@npm:1.1.5" @@ -6434,6 +6464,17 @@ __metadata: languageName: node linkType: hard +"@scure/bip32@npm:1.3.2": + version: 1.3.2 + resolution: "@scure/bip32@npm:1.3.2" + dependencies: + "@noble/curves": ~1.2.0 + "@noble/hashes": ~1.3.2 + "@scure/base": ~1.1.2 + checksum: c5ae84fae43490853693b481531132b89e056d45c945fc8b92b9d032577f753dfd79c5a7bbcbf0a7f035951006ff0311b6cf7a389e26c9ec6335e42b20c53157 + languageName: node + linkType: hard + "@scure/bip39@npm:1.1.1": version: 1.1.1 resolution: "@scure/bip39@npm:1.1.1" @@ -6454,6 +6495,16 @@ __metadata: languageName: node linkType: hard +"@scure/bip39@npm:1.2.1": + version: 1.2.1 + resolution: "@scure/bip39@npm:1.2.1" + dependencies: + "@noble/hashes": ~1.3.0 + "@scure/base": ~1.1.0 + checksum: c5bd6f1328fdbeae2dcdd891825b1610225310e5e62a4942714db51066866e4f7bef242c7b06a1b9dcc8043a4a13412cf5c5df76d3b10aa9e36b82e9b6e3eeaa + languageName: node + linkType: hard + "@sentry-internal/tracing@npm:7.44.1": version: 7.44.1 resolution: "@sentry-internal/tracing@npm:7.44.1" @@ -7463,11 +7514,11 @@ __metadata: "@types/lodash": ^4.14.180 anylogger: ^1.0.11 eslint: ^8.52.0 - ethers: 5.7.2 jest: ^28.1.0 lodash: 4.17.21 ts-jest: ^28.0.2 typescript: ^5.2.2 + viem: ^1.16.6 peerDependencies: "@polkadot/util": 11.x languageName: unknown @@ -7486,11 +7537,11 @@ __metadata: "@types/lodash": ^4.14.180 anylogger: ^1.0.11 eslint: ^8.52.0 - ethers: 5.7.2 jest: ^28.1.0 lodash: 4.17.21 ts-jest: ^28.0.2 typescript: ^5.2.2 + viem: ^1.16.6 languageName: unknown linkType: soft @@ -7733,6 +7784,7 @@ __metadata: lodash: 4.17.21 ts-jest: ^28.0.2 typescript: ^5.2.2 + viem: ^1.16.6 languageName: unknown linkType: soft @@ -9947,6 +9999,21 @@ __metadata: languageName: node linkType: hard +"abitype@npm:0.9.8": + version: 0.9.8 + resolution: "abitype@npm:0.9.8" + peerDependencies: + typescript: ">=5.0.4" + zod: ^3 >=3.19.1 + peerDependenciesMeta: + typescript: + optional: true + zod: + optional: true + checksum: d7d887f29d6821e3f7a400de9620511b80ead3f85c5c87308aaec97965d3493e6687ed816e88722b4f512249bd66dee9e69231b49af0e1db8f69400a62c87cf6 + languageName: node + linkType: hard + "abitype@npm:^0.3.0": version: 0.3.0 resolution: "abitype@npm:0.3.0" @@ -17656,6 +17723,15 @@ __metadata: languageName: node linkType: hard +"isows@npm:1.0.3": + version: 1.0.3 + resolution: "isows@npm:1.0.3" + peerDependencies: + ws: "*" + checksum: 9cacd5cf59f67deb51e825580cd445ab1725ecb05a67c704050383fb772856f3cd5e7da8ad08f5a3bd2823680d77d099459d0c6a7037972a74d6429af61af440 + languageName: node + linkType: hard + "isstream@npm:~0.1.2": version: 0.1.2 resolution: "isstream@npm:0.1.2" @@ -26110,6 +26186,27 @@ __metadata: languageName: node linkType: hard +"viem@npm:^1.16.6": + version: 1.16.6 + resolution: "viem@npm:1.16.6" + dependencies: + "@adraffy/ens-normalize": 1.9.4 + "@noble/curves": 1.2.0 + "@noble/hashes": 1.3.2 + "@scure/bip32": 1.3.2 + "@scure/bip39": 1.2.1 + abitype: 0.9.8 + isows: 1.0.3 + ws: 8.13.0 + peerDependencies: + typescript: ">=5.0.4" + peerDependenciesMeta: + typescript: + optional: true + checksum: 2f116cad184cfc7a9584073451549edfb23c3847b1784f092b80a279b848fe011a054bc4141c923b5bcce1d8493db98284db65416ce72e8ba522225d02786a9a + languageName: node + linkType: hard + "vinyl-fs@npm:^3.0.2": version: 3.0.3 resolution: "vinyl-fs@npm:3.0.3" From c831456800d22d38dde8de08ad651ec86d122327 Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Thu, 26 Oct 2023 13:24:31 +0900 Subject: [PATCH 05/50] feat: signAndSend using viem --- apps/extension/package.json | 1 + .../src/core/domains/accounts/handler.ts | 4 +- .../src/core/domains/encrypt/handler.ts | 4 +- .../domains/ethereum/handler.extension.ts | 25 ++++++++----- .../core/domains/ethereum/viemMigration.ts | 37 +++++++++++++++++++ .../src/core/domains/transfers/handler.ts | 2 +- apps/extension/src/core/util/getPrivateKey.ts | 29 ++++++++++++++- .../src/ChainConnectorEvm.ts | 17 ++++++++- ...Client.ts => getEvmNetworkPublicClient.ts} | 0 .../src/getEvmNetworkWalletClient.ts | 14 +++++++ yarn.lock | 1 + 11 files changed, 115 insertions(+), 19 deletions(-) create mode 100644 apps/extension/src/core/domains/ethereum/viemMigration.ts rename packages/chain-connector-evm/src/{getEvmNetworkClient.ts => getEvmNetworkPublicClient.ts} (100%) create mode 100644 packages/chain-connector-evm/src/getEvmNetworkWalletClient.ts diff --git a/apps/extension/package.json b/apps/extension/package.json index 406f2269f9..d6a75ca27d 100644 --- a/apps/extension/package.json +++ b/apps/extension/package.json @@ -138,6 +138,7 @@ "typescript": "^5.2.2", "url-join": "^5.0.0", "uuid": "^8.3.2", + "viem": "^1.16.6", "webextension-polyfill": "0.8.0", "webpack": "^5.88.1", "webpack-cli": "^4.10.0", diff --git a/apps/extension/src/core/domains/accounts/handler.ts b/apps/extension/src/core/domains/accounts/handler.ts index 92f73c137b..ec55326b2b 100644 --- a/apps/extension/src/core/domains/accounts/handler.ts +++ b/apps/extension/src/core/domains/accounts/handler.ts @@ -437,11 +437,11 @@ export default class AccountsHandler extends ExtensionHandler { const { err, val } = await getPairForAddressSafely(address, async (pair) => { assert(pair.type === "ethereum", "Private key cannot be exported for this account type") - const pk = getPrivateKey(pair, pw as string) + const pk = getPrivateKey(pair, pw as string, "hex") talismanAnalytics.capture("account export", { type: pair.type, mode: "pk" }) - return pk.toString("hex") + return pk }) if (err) throw new Error(val as string) diff --git a/apps/extension/src/core/domains/encrypt/handler.ts b/apps/extension/src/core/domains/encrypt/handler.ts index eba69f8a12..b78c6ba0e5 100644 --- a/apps/extension/src/core/domains/encrypt/handler.ts +++ b/apps/extension/src/core/domains/encrypt/handler.ts @@ -27,7 +27,7 @@ export default class EncryptHandler extends ExtensionHandler { const pw = this.stores.password.getPassword() assert(pw, "Unable to retreive password from store.") - const pk = getPrivateKey(pair, pw) + const pk = getPrivateKey(pair, pw, "u8a") const kp = { publicKey: pair.publicKey, secretKey: u8aToU8a(pk) } as Keypair assert(kp.secretKey.length === 64, "Talisman secretKey is incorrect length") @@ -66,7 +66,7 @@ export default class EncryptHandler extends ExtensionHandler { const pw = this.stores.password.getPassword() assert(pw, "Unable to retreive password from store.") - const pk = getPrivateKey(pair, pw) + const pk = getPrivateKey(pair, pw, "u8a") assert(pk.length === 64, "Talisman secretKey is incorrect length") diff --git a/apps/extension/src/core/domains/ethereum/handler.extension.ts b/apps/extension/src/core/domains/ethereum/handler.extension.ts index 2fe9f10df7..9f4ca28b1a 100644 --- a/apps/extension/src/core/domains/ethereum/handler.extension.ts +++ b/apps/extension/src/core/domains/ethereum/handler.extension.ts @@ -23,12 +23,14 @@ import { HexString } from "@polkadot/util/types" import { evmNativeTokenId } from "@talismn/balances-evm-native" import { CustomEvmNetwork, githubUnknownTokenLogoUrl } from "@talismn/chaindata-provider" import { ethers } from "ethers" +import { privateKeyToAccount } from "viem/accounts" import { getHostName } from "../app/helpers" import { getHumanReadableErrorMessage } from "./errors" import { rebuildTransactionRequestNumbers } from "./helpers" import { getProviderForEvmNetworkId } from "./rpcProviders" import { getTransactionCount, incrementTransactionCount } from "./transactionCountManager" +import { getViemSendTransactionParams } from "./viemMigration" export class EthHandler extends ExtensionHandler { private signAndSendApproveHardware: MessageHandler<"pri(eth.signing.approveSignAndSendHardware)"> = @@ -85,22 +87,25 @@ export class EthHandler extends ExtensionHandler { assert(queued, "Unable to find request") const { resolve, reject, ethChainId, account, url } = queued - const provider = await getProviderForEvmNetworkId(ethChainId) - assert(provider, "Unable to find provider for chain " + ethChainId) - // rebuild BigNumber property values (converted to json when serialized) const tx = rebuildTransactionRequestNumbers(transaction) - tx.nonce = await getTransactionCount(account.address, ethChainId) + if (tx.nonce === undefined) tx.nonce = await getTransactionCount(account.address, ethChainId) const result = await getPairForAddressSafely(account.address, async (pair) => { + const client = await chainConnectorEvm.getWalletClientForEvmNetwork(ethChainId) + assert(client, "Missing client for chain " + ethChainId) + const password = this.stores.password.getPassword() assert(password, "Unauthorised") - const privateKey = getPrivateKey(pair, password) - const signer = new ethers.Wallet(privateKey, provider) - const { hash } = await signer.sendTransaction(tx) + const privateKey = getPrivateKey(pair, password, "hex") + const account = privateKeyToAccount(privateKey) - return hash + return await client.sendTransaction({ + chain: client.chain, + account, + ...getViemSendTransactionParams(tx), + }) }) if (result.ok) { @@ -186,7 +191,7 @@ export class EthHandler extends ExtensionHandler { const result = await getPairForAddressSafely(unsigned.from, async (pair) => { const password = this.stores.password.getPassword() assert(password, "Unauthorised") - const privateKey = getPrivateKey(pair, password) + const privateKey = getPrivateKey(pair, password, "u8a") const signer = new ethers.Wallet(privateKey, provider) const { hash } = await signer.sendTransaction(tx) @@ -259,7 +264,7 @@ export class EthHandler extends ExtensionHandler { const { val, ok } = await getPairForAddressSafely(queued.account.address, async (pair) => { const pw = this.stores.password.getPassword() assert(pw, "Unauthorised") - const privateKey = getPrivateKey(pair, pw) + const privateKey = getPrivateKey(pair, pw, "buffer") let signature: string if (method === "personal_sign") { diff --git a/apps/extension/src/core/domains/ethereum/viemMigration.ts b/apps/extension/src/core/domains/ethereum/viemMigration.ts new file mode 100644 index 0000000000..44499658d6 --- /dev/null +++ b/apps/extension/src/core/domains/ethereum/viemMigration.ts @@ -0,0 +1,37 @@ +// TODO delete this file after ethers => viem migration + +import { log } from "@core/log" +import { BigNumber, ethers } from "ethers" +import { accessListify } from "ethers/lib/utils" +import { AccessList } from "viem" + +export const getViemGasSettings = (tx: ethers.providers.TransactionRequest) => { + return tx.type === 2 + ? ({ + type: "eip1559", + gas: tx.gasLimit ? BigNumber.from(tx.gasLimit).toBigInt() : undefined, + maxFeePerGas: tx.maxFeePerGas ? BigNumber.from(tx.maxFeePerGas).toBigInt() : undefined, + maxPriorityFeePerGas: tx.maxPriorityFeePerGas + ? BigNumber.from(tx.maxPriorityFeePerGas).toBigInt() + : undefined, + accessList: tx.accessList ? (accessListify(tx.accessList) as AccessList) : undefined, + } as const) + : ({ + type: "legacy" as const, + gas: tx.gasLimit ? BigNumber.from(tx.gasLimit).toBigInt() : undefined, + gasPrice: tx.gasPrice ? BigNumber.from(tx.gasPrice).toBigInt() : undefined, + } as const) +} + +export const getViemSendTransactionParams = (ethersTx: ethers.providers.TransactionRequest) => { + const viemTx = { + to: ethersTx.to ? (ethersTx.to as `0x${string}`) : undefined, + data: ethersTx.data ? (ethersTx.data as `0x${string}`) : undefined, + value: ethersTx.value !== undefined ? BigNumber.from(ethersTx.value).toBigInt() : undefined, + nonce: ethersTx.nonce !== undefined ? BigNumber.from(ethersTx.nonce).toNumber() : undefined, + ...getViemGasSettings(ethersTx), + } + + log.log("getViemSendTransactionParams", { ethersTx, viemTx }) + return viemTx +} diff --git a/apps/extension/src/core/domains/transfers/handler.ts b/apps/extension/src/core/domains/transfers/handler.ts index 107cf32eaa..cb2b2bd2e9 100644 --- a/apps/extension/src/core/domains/transfers/handler.ts +++ b/apps/extension/src/core/domains/transfers/handler.ts @@ -218,7 +218,7 @@ export default class AssetTransferHandler extends ExtensionHandler { const password = this.stores.password.getPassword() assert(password, "Unauthorised") - const privateKey = getPrivateKey(pair, password) + const privateKey = getPrivateKey(pair, password, "u8a") const wallet = new Wallet(privateKey, provider) const { hash } = await wallet.sendTransaction(transaction) diff --git a/apps/extension/src/core/util/getPrivateKey.ts b/apps/extension/src/core/util/getPrivateKey.ts index ee66390549..b2ff71cbc9 100644 --- a/apps/extension/src/core/util/getPrivateKey.ts +++ b/apps/extension/src/core/util/getPrivateKey.ts @@ -12,7 +12,7 @@ const SEED_LENGTH = 32 const SEED_OFFSET = PKCS8_HEADER.length // built from reverse engineering polkadot keyring -export const getPrivateKey = (pair: KeyringPair, password: string) => { +const getU8aPrivateKey = (pair: KeyringPair, password: string) => { if (pair.isLocked) pair.unlock(password) const json = pair.toJson(password) @@ -34,5 +34,30 @@ export const getPrivateKey = (pair: KeyringPair, password: string) => { if (!u8aEq(divider, PKCS8_DIVIDER)) throw new Error("Invalid Pkcs8 divider found in body") } - return u8aToBuffer(privateKey) + return privateKey +} + +type PrivateKeyFormat = "u8a" | "hex" | "buffer" +type PrivateKeyOutput = T extends "u8a" + ? Uint8Array + : T extends "hex" + ? `0x${string}` + : Buffer + +export const getPrivateKey = ( + pair: KeyringPair, + password: string, + format: F +): PrivateKeyOutput => { + const privateKey = getU8aPrivateKey(pair, password) + + switch (format) { + case "hex": + return `0x${Buffer.from(privateKey).toString("hex")}` as PrivateKeyOutput + case "u8a": + return u8aToBuffer(privateKey) as PrivateKeyOutput + case "buffer": + default: + return privateKey as PrivateKeyOutput + } } diff --git a/packages/chain-connector-evm/src/ChainConnectorEvm.ts b/packages/chain-connector-evm/src/ChainConnectorEvm.ts index 7ffda89ddd..21a2be2dd1 100644 --- a/packages/chain-connector-evm/src/ChainConnectorEvm.ts +++ b/packages/chain-connector-evm/src/ChainConnectorEvm.ts @@ -6,10 +6,11 @@ import { } from "@talismn/chaindata-provider" import { ChaindataEvmNetworkProvider } from "@talismn/chaindata-provider" import { ethers } from "ethers" -import { PublicClient } from "viem" +import { Account, PublicClient, WalletClient } from "viem" import { RPC_CALL_TIMEOUT } from "./constants" -import { clearPublicClientCache, getEvmNetworkPublicClient } from "./getEvmNetworkClient" +import { clearPublicClientCache, getEvmNetworkPublicClient } from "./getEvmNetworkPublicClient" +import { getEvmNetworkWalletClient } from "./getEvmNetworkWalletClient" import log from "./log" import { BatchRpcProvider, StandardRpcProvider, addOnfinalityApiKey, getHealthyRpc } from "./util" @@ -115,6 +116,18 @@ export class ChainConnectorEvm { return getEvmNetworkPublicClient(network, nativeToken) } + public async getWalletClientForEvmNetwork( + evmNetworkId: EvmNetworkId, + account?: `0x${string}` | Account + ): Promise { + const network = await this.#chaindataEvmNetworkProvider.getEvmNetwork(evmNetworkId) + if (!network?.nativeToken?.id) return null + const nativeToken = await this.#chaindataTokenProvider.getToken(network.nativeToken.id) + if (!nativeToken) return null + + return getEvmNetworkWalletClient(network, nativeToken, account) + } + clearRpcProvidersCache(evmNetworkId?: EvmNetworkId, clearRpcUrlsCache = true) { if (evmNetworkId) { this.#providerCache.delete(getEvmNetworkProviderCacheKey(evmNetworkId, false)) diff --git a/packages/chain-connector-evm/src/getEvmNetworkClient.ts b/packages/chain-connector-evm/src/getEvmNetworkPublicClient.ts similarity index 100% rename from packages/chain-connector-evm/src/getEvmNetworkClient.ts rename to packages/chain-connector-evm/src/getEvmNetworkPublicClient.ts diff --git a/packages/chain-connector-evm/src/getEvmNetworkWalletClient.ts b/packages/chain-connector-evm/src/getEvmNetworkWalletClient.ts new file mode 100644 index 0000000000..4cf90ace7c --- /dev/null +++ b/packages/chain-connector-evm/src/getEvmNetworkWalletClient.ts @@ -0,0 +1,14 @@ +import { EvmNetwork, Token } from "@talismn/chaindata-provider" +import { Account, WalletClient, createWalletClient, http } from "viem" + +import { getChainFromEvmNetwork } from "./getChainFromEvmNetwork" + +export const getEvmNetworkWalletClient = ( + evmNetwork: EvmNetwork, + nativeToken: Token, + account?: `0x${string}` | Account +): WalletClient => { + const chain = getChainFromEvmNetwork(evmNetwork, nativeToken) + + return createWalletClient({ chain, transport: http(), account }) +} diff --git a/yarn.lock b/yarn.lock index 9f374de770..28c1ee512c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15056,6 +15056,7 @@ __metadata: url: ^0.11.0 url-join: ^5.0.0 uuid: ^8.3.2 + viem: ^1.16.6 webextension-polyfill: 0.8.0 webpack: ^5.88.1 webpack-bundle-analyzer: ^4.9.0 From 2c39b9a51a0b5bdf7215b0426ca21a1f25089744 Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Thu, 26 Oct 2023 15:23:45 +0900 Subject: [PATCH 06/50] chore: handler.tabs to use viem --- .../src/core/domains/ethereum/handler.tabs.ts | 107 ++++++++---------- apps/extension/src/core/injectEth/types.ts | 3 +- 2 files changed, 52 insertions(+), 58 deletions(-) diff --git a/apps/extension/src/core/domains/ethereum/handler.tabs.ts b/apps/extension/src/core/domains/ethereum/handler.tabs.ts index 30e154cb20..b54ffa5ab4 100644 --- a/apps/extension/src/core/domains/ethereum/handler.tabs.ts +++ b/apps/extension/src/core/domains/ethereum/handler.tabs.ts @@ -29,12 +29,12 @@ import { } from "@core/injectEth/types" import { TabsHandler } from "@core/libs/Handler" import { log } from "@core/log" +import { chainConnectorEvm } from "@core/rpcs/chain-connector-evm" import { chaindataProvider } from "@core/rpcs/chaindata" import type { RequestSignatures, RequestTypes, ResponseType } from "@core/types" import { Port } from "@core/types/base" import { getErc20TokenInfo } from "@core/util/getErc20TokenInfo" import { urlToDomain } from "@core/util/urlToDomain" -import { recoverPersonalSignature } from "@metamask/eth-sig-util" import keyring from "@polkadot/ui-keyring" import { accounts as accountsObservable } from "@polkadot/ui-keyring/observable/accounts" import { assert } from "@polkadot/util" @@ -42,7 +42,8 @@ import { isEthereumAddress } from "@polkadot/util-crypto" import { convertAddress } from "@talisman/util/convertAddress" import { githubUnknownTokenLogoUrl } from "@talismn/chaindata-provider" import { throwAfter } from "@talismn/util" -import { ethers, providers } from "ethers" +import { PublicClient, createClient, getAddress, http, recoverMessageAddress, toHex } from "viem" +import { hexToNumber } from "viem/utils" import { ERROR_DUPLICATE_AUTH_REQUEST_MESSAGE, @@ -90,14 +91,14 @@ export class EthTabsHandler extends TabsHandler { return site as EthAuthorizedSite } - async getProvider(url: string, authorisedAddress?: string): Promise { + async getPublicClient(url: string, authorisedAddress?: string): Promise { const site = await this.getSiteDetails(url, authorisedAddress) const ethereumNetwork = await chaindataProvider.getEvmNetwork(site.ethChainId.toString()) if (!ethereumNetwork) throw new EthProviderRpcError("Network not supported", ETH_ERROR_EIP1993_CHAIN_DISCONNECTED) - const provider = await getProviderForEthereumNetwork(ethereumNetwork, { batch: true }) + const provider = await chainConnectorEvm.getPublicClientForEvmNetwork(ethereumNetwork.id) if (!provider) throw new EthProviderRpcError( `No provider for network ${ethereumNetwork.id} (${ethereumNetwork.name})`, @@ -156,7 +157,7 @@ export class EthTabsHandler extends TabsHandler { ) .filter(({ type }) => type === "ethereum") // send as - .map(({ address }) => ethers.utils.getAddress(address).toLowerCase()) + .map(({ address }) => getAddress(address).toLowerCase()) ) } @@ -190,10 +191,7 @@ export class EthTabsHandler extends TabsHandler { if (!site) return siteId = site.id if (site.ethChainId && site.ethAddresses?.length) { - chainId = - typeof site?.ethChainId !== "undefined" - ? ethers.utils.hexValue(site.ethChainId) - : undefined + chainId = typeof site?.ethChainId !== "undefined" ? toHex(site.ethChainId) : undefined accounts = site.ethAddresses ?? [] // check that the network is still registered before broadcasting @@ -228,10 +226,7 @@ export class EthTabsHandler extends TabsHandler { try { // new state for this dapp - chainId = - typeof site?.ethChainId !== "undefined" - ? ethers.utils.hexValue(site.ethChainId) - : undefined + chainId = typeof site?.ethChainId !== "undefined" ? toHex(site.ethChainId) : undefined //TODO check eth addresses still exist accounts = site?.ethAddresses ?? [] connected = !!accounts.length @@ -259,7 +254,7 @@ export class EthTabsHandler extends TabsHandler { if (connected && chainId && prevAccounts?.join() !== accounts.join()) { sendToClient({ type: "accountsChanged", - data: accounts.map((ac) => ethers.utils.getAddress(ac).toLowerCase()), + data: accounts.map((ac) => getAddress(ac).toLowerCase()), }) } } catch (err) { @@ -322,14 +317,14 @@ export class EthTabsHandler extends TabsHandler { await Promise.all( network.rpcUrls.map(async (rpcUrl) => { try { - const provider = new providers.JsonRpcProvider(rpcUrl) - const providerChainIdHex: string = await Promise.race([ - provider.send("eth_chainId", []), + const client = createClient({ transport: http(rpcUrl) }) + const rpcChainIdHex = await Promise.race([ + client.request({ method: "eth_chainId" }), throwAfter(10_000, "timeout"), // 10 sec timeout ]) - const providerChainId = parseInt(providerChainIdHex, 16) + const rpcChainId = hexToNumber(rpcChainIdHex as `0x${string}`) - assert(providerChainId === chainId, "chainId mismatch") + assert(rpcChainId === chainId, "chainId mismatch") } catch (err) { log.error({ err }) throw new EthProviderRpcError("Invalid rpc " + rpcUrl, ETH_ERROR_EIP1474_INVALID_PARAMS) @@ -400,23 +395,18 @@ export class EthTabsHandler extends TabsHandler { // obtain the chain id without checking auth. // note: this method is only called if method doesn't require auth, or if auth is already checked const chainId = await this.getChainId(url) + const publicClient = await chainConnectorEvm.getPublicClientForEvmNetwork(chainId.toString()) - const ethereumNetwork = await chaindataProvider.getEvmNetwork(chainId.toString()) - if (!ethereumNetwork) + if (!publicClient) throw new EthProviderRpcError( `Unknown network ${chainId}`, ETH_ERROR_UNKNOWN_CHAIN_NOT_CONFIGURED ) - const provider = await getProviderForEthereumNetwork(ethereumNetwork, { batch: true }) - if (!provider) - throw new EthProviderRpcError( - `Failed to connect to network ${chainId}`, - ETH_ERROR_EIP1993_CHAIN_DISCONNECTED - ) - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return provider.send(request.method, request.params as unknown as any[]) + return publicClient.request({ + method: request.method as never, + params: request.params as never, + }) } private signMessage = async (url: string, request: EthRequestSignArguments, port: Port) => { @@ -436,8 +426,8 @@ export class EthTabsHandler extends TabsHandler { isMessageFirst = false const [uncheckedMessage, from] = isMessageFirst - ? [params[0], ethers.utils.getAddress(params[1])] - : [params[1], ethers.utils.getAddress(params[0])] + ? [params[0], getAddress(params[1])] + : [params[1], getAddress(params[0])] // message is either a raw string or a hex string or an object (signTypedData_v1) const message = @@ -448,7 +438,7 @@ export class EthTabsHandler extends TabsHandler { const address = site.ethAddresses[0] const pair = keyring.getPair(address) - if (!address || !pair || ethers.utils.getAddress(address) !== ethers.utils.getAddress(from)) { + if (!address || !pair || getAddress(address) !== getAddress(from)) { throw new EthProviderRpcError( `No account available for ${url}`, ETH_ERROR_EIP1993_UNAUTHORIZED @@ -461,7 +451,7 @@ export class EthTabsHandler extends TabsHandler { message, site.ethChainId.toString(), { - address: ethers.utils.getAddress(address), + address: getAddress(address), ...pair.meta, }, port @@ -579,7 +569,7 @@ export class EthTabsHandler extends TabsHandler { throw new EthProviderRpcError("Wrong network", ETH_ERROR_EIP1474_INVALID_PARAMS) try { - await this.getProvider(url, txRequest.from) + await this.getPublicClient(url, txRequest.from) } catch (error) { throw new EthProviderRpcError("Network not supported", ETH_ERROR_EIP1993_CHAIN_DISCONNECTED) } @@ -699,9 +689,9 @@ export class EthTabsHandler extends TabsHandler { ![ "eth_requestAccounts", "eth_accounts", - "eth_chainId", - "eth_blockNumber", - "net_version", + "eth_chainId", // TODO check if necessary ? + "eth_blockNumber", // TODO check if necessary ? + "net_version", // TODO check if necessary ? "wallet_switchEthereumChain", "wallet_addEthereumChain", "wallet_watchAsset", @@ -733,29 +723,31 @@ export class EthTabsHandler extends TabsHandler { case "eth_chainId": // public method, no need to auth (returns undefined if not authorized yet) - return ethers.utils.hexValue(await this.getChainId(url)) + return toHex(await this.getChainId(url)) case "net_version": // public method, no need to auth (returns undefined if not authorized yet) // legacy, but still used by etherscan prior calling eth_watchAsset return (await this.getChainId(url)).toString() - case "estimateGas": { - const { params } = request as EthRequestArguments<"estimateGas"> - if (params[1] && params[1] !== "latest") { - throw new EthProviderRpcError( - "estimateGas does not support blockTag", - ETH_ERROR_EIP1474_INVALID_PARAMS - ) - } - - await this.checkAccountAuthorised(url, params[0].from) - - const req = ethers.providers.JsonRpcProvider.hexlifyTransaction(params[0]) - const provider = await this.getProvider(url) - const result = await provider.estimateGas(req) - return result.toHexString() - } + // TODO check if this is ever called, it doesn't exist in viem's public rpc schema + // case "estimateGas": { + // const { params } = request as EthRequestArguments<"estimateGas"> + // console.log("estimateGas", params) + // if (params[1] && params[1] !== "latest") { + // throw new EthProviderRpcError( + // "estimateGas does not support blockTag", + // ETH_ERROR_EIP1474_INVALID_PARAMS + // ) + // } + + // await this.checkAccountAuthorised(url, params[0].from) + + // const req = ethers.providers.JsonRpcProvider.hexlifyTransaction(params[0]) + // const provider = await this.getProvider(url) + // const result = await provider.estimateGas(req) + // return result.toHexString() + // } case "personal_sign": case "eth_signTypedData": @@ -767,9 +759,10 @@ export class EthTabsHandler extends TabsHandler { case "personal_ecRecover": { const { - params: [data, signature], + params: [message, signature], } = request as EthRequestArguments<"personal_ecRecover"> - return recoverPersonalSignature({ data, signature }) + return recoverMessageAddress({ message, signature }) + //return recoverPersonalSignature({ data, signature }) } case "eth_sendTransaction": diff --git a/apps/extension/src/core/injectEth/types.ts b/apps/extension/src/core/injectEth/types.ts index 17770c4d4c..b432d791b3 100644 --- a/apps/extension/src/core/injectEth/types.ts +++ b/apps/extension/src/core/injectEth/types.ts @@ -48,7 +48,7 @@ export type EthRequestGetBlock = PromisifyArray<[BlockTag, boolean]> export type EthRequestTxHashOnly = PromisifyArray<[string]> export type EthRequestSign = [string, string] -export type EthRequestRecoverAddress = [string, string] +export type EthRequestRecoverAddress = [string, `0x${string}`] export type EthRequestSendTx = [TransactionRequest] @@ -56,6 +56,7 @@ export type EthRequestAddEthereumChain = [AddEthereumChainParameter] export type EthRequestSwitchEthereumChain = [{ chainId: string }] +// TODO : nuke all this and use viem's EIP1193RequestFn ? export interface EthRequestSignatures { eth_requestAccounts: [null, InjectedAccount[]] eth_gasPrice: [null, string] From e149ad8cbbcc8edc9a6d9ceb9c803bb48664c28a Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Thu, 26 Oct 2023 15:54:02 +0900 Subject: [PATCH 07/50] fix: use viem client in handler.tabs --- .../src/core/domains/ethereum/handler.tabs.ts | 11 +- .../src/core/inject/injectSubstrate.ts | 4 +- apps/extension/src/core/util/abi/abiErc20.ts | 232 +++++++++++++++++- .../src/core/util/getErc20ContractData.ts | 22 +- .../src/core/util/getErc20TokenInfo.ts | 30 ++- .../src/ui/hooks/useErc20TokenInfo.ts | 4 +- 6 files changed, 278 insertions(+), 25 deletions(-) diff --git a/apps/extension/src/core/domains/ethereum/handler.tabs.ts b/apps/extension/src/core/domains/ethereum/handler.tabs.ts index b54ffa5ab4..fc0f194e82 100644 --- a/apps/extension/src/core/domains/ethereum/handler.tabs.ts +++ b/apps/extension/src/core/domains/ethereum/handler.tabs.ts @@ -33,7 +33,6 @@ import { chainConnectorEvm } from "@core/rpcs/chain-connector-evm" import { chaindataProvider } from "@core/rpcs/chaindata" import type { RequestSignatures, RequestTypes, ResponseType } from "@core/types" import { Port } from "@core/types/base" -import { getErc20TokenInfo } from "@core/util/getErc20TokenInfo" import { urlToDomain } from "@core/util/urlToDomain" import keyring from "@polkadot/ui-keyring" import { accounts as accountsObservable } from "@polkadot/ui-keyring/observable/accounts" @@ -45,6 +44,7 @@ import { throwAfter } from "@talismn/util" import { PublicClient, createClient, getAddress, http, recoverMessageAddress, toHex } from "viem" import { hexToNumber } from "viem/utils" +import { getErc20TokenInfo } from "../../util/getErc20TokenInfo" import { ERROR_DUPLICATE_AUTH_REQUEST_MESSAGE, requestAuthoriseSite, @@ -57,7 +57,6 @@ import { sanitizeWatchAssetRequestParam, } from "./helpers" import { requestAddNetwork, requestWatchAsset } from "./requests" -import { getProviderForEthereumNetwork, getProviderForEvmNetworkId } from "./rpcProviders" import { Web3WalletPermission, Web3WalletPermissionTarget } from "./types" interface EthAuthorizedSite extends AuthorizedSite { @@ -363,7 +362,7 @@ export class EthTabsHandler extends TabsHandler { ETH_ERROR_UNKNOWN_CHAIN_NOT_CONFIGURED ) - const provider = await getProviderForEthereumNetwork(ethereumNetwork, { batch: true }) + const provider = await chainConnectorEvm.getPublicClientForEvmNetwork(ethereumNetwork.id) if (!provider) throw new EthProviderRpcError( `Failed to connect to network ${ethChainId}`, @@ -481,8 +480,8 @@ export class EthTabsHandler extends TabsHandler { if (existing) throw new EthProviderRpcError("Asset already exists", ETH_ERROR_EIP1474_INVALID_PARAMS) - const provider = await getProviderForEvmNetworkId(ethChainId.toString()) - if (!provider) + const client = await chainConnectorEvm.getPublicClientForEvmNetwork(ethChainId.toString()) + if (!client) throw new EthProviderRpcError( "Network not supported", ETH_ERROR_EIP1993_CHAIN_DISCONNECTED @@ -490,7 +489,7 @@ export class EthTabsHandler extends TabsHandler { try { // eslint-disable-next-line no-var - var tokenInfo = await getErc20TokenInfo(provider, ethChainId.toString(), address) + var tokenInfo = await getErc20TokenInfo(client, ethChainId.toString(), address) } catch (err) { throw new EthProviderRpcError("Asset not found", ETH_ERROR_EIP1474_INVALID_PARAMS) } diff --git a/apps/extension/src/core/inject/injectSubstrate.ts b/apps/extension/src/core/inject/injectSubstrate.ts index 98f45a09f4..aaf82b2f6a 100644 --- a/apps/extension/src/core/inject/injectSubstrate.ts +++ b/apps/extension/src/core/inject/injectSubstrate.ts @@ -1,8 +1,8 @@ import { log } from "@core/log" import type { ResponseType, SendRequest } from "@core/types" import type { ProviderInterfaceCallback } from "@polkadot/rpc-provider/types" -import { HexString } from "@polkadot/util/types" -import { CustomChain, CustomEvmNetwork, Token } from "@talismn/chaindata-provider" +import type { HexString } from "@polkadot/util/types" +import type { CustomChain, CustomEvmNetwork, Token } from "@talismn/chaindata-provider" type TalismanWindow = typeof globalThis & { talismanSub?: ReturnType & ReturnType diff --git a/apps/extension/src/core/util/abi/abiErc20.ts b/apps/extension/src/core/util/abi/abiErc20.ts index e2cc8bd5d7..f912c6e38a 100644 --- a/apps/extension/src/core/util/abi/abiErc20.ts +++ b/apps/extension/src/core/util/abi/abiErc20.ts @@ -1,12 +1,222 @@ export const abiErc20 = [ - "function balanceOf(address owner) view returns (uint256)", - "function decimals() view returns (uint8)", - "function symbol() view returns (string)", - "function name() view returns (string)", - - "function totalSupply() view returns (uint256)", - "function transfer(address to, uint256 amount) returns (bool)", - "function allowance(address owner, address spender) view returns (uint256)", - "function approve(address spender, uint256 amount) returns (bool)", - "function transferFrom(address from, address to, uint256 amount) returns (bool)", -] + { + type: "event", + name: "Approval", + inputs: [ + { + indexed: true, + name: "owner", + type: "address", + }, + { + indexed: true, + name: "spender", + type: "address", + }, + { + indexed: false, + name: "value", + type: "uint256", + }, + ], + }, + { + type: "event", + name: "Transfer", + inputs: [ + { + indexed: true, + name: "from", + type: "address", + }, + { + indexed: true, + name: "to", + type: "address", + }, + { + indexed: false, + name: "value", + type: "uint256", + }, + ], + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "owner", + type: "address", + }, + { + indexed: true, + internalType: "address", + name: "operator", + type: "address", + }, + { + indexed: false, + internalType: "bool", + name: "approved", + type: "bool", + }, + ], + name: "ApprovalForAll", + type: "event", + }, + { + type: "function", + name: "allowance", + stateMutability: "view", + inputs: [ + { + name: "owner", + type: "address", + }, + { + name: "spender", + type: "address", + }, + ], + outputs: [ + { + name: "", + type: "uint256", + }, + ], + }, + { + type: "function", + name: "approve", + stateMutability: "nonpayable", + inputs: [ + { + name: "spender", + type: "address", + }, + { + name: "amount", + type: "uint256", + }, + ], + outputs: [ + { + name: "", + type: "bool", + }, + ], + }, + { + type: "function", + name: "balanceOf", + stateMutability: "view", + inputs: [ + { + name: "account", + type: "address", + }, + ], + outputs: [ + { + name: "", + type: "uint256", + }, + ], + }, + { + type: "function", + name: "decimals", + stateMutability: "view", + inputs: [], + outputs: [ + { + name: "", + type: "uint8", + }, + ], + }, + { + type: "function", + name: "name", + stateMutability: "view", + inputs: [], + outputs: [ + { + name: "", + type: "string", + }, + ], + }, + { + type: "function", + name: "symbol", + stateMutability: "view", + inputs: [], + outputs: [ + { + name: "", + type: "string", + }, + ], + }, + { + type: "function", + name: "totalSupply", + stateMutability: "view", + inputs: [], + outputs: [ + { + name: "", + type: "uint256", + }, + ], + }, + { + type: "function", + name: "transfer", + stateMutability: "nonpayable", + inputs: [ + { + name: "recipient", + type: "address", + }, + { + name: "amount", + type: "uint256", + }, + ], + outputs: [ + { + name: "", + type: "bool", + }, + ], + }, + { + type: "function", + name: "transferFrom", + stateMutability: "nonpayable", + inputs: [ + { + name: "sender", + type: "address", + }, + { + name: "recipient", + type: "address", + }, + { + name: "amount", + type: "uint256", + }, + ], + outputs: [ + { + name: "", + type: "bool", + }, + ], + }, +] as const diff --git a/apps/extension/src/core/util/getErc20ContractData.ts b/apps/extension/src/core/util/getErc20ContractData.ts index a3ea3ac729..76a0e7384e 100644 --- a/apps/extension/src/core/util/getErc20ContractData.ts +++ b/apps/extension/src/core/util/getErc20ContractData.ts @@ -1,4 +1,7 @@ import { ethers } from "ethers" +import { Client, getContract } from "viem" + +import { abiErc20 } from "./abi" const ABI_ERC20 = [ "function symbol() view returns (string)", @@ -10,7 +13,10 @@ export type Erc20ContractData = { decimals: number } -export const getErc20ContractData = async ( +/** + * @deprecated use viem + */ +export const getErc20ContractDataOld = async ( provider: ethers.providers.JsonRpcProvider, contractAddress: string ): Promise => { @@ -18,3 +24,17 @@ export const getErc20ContractData = async ( const [symbol, decimals] = await Promise.all([erc20.symbol(), erc20.decimals()]) return { symbol, decimals } } + +export const getErc20ContractData = async ( + client: Client, + contractAddress: `0x${string}` +): Promise => { + const contract = getContract({ + address: contractAddress, + abi: abiErc20, + publicClient: client, + }) + const [symbol, decimals] = await Promise.all([contract.read.symbol(), contract.read.decimals()]) + //const [symbol, decimals] = await Promise.all([erc20.symbol(), erc20.decimals()]) + return { symbol, decimals } +} diff --git a/apps/extension/src/core/util/getErc20TokenInfo.ts b/apps/extension/src/core/util/getErc20TokenInfo.ts index 37268d9200..05c9c24f48 100644 --- a/apps/extension/src/core/util/getErc20TokenInfo.ts +++ b/apps/extension/src/core/util/getErc20TokenInfo.ts @@ -1,17 +1,41 @@ import { EvmNetworkId } from "@core/domains/ethereum/types" import { CustomErc20TokenCreate } from "@core/domains/tokens/types" import { ethers } from "ethers" +import { Client } from "viem" import { getCoinGeckoErc20Coin } from "./coingecko/getCoinGeckoErc20Coin" -import { getErc20ContractData } from "./getErc20ContractData" +import { getErc20ContractData, getErc20ContractDataOld } from "./getErc20ContractData" -export const getErc20TokenInfo = async ( +/** + * @deprecated use viem + */ +export const getErc20TokenInfoOld = async ( provider: ethers.providers.JsonRpcProvider, evmNetworkId: EvmNetworkId, contractAddress: string ): Promise => { const [{ decimals, symbol }, coinGeckoData] = await Promise.all([ - getErc20ContractData(provider, contractAddress), + getErc20ContractDataOld(provider, contractAddress), + getCoinGeckoErc20Coin(evmNetworkId, contractAddress), + ]) + + return { + evmNetworkId, + contractAddress, + decimals, + symbol, + image: coinGeckoData?.image.small, + coingeckoId: coinGeckoData?.id, + } +} + +export const getErc20TokenInfo = async ( + client: Client, + evmNetworkId: EvmNetworkId, + contractAddress: `0x${string}` +): Promise => { + const [{ decimals, symbol }, coinGeckoData] = await Promise.all([ + getErc20ContractData(client, contractAddress), getCoinGeckoErc20Coin(evmNetworkId, contractAddress), ]) diff --git a/apps/extension/src/ui/hooks/useErc20TokenInfo.ts b/apps/extension/src/ui/hooks/useErc20TokenInfo.ts index 42954cc471..552b2ca64b 100644 --- a/apps/extension/src/ui/hooks/useErc20TokenInfo.ts +++ b/apps/extension/src/ui/hooks/useErc20TokenInfo.ts @@ -1,5 +1,5 @@ import { CustomErc20TokenCreate } from "@core/domains/tokens/types" -import { getErc20TokenInfo } from "@core/util/getErc20TokenInfo" +import { getErc20TokenInfoOld } from "@core/util/getErc20TokenInfo" import { EvmNetworkId } from "@talismn/chaindata-provider" import { useEthereumProvider } from "@ui/domains/Ethereum/useEthereumProvider" import { useEffect, useState } from "react" @@ -16,7 +16,7 @@ export const useErc20TokenInfo = (evmNetworkId?: EvmNetworkId, contractAddress?: setToken(undefined) if (!evmNetworkId || !provider || !contractAddress) return setIsLoading(true) - getErc20TokenInfo(provider, evmNetworkId, contractAddress) + getErc20TokenInfoOld(provider, evmNetworkId, contractAddress) .then(setToken) .catch(setError) .finally(() => setIsLoading(false)) From a6366ed5cd927712e338448b360d28d241c7d0b8 Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Thu, 26 Oct 2023 18:43:30 +0900 Subject: [PATCH 08/50] chore: remove ethers from evm connector --- .changeset/witty-spiders-rhyme.md | 6 + .../domains/ethereum/handler.extension.ts | 63 ++++--- .../src/core/domains/ethereum/helpers.ts | 42 ++++- .../src/core/domains/ethereum/rpcProviders.ts | 27 --- .../ethereum/transactionCountManager.ts | 9 +- .../src/core/domains/ethereum/types.ts | 2 +- .../core/domains/ethereum/viemMigration.ts | 34 ++++ .../src/core/domains/transactions/helpers.ts | 2 +- .../transactions/watchEthereumTransaction.ts | 31 ++-- .../src/core/domains/transfers/handler.ts | 70 +++++--- .../src/core/domains/transfers/types.ts | 2 +- apps/extension/src/ui/api/types.ts | 2 +- .../ui/domains/Ethereum/useEthTransaction.ts | 4 +- .../src/ui/domains/SendFunds/useSendFunds.ts | 4 +- packages/chain-connector-evm/package.json | 1 - .../src/ChainConnectorEvm.ts | 170 ++---------------- .../src/EvmJsonRpcBatchProvider.ts | 96 ---------- .../src/getChainFromEvmNetwork.ts | 15 +- .../src/getEvmNetworkPublicClient.ts | 16 +- .../src/getEvmNetworkWalletClient.ts | 13 +- packages/chain-connector-evm/src/util.ts | 71 -------- packages/on-chain-id/src/index.ts | 16 +- packages/util/src/isEthereumAddress.ts | 4 +- yarn.lock | 1 - 24 files changed, 237 insertions(+), 464 deletions(-) create mode 100644 .changeset/witty-spiders-rhyme.md delete mode 100644 apps/extension/src/core/domains/ethereum/rpcProviders.ts delete mode 100644 packages/chain-connector-evm/src/EvmJsonRpcBatchProvider.ts diff --git a/.changeset/witty-spiders-rhyme.md b/.changeset/witty-spiders-rhyme.md new file mode 100644 index 0000000000..7d09406d5e --- /dev/null +++ b/.changeset/witty-spiders-rhyme.md @@ -0,0 +1,6 @@ +--- +"@talismn/chain-connector-evm": minor +"@talismn/on-chain-id": minor +--- + +replace ethers by viem diff --git a/apps/extension/src/core/domains/ethereum/handler.extension.ts b/apps/extension/src/core/domains/ethereum/handler.extension.ts index 9f4ca28b1a..6e8eb4d6af 100644 --- a/apps/extension/src/core/domains/ethereum/handler.extension.ts +++ b/apps/extension/src/core/domains/ethereum/handler.extension.ts @@ -10,7 +10,6 @@ import { import { talismanAnalytics } from "@core/libs/Analytics" import { ExtensionHandler } from "@core/libs/Handler" import { requestStore } from "@core/libs/requests/store" -import { log } from "@core/log" import { chainConnectorEvm } from "@core/rpcs/chain-connector-evm" import { chaindataProvider } from "@core/rpcs/chaindata" import { MessageHandler, MessageTypes, RequestTypes, ResponseType } from "@core/types" @@ -22,13 +21,12 @@ import { assert } from "@polkadot/util" import { HexString } from "@polkadot/util/types" import { evmNativeTokenId } from "@talismn/balances-evm-native" import { CustomEvmNetwork, githubUnknownTokenLogoUrl } from "@talismn/chaindata-provider" -import { ethers } from "ethers" +import { isEthereumAddress } from "@talismn/util" import { privateKeyToAccount } from "viem/accounts" import { getHostName } from "../app/helpers" import { getHumanReadableErrorMessage } from "./errors" import { rebuildTransactionRequestNumbers } from "./helpers" -import { getProviderForEvmNetworkId } from "./rpcProviders" import { getTransactionCount, incrementTransactionCount } from "./transactionCountManager" import { getViemSendTransactionParams } from "./viemMigration" @@ -46,17 +44,19 @@ export class EthHandler extends ExtensionHandler { account: { address: accountAddress }, } = queued - const provider = await getProviderForEvmNetworkId(ethChainId) - assert(provider, "Unable to find provider for chain " + ethChainId) + const client = await chainConnectorEvm.getPublicClientForEvmNetwork(ethChainId) + assert(client, "Unable to find client for chain " + ethChainId) - const { chainId, hash, from } = await provider.sendTransaction(signedPayload) + const hash = await client.sendRawTransaction({ + serializedTransaction: signedPayload, + }) - watchEthereumTransaction(chainId.toString(), hash, unsigned, { + watchEthereumTransaction(ethChainId, hash, unsigned, { siteUrl: queued.url, notifications: true, }) - incrementTransactionCount(from, chainId.toString()) + if (unsigned.from) incrementTransactionCount(unsigned.from, ethChainId) resolve(hash) @@ -67,7 +67,7 @@ export class EthHandler extends ExtensionHandler { method, hostName: ok ? host : null, dapp: queued.url, - chain: chainId, + chain: Number(ethChainId), networkType: "ethereum", hardwareType: account?.meta.hardwareType, }) @@ -87,6 +87,8 @@ export class EthHandler extends ExtensionHandler { assert(queued, "Unable to find request") const { resolve, reject, ethChainId, account, url } = queued + assert(isEthereumAddress(account.address), "Invalid ethereum address") + // rebuild BigNumber property values (converted to json when serialized) const tx = rebuildTransactionRequestNumbers(transaction) if (tx.nonce === undefined) tx.nonce = await getTransactionCount(account.address, ethChainId) @@ -147,14 +149,14 @@ export class EthHandler extends ExtensionHandler { assert(unsigned.chainId, "chainId is not defined") const evmNetworkId = unsigned.chainId.toString() - const provider = await getProviderForEvmNetworkId(evmNetworkId) - assert(provider, `Unable to find provider for chain ${unsigned.chainId}`) + const client = await chainConnectorEvm.getWalletClientForEvmNetwork(evmNetworkId) + assert(client, "Missing client for chain " + evmNetworkId) // rebuild BigNumber property values (converted to json when serialized) const tx = rebuildTransactionRequestNumbers(unsigned) try { - const { hash } = await provider.sendTransaction(signed) + const hash = await client.sendRawTransaction({ serializedTransaction: signed }) // long running operation, we do not want this inside getPairForAddressSafely watchEthereumTransaction(evmNetworkId, hash, tx, { @@ -182,21 +184,23 @@ export class EthHandler extends ExtensionHandler { assert(unsigned.from, "from is not defined") const evmNetworkId = unsigned.chainId.toString() - const provider = await getProviderForEvmNetworkId(evmNetworkId) - assert(provider, `Unable to find provider for chain ${unsigned.chainId}`) - // rebuild BigNumber property values (converted to json when serialized) const tx = rebuildTransactionRequestNumbers(unsigned) const result = await getPairForAddressSafely(unsigned.from, async (pair) => { + const client = await chainConnectorEvm.getWalletClientForEvmNetwork(evmNetworkId) + assert(client, "Missing client for chain " + evmNetworkId) + const password = this.stores.password.getPassword() assert(password, "Unauthorised") - const privateKey = getPrivateKey(pair, password, "u8a") - const signer = new ethers.Wallet(privateKey, provider) - - const { hash } = await signer.sendTransaction(tx) + const privateKey = getPrivateKey(pair, password, "hex") + const account = privateKeyToAccount(privateKey) - return hash as HexString + return await client.sendTransaction({ + chain: client.chain, + account, + ...getViemSendTransactionParams(tx), + }) }) if (result.ok) { @@ -528,18 +532,13 @@ export class EthHandler extends ExtensionHandler { } private ethRequest: MessageHandler<"pri(eth.request)"> = async ({ chainId, method, params }) => { - const provider = await getProviderForEvmNetworkId(chainId, { batch: true }) - assert(provider, `No healthy RPCs available for chain ${chainId}`) - try { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return await provider.send(method, params as unknown as any[]) - } catch (err) { - log.error("[ethRequest]", { err }) - // errors raised from batches are raw (number code), errors raised from ethers JsonProvider are wrapped by ethers (text code) - // throw error as-is so frontend can figure it out on it's own it, while keeping access to underlying error message - // any component interested in knowing what the error is about should use @core/domains/ethereum/errors helpers - throw err - } + const client = await chainConnectorEvm.getPublicClientForEvmNetwork(chainId) + assert(client, `No client for chain ${chainId}`) + + return client.request({ + method: method as never, + params: params as never, + }) } public async handle( diff --git a/apps/extension/src/core/domains/ethereum/helpers.ts b/apps/extension/src/core/domains/ethereum/helpers.ts index 0f8c1037d3..5d09970409 100644 --- a/apps/extension/src/core/domains/ethereum/helpers.ts +++ b/apps/extension/src/core/domains/ethereum/helpers.ts @@ -7,9 +7,10 @@ import { } from "@core/domains/ethereum/types" import { Token } from "@core/domains/tokens/types" import { assert } from "@polkadot/util" -import { isEthereumAddress } from "@polkadot/util-crypto" import { erc20Abi } from "@talismn/balances-evm-erc20" +import { isEthereumAddress } from "@talismn/util" import { BigNumber, BigNumberish, ethers } from "ethers" +import { encodeFunctionData, getAddress, isAddress } from "viem" import * as yup from "yup" const DERIVATION_PATHS_PATTERNS = { @@ -30,7 +31,10 @@ export const getEthLedgerDerivationPath = (type: LedgerEthDerivationPathType, in return getDerivationPathFromPattern(index, DERIVATION_PATHS_PATTERNS[type]) } -export const getEthTransferTransactionBase = async ( +/** + * @deprecated use viem + */ +export const getEthTransferTransactionBaseOld = async ( evmNetworkId: EvmNetworkId, from: string, to: string, @@ -62,6 +66,40 @@ export const getEthTransferTransactionBase = async ( } } +export const getEthTransferTransactionBase = async ( + evmNetworkId: EvmNetworkId, + from: string, + to: string, + token: Token, + planck: bigint +) => { + assert(evmNetworkId, "evmNetworkId is required") + assert(token, "token is required") + assert(planck, "planck is required") + assert(isAddress(from), "from address is required") + assert(isAddress(to), "to address is required") + + if (token.type === "evm-native") { + return { + from, + value: planck, + to: getAddress(to), + } + } else if (token.type === "evm-erc20") { + const data = encodeFunctionData({ + abi: erc20Abi, + functionName: "transfer", + args: [to, planck], + }) + + return { + from, + to: getAddress(token.contractAddress), + data, + } + } else throw new Error(`Invalid token type ${token.type} - token ${token.id}`) +} + export const getErc20TokenId = ( chainOrNetworkId: ChainId | EvmNetworkId, contractAddress: string diff --git a/apps/extension/src/core/domains/ethereum/rpcProviders.ts b/apps/extension/src/core/domains/ethereum/rpcProviders.ts deleted file mode 100644 index a67f1ac01e..0000000000 --- a/apps/extension/src/core/domains/ethereum/rpcProviders.ts +++ /dev/null @@ -1,27 +0,0 @@ -// TODO: Replace all users of this with an instance of ChainConnectorEvm - -import { chainConnectorEvm } from "@core/rpcs/chain-connector-evm" -import type { GetProviderOptions } from "@talismn/chain-connector-evm" -import type { ethers } from "ethers" - -import { CustomEvmNetwork, EvmNetwork, EvmNetworkId } from "./types" - -export type { GetProviderOptions } from "@talismn/chain-connector-evm" - -// TODO: Refactor any code which uses this function to directly -// call methods on `chainConnectorEvm` instead! -export const getProviderForEthereumNetwork = ( - ethereumNetwork: EvmNetwork | CustomEvmNetwork, - { batch }: GetProviderOptions = {} -): Promise => { - return chainConnectorEvm.getProviderForEvmNetwork(ethereumNetwork, { batch }) -} - -// TODO: Refactor any code which uses this function to directly -// call methods on `chainConnectorEvm` instead! -export const getProviderForEvmNetworkId = async ( - ethereumNetworkId: EvmNetworkId, - { batch }: GetProviderOptions = {} -): Promise => { - return await chainConnectorEvm.getProviderForEvmNetworkId(ethereumNetworkId, { batch }) -} diff --git a/apps/extension/src/core/domains/ethereum/transactionCountManager.ts b/apps/extension/src/core/domains/ethereum/transactionCountManager.ts index e3c77de052..79b9416510 100644 --- a/apps/extension/src/core/domains/ethereum/transactionCountManager.ts +++ b/apps/extension/src/core/domains/ethereum/transactionCountManager.ts @@ -1,4 +1,5 @@ -import { getProviderForEvmNetworkId } from "./rpcProviders" +import { chainConnectorEvm } from "@core/rpcs/chain-connector-evm" + import { EvmNetworkId } from "./types" const dicTransactionCount = new Map() @@ -9,13 +10,13 @@ const getKey = (address: string, evmNetworkId: EvmNetworkId) => /* To be called to set a valid nonce for a transaction */ -export const getTransactionCount = async (address: string, evmNetworkId: EvmNetworkId) => { +export const getTransactionCount = async (address: `0x${string}`, evmNetworkId: EvmNetworkId) => { const key = getKey(address, evmNetworkId) - const provider = await getProviderForEvmNetworkId(evmNetworkId) + const provider = await chainConnectorEvm.getPublicClientForEvmNetwork(evmNetworkId) if (!provider) throw new Error(`Could not find provider for EVM chain ${evmNetworkId}`) - const transactionCount = await provider.getTransactionCount(address) + const transactionCount = await provider.getTransactionCount({ address }) if (!dicTransactionCount.has(key)) { // initial value diff --git a/apps/extension/src/core/domains/ethereum/types.ts b/apps/extension/src/core/domains/ethereum/types.ts index eff4f2a69c..8387b1cfdd 100644 --- a/apps/extension/src/core/domains/ethereum/types.ts +++ b/apps/extension/src/core/domains/ethereum/types.ts @@ -60,7 +60,7 @@ export interface AnyEthRequestChainId extends AnyEthRequest { } export type EthNonceRequest = { - address: string + address: `0x${string}` evmNetworkId: EvmNetworkId } diff --git a/apps/extension/src/core/domains/ethereum/viemMigration.ts b/apps/extension/src/core/domains/ethereum/viemMigration.ts index 44499658d6..adf452d36d 100644 --- a/apps/extension/src/core/domains/ethereum/viemMigration.ts +++ b/apps/extension/src/core/domains/ethereum/viemMigration.ts @@ -5,6 +5,40 @@ import { BigNumber, ethers } from "ethers" import { accessListify } from "ethers/lib/utils" import { AccessList } from "viem" +import { EthGasSettings } from "./types" + +type ViemGasSettings = + | { + type: "legacy" + gas?: bigint + gasPrice?: bigint + } + | { + type: "eip1559" + gas?: bigint + maxFeePerGas?: bigint + maxPriorityFeePerGas?: bigint + } + +export const ethGasSettingsToViemGasSettings = (settings: EthGasSettings): ViemGasSettings => { + return settings.type === 2 + ? ({ + type: "eip1559", + gas: settings.gasLimit ? BigNumber.from(settings.gasLimit).toBigInt() : undefined, + maxFeePerGas: settings.maxFeePerGas + ? BigNumber.from(settings.maxFeePerGas).toBigInt() + : undefined, + maxPriorityFeePerGas: settings.maxPriorityFeePerGas + ? BigNumber.from(settings.maxPriorityFeePerGas).toBigInt() + : undefined, + } as const) + : ({ + type: "legacy", + gas: settings.gasLimit ? BigNumber.from(settings.gasLimit).toBigInt() : undefined, + gasPrice: settings.gasPrice ? BigNumber.from(settings.gasPrice).toBigInt() : undefined, + } as const) +} + export const getViemGasSettings = (tx: ethers.providers.TransactionRequest) => { return tx.type === 2 ? ({ diff --git a/apps/extension/src/core/domains/transactions/helpers.ts b/apps/extension/src/core/domains/transactions/helpers.ts index 7a716a3014..7ca403b2ad 100644 --- a/apps/extension/src/core/domains/transactions/helpers.ts +++ b/apps/extension/src/core/domains/transactions/helpers.ts @@ -103,7 +103,7 @@ export const addSubstrateTransaction = async ( export const updateTransactionStatus = async ( hash: string, status: TransactionStatus, - blockNumber?: number + blockNumber?: bigint | number ) => { try { await db.transactions.update(hash, { status, blockNumber: blockNumber?.toString() }) diff --git a/apps/extension/src/core/domains/transactions/watchEthereumTransaction.ts b/apps/extension/src/core/domains/transactions/watchEthereumTransaction.ts index 4e2c4de16f..6e85bbf3f7 100644 --- a/apps/extension/src/core/domains/transactions/watchEthereumTransaction.ts +++ b/apps/extension/src/core/domains/transactions/watchEthereumTransaction.ts @@ -1,7 +1,7 @@ import { settingsStore } from "@core/domains/app" import { addEvmTransaction, updateTransactionStatus } from "@core/domains/transactions/helpers" -import { log } from "@core/log" import { createNotification } from "@core/notifications" +import { chainConnectorEvm } from "@core/rpcs/chain-connector-evm" import { chaindataProvider } from "@core/rpcs/chaindata" import * as Sentry from "@sentry/browser" import { EvmNetworkId } from "@talismn/chaindata-provider" @@ -9,12 +9,11 @@ import { ethers } from "ethers" import { nanoid } from "nanoid" import urlJoin from "url-join" -import { getProviderForEthereumNetwork } from "../ethereum/rpcProviders" import { WatchTransactionOptions } from "./types" export const watchEthereumTransaction = async ( ethChainId: EvmNetworkId, - txHash: string, + hash: `0x${string}`, unsigned: ethers.providers.TransactionRequest, options: WatchTransactionOptions = {} ) => { @@ -25,30 +24,30 @@ export const watchEthereumTransaction = async ( const ethereumNetwork = await chaindataProvider.getEvmNetwork(ethChainId) if (!ethereumNetwork) throw new Error(`Could not find ethereum network ${ethChainId}`) - const provider = await getProviderForEthereumNetwork(ethereumNetwork, { batch: true }) - if (!provider) - throw new Error(`No provider for network ${ethChainId} (${ethereumNetwork.name})`) + const client = await chainConnectorEvm.getPublicClientForEvmNetwork(ethChainId) + if (!client) throw new Error(`No client for network ${ethChainId} (${ethereumNetwork.name})`) const networkName = ethereumNetwork.name ?? "unknown network" const txUrl = ethereumNetwork.explorerUrl - ? urlJoin(ethereumNetwork.explorerUrl, "tx", txHash) + ? urlJoin(ethereumNetwork.explorerUrl, "tx", hash) : nanoid() // PENDING if (withNotifications) await createNotification("submitted", networkName, txUrl) try { - await addEvmTransaction(txHash, unsigned, { siteUrl, ...transferInfo }) + await addEvmTransaction(hash, unsigned, { siteUrl, ...transferInfo }) - const receipt = await provider.waitForTransaction(txHash) + const receipt = await client.waitForTransactionReceipt({ + hash, + }) // to test failing transactions, swap on busy AMM pools with a 0.05% slippage limit - // status 0 = error - // status 1 = ok - // TODO are there other statuses ? - if (receipt.status === undefined || ![0, 1].includes(receipt.status)) - log.warn("Unknown evm tx status", receipt) - updateTransactionStatus(txHash, receipt.status ? "success" : "error", receipt.blockNumber) + updateTransactionStatus( + hash, + receipt.status === "success" ? "success" : "error", + receipt.blockNumber + ) // success if associated to a block number if (withNotifications) @@ -58,7 +57,7 @@ export const watchEthereumTransaction = async ( txUrl ) } catch (err) { - updateTransactionStatus(txHash, "unknown") + updateTransactionStatus(hash, "error") if (withNotifications) await createNotification("error", networkName, txUrl, err as Error) // eslint-disable-next-line no-console diff --git a/apps/extension/src/core/domains/transfers/handler.ts b/apps/extension/src/core/domains/transfers/handler.ts index cb2b2bd2e9..5fce7935c0 100644 --- a/apps/extension/src/core/domains/transfers/handler.ts +++ b/apps/extension/src/core/domains/transfers/handler.ts @@ -1,6 +1,4 @@ -import { DEBUG } from "@core/constants" -import { getEthTransferTransactionBase, rebuildGasSettings } from "@core/domains/ethereum/helpers" -import { getProviderForEvmNetworkId } from "@core/domains/ethereum/rpcProviders" +import { getEthTransferTransactionBase } from "@core/domains/ethereum/helpers" import { getTransactionCount, incrementTransactionCount, @@ -17,18 +15,18 @@ import { import { getPairForAddressSafely, getPairFromAddress } from "@core/handlers/helpers" import { ExtensionHandler } from "@core/libs/Handler" import { log } from "@core/log" +import { chainConnectorEvm } from "@core/rpcs/chain-connector-evm" import { chaindataProvider } from "@core/rpcs/chaindata" import type { RequestSignatures, RequestTypes, ResponseType } from "@core/types" import { Port } from "@core/types/base" import { getPrivateKey } from "@core/util/getPrivateKey" import { validateHexString } from "@core/util/validateHexString" -import { TransactionRequest } from "@ethersproject/abstract-provider" import { assert } from "@polkadot/util" -import { HexString } from "@polkadot/util/types" import * as Sentry from "@sentry/browser" -import { planckToTokens } from "@talismn/util" -import { Wallet, ethers } from "ethers" +import { isEthereumAddress, planckToTokens } from "@talismn/util" +import { privateKeyToAccount } from "viem/accounts" +import { ethGasSettingsToViemGasSettings } from "../ethereum/viemMigration" import { transferAnalytics } from "./helpers" export default class AssetTransferHandler extends ExtensionHandler { @@ -153,14 +151,20 @@ export default class AssetTransferHandler extends ExtensionHandler { signedTransaction, }: RequestAssetTransferEthHardware): Promise { try { - const provider = await getProviderForEvmNetworkId(evmNetworkId) - if (!provider) throw new Error(`Could not find provider for network ${evmNetworkId}`) + const client = await chainConnectorEvm.getPublicClientForEvmNetwork(evmNetworkId) + if (!client) throw new Error(`Could not find provider for network ${evmNetworkId}`) const token = await chaindataProvider.getToken(tokenId) if (!token) throw new Error(`Invalid tokenId ${tokenId}`) - const { from, to, hash } = await provider.sendTransaction(signedTransaction) - if (!to) throw new Error("Unable to transfer - no recipient address given") + const { from, to } = unsigned + if (!from) throw new Error("Unable to transfer - no from address specified") + if (!to) throw new Error("Unable to transfer - no recipient address specified") + + const hash = await client?.sendRawTransaction({ + serializedTransaction: signedTransaction, + }) + if (!hash) throw new Error("Failed to submit - no hash returned") watchEthereumTransaction(evmNetworkId, hash, unsigned, { transferInfo: { tokenId: token.id, value: amount, to }, @@ -176,11 +180,10 @@ export default class AssetTransferHandler extends ExtensionHandler { incrementTransactionCount(from, evmNetworkId) - return { hash } as { hash: HexString } + return { hash } } catch (err) { const error = err as Error & { reason?: string; error?: Error } - // eslint-disable-next-line no-console - DEBUG && console.error(error.message, { err }) + log.error(error.message, { err }) Sentry.captureException(err, { extra: { tokenId, evmNetworkId } }) throw new Error(error?.error?.message ?? error.reason ?? "Failed to send transaction") } @@ -197,41 +200,50 @@ export default class AssetTransferHandler extends ExtensionHandler { const token = await chaindataProvider.getToken(tokenId) if (!token) throw new Error(`Invalid tokenId ${tokenId}`) - const provider = await getProviderForEvmNetworkId(evmNetworkId) - if (!provider) throw new Error(`Could not find provider for network ${evmNetworkId}`) + assert(isEthereumAddress(fromAddress), "Invalid from address") + assert(isEthereumAddress(toAddress), "Invalid to address") const transfer = await getEthTransferTransactionBase( evmNetworkId, - ethers.utils.getAddress(fromAddress), - ethers.utils.getAddress(toAddress), + fromAddress, + toAddress, token, - amount + BigInt(amount ?? 0) ) - const transaction: TransactionRequest = { - nonce: await getTransactionCount(fromAddress, evmNetworkId), - ...rebuildGasSettings(gasSettings), + const viemGasSettings = ethGasSettingsToViemGasSettings(gasSettings) + + const transaction = { ...transfer, + ...viemGasSettings, + nonce: await getTransactionCount(fromAddress, evmNetworkId), } const result = await getPairForAddressSafely(fromAddress, async (pair) => { + const client = await chainConnectorEvm.getWalletClientForEvmNetwork(evmNetworkId) + assert(client, "Missing client for chain " + evmNetworkId) + const password = this.stores.password.getPassword() assert(password, "Unauthorised") - const privateKey = getPrivateKey(pair, password, "u8a") - const wallet = new Wallet(privateKey, provider) + const privateKey = getPrivateKey(pair, password, "hex") + const account = privateKeyToAccount(privateKey) - const { hash } = await wallet.sendTransaction(transaction) + const hash = await client.sendTransaction({ + chain: client.chain, + account, + ...transaction, + }) incrementTransactionCount(fromAddress, evmNetworkId) - return { hash } as { hash: HexString } + return { hash } }) if (result.ok) { - watchEthereumTransaction(evmNetworkId, result.val.hash, transaction, { - transferInfo: { tokenId: token.id, value: amount, to: toAddress }, - }) + // watchEthereumTransaction(evmNetworkId, result.val.hash, transaction, { + // transferInfo: { tokenId: token.id, value: amount, to: toAddress }, + // }) transferAnalytics({ network: { evmNetworkId }, diff --git a/apps/extension/src/core/domains/transfers/types.ts b/apps/extension/src/core/domains/transfers/types.ts index 4821181152..881838d7f2 100644 --- a/apps/extension/src/core/domains/transfers/types.ts +++ b/apps/extension/src/core/domains/transfers/types.ts @@ -33,7 +33,7 @@ export interface RequestAssetTransferEthHardware { amount: string to: Address unsigned: ethers.providers.TransactionRequest - signedTransaction: string + signedTransaction: `0x${string}` } export interface RequestAssetTransferApproveSign { diff --git a/apps/extension/src/ui/api/types.ts b/apps/extension/src/ui/api/types.ts index 92e3c96066..08d090f9f4 100644 --- a/apps/extension/src/ui/api/types.ts +++ b/apps/extension/src/ui/api/types.ts @@ -280,7 +280,7 @@ export default interface MessageTypes { ) => Promise ethCancelSign: (id: SigningRequestID<"eth-sign" | "eth-send">) => Promise ethRequest: (request: T) => Promise> - ethGetTransactionsCount: (address: string, evmNetworkId: EvmNetworkId) => Promise + ethGetTransactionsCount: (address: `0x${string}`, evmNetworkId: EvmNetworkId) => Promise ethNetworkAddGetRequests: () => Promise ethNetworkAddApprove: (id: AddEthereumChainRequestId) => Promise ethNetworkAddCancel: (is: AddEthereumChainRequestId) => Promise diff --git a/apps/extension/src/ui/domains/Ethereum/useEthTransaction.ts b/apps/extension/src/ui/domains/Ethereum/useEthTransaction.ts index 1fd35054b3..7f0d76a1b2 100644 --- a/apps/extension/src/ui/domains/Ethereum/useEthTransaction.ts +++ b/apps/extension/src/ui/domains/Ethereum/useEthTransaction.ts @@ -34,7 +34,7 @@ import { useIsValidEthTransaction } from "./useIsValidEthTransaction" const UNRELIABLE_GASPRICE_NETWORK_IDS = [137, 80001] const useNonce = ( - address: string | undefined, + address: `0x${string}` | undefined, evmNetworkId: EvmNetworkId | undefined, forcedValue?: number ) => { @@ -377,7 +377,7 @@ export const useEthTransaction = ( const { transactionInfo, error: errorTransactionInfo } = useTransactionInfo(provider, tx) const { hasEip1559Support, error: errorEip1559Support } = useHasEip1559Support(provider) const { nonce, error: nonceError } = useNonce( - tx?.from, + tx?.from as `0x${string}` | undefined, tx?.chainId?.toString(), isReplacement && tx?.nonce ? BigNumber.from(tx.nonce).toNumber() : undefined ) diff --git a/apps/extension/src/ui/domains/SendFunds/useSendFunds.ts b/apps/extension/src/ui/domains/SendFunds/useSendFunds.ts index 5861cba249..02f5f20df5 100644 --- a/apps/extension/src/ui/domains/SendFunds/useSendFunds.ts +++ b/apps/extension/src/ui/domains/SendFunds/useSendFunds.ts @@ -1,5 +1,5 @@ import { AccountType } from "@core/domains/accounts/types" -import { getEthTransferTransactionBase } from "@core/domains/ethereum/helpers" +import { getEthTransferTransactionBaseOld } from "@core/domains/ethereum/helpers" import { AssetTransferMethod } from "@core/domains/transfers/types" import { log } from "@core/log" import { HexString } from "@polkadot/util/types" @@ -108,7 +108,7 @@ const useEvmTransaction = ( if (!isEvmToken(token) || !token.evmNetwork?.id || !from || !token || !amount || !to) setTx(undefined) else { - getEthTransferTransactionBase(token.evmNetwork.id, from, to, token, amount) + getEthTransferTransactionBaseOld(token.evmNetwork.id, from, to, token, amount) .then(setTx) .catch((err) => { setEvmInvalidTxError(err) diff --git a/packages/chain-connector-evm/package.json b/packages/chain-connector-evm/package.json index 531484bd96..0ce8d5326a 100644 --- a/packages/chain-connector-evm/package.json +++ b/packages/chain-connector-evm/package.json @@ -29,7 +29,6 @@ "@talismn/chaindata-provider": "workspace:*", "@talismn/util": "workspace:*", "anylogger": "^1.0.11", - "ethers": "5.7.2", "lodash": "4.17.21", "viem": "^1.16.6" }, diff --git a/packages/chain-connector-evm/src/ChainConnectorEvm.ts b/packages/chain-connector-evm/src/ChainConnectorEvm.ts index 21a2be2dd1..56c4141a72 100644 --- a/packages/chain-connector-evm/src/ChainConnectorEvm.ts +++ b/packages/chain-connector-evm/src/ChainConnectorEvm.ts @@ -1,28 +1,9 @@ -import { - ChaindataTokenProvider, - CustomEvmNetwork, - EvmNetwork, - EvmNetworkId, -} from "@talismn/chaindata-provider" +import { ChaindataTokenProvider, EvmNetworkId } from "@talismn/chaindata-provider" import { ChaindataEvmNetworkProvider } from "@talismn/chaindata-provider" -import { ethers } from "ethers" import { Account, PublicClient, WalletClient } from "viem" -import { RPC_CALL_TIMEOUT } from "./constants" import { clearPublicClientCache, getEvmNetworkPublicClient } from "./getEvmNetworkPublicClient" import { getEvmNetworkWalletClient } from "./getEvmNetworkWalletClient" -import log from "./log" -import { BatchRpcProvider, StandardRpcProvider, addOnfinalityApiKey, getHealthyRpc } from "./util" - -export type GetProviderOptions = { - /** If true, returns a provider which will batch requests */ - batch?: boolean -} - -const getEvmNetworkProviderCacheKey = (evmNetworkId: EvmNetworkId, batch?: boolean) => - `id-${evmNetworkId}-${batch ? "batch" : "standard"}` -const getUrlProviderCacheKey = (url: string, batch?: boolean) => - `url-${url}-${batch ? "batch" : "standard"}` export type ChainConnectorEvmOptions = { onfinalityApiKey?: string @@ -33,26 +14,6 @@ export class ChainConnectorEvm { #chaindataTokenProvider: ChaindataTokenProvider #onfinalityApiKey?: string - // cache for providers - // - // per network providers will change over time if they get unhealthy - // per url providers do not change over time - // - // the cached object is a promise which will return the provider - // if we didn't store the promise, multiple rpc calls sent before an rpc provider is ready - // would end up in us creating multiple rpc providers, instead of what we want which is - // to wait for the single provider to spin up - #providerCache: Map< - EvmNetworkId | string, - Promise - > = new Map() - - // cache for rpc urls per network - // - // always initialized with the order defined in the database - // when an error is raised, push the current rpc to the back of the list - #rpcUrlsCache: Map = new Map() - constructor( chaindataEvmNetworkProvider: ChaindataEvmNetworkProvider, chaindataTokenProvider: ChaindataTokenProvider, @@ -63,48 +24,11 @@ export class ChainConnectorEvm { this.#onfinalityApiKey = options?.onfinalityApiKey ?? undefined } - setOnfinalityApiKey(apiKey: string | undefined) { + public setOnfinalityApiKey(apiKey: string | undefined) { this.#onfinalityApiKey = apiKey this.clearRpcProvidersCache() } - async getProviderForEvmNetworkId( - evmNetworkId: EvmNetworkId, - { batch }: GetProviderOptions = {} - ): Promise { - const network = await this.#chaindataEvmNetworkProvider.getEvmNetwork(evmNetworkId) - if (!network) return null - - return await this.getProviderForEvmNetwork(network, { batch }) - } - - async getProviderForEvmNetwork( - evmNetwork: EvmNetwork | CustomEvmNetwork, - { batch }: GetProviderOptions = {} - ): Promise { - const cacheKey = getEvmNetworkProviderCacheKey(evmNetwork.id, batch) - - // By using `Promise.race`, this variable will immediately resolve to either the - // value of the promise at `this.#providerCache.get(cacheKey)`, - // or if it's still pending, then it will resolve to `undefined` - const cached = await Promise.race([this.#providerCache.get(cacheKey), undefined]) - - const createNewProvider = - // Check if #providerCache has no pending provider for this key - if so, we should attempt to create a new provider - !this.#providerCache.has(cacheKey) || - // Check if #providerCache has already resolved to `null` - if so, we should attempt to create a new provider - cached === null - - if (createNewProvider) { - // store the promise straight away - // otherwise another call to `getProviderForEvmNetwork` would create a new provider, - // instead of what we want which is to wait for this provider to spin up - this.#providerCache.set(cacheKey, this.newProviderFromEvmNetwork(evmNetwork, { batch })) - } - - return (await this.#providerCache.get(cacheKey)) ?? null - } - public async getPublicClientForEvmNetwork( evmNetworkId: EvmNetworkId ): Promise { @@ -113,7 +37,9 @@ export class ChainConnectorEvm { const nativeToken = await this.#chaindataTokenProvider.getToken(network.nativeToken.id) if (!nativeToken) return null - return getEvmNetworkPublicClient(network, nativeToken) + return getEvmNetworkPublicClient(network, nativeToken, { + onFinalityApiKey: this.#onfinalityApiKey, + }) } public async getWalletClientForEvmNetwork( @@ -125,89 +51,13 @@ export class ChainConnectorEvm { const nativeToken = await this.#chaindataTokenProvider.getToken(network.nativeToken.id) if (!nativeToken) return null - return getEvmNetworkWalletClient(network, nativeToken, account) + return getEvmNetworkWalletClient(network, nativeToken, { + onFinalityApiKey: this.#onfinalityApiKey, + account, + }) } - clearRpcProvidersCache(evmNetworkId?: EvmNetworkId, clearRpcUrlsCache = true) { - if (evmNetworkId) { - this.#providerCache.delete(getEvmNetworkProviderCacheKey(evmNetworkId, false)) - this.#providerCache.delete(getEvmNetworkProviderCacheKey(evmNetworkId, true)) - if (clearRpcUrlsCache) this.#rpcUrlsCache.delete(evmNetworkId) - } else { - this.#providerCache.clear() - if (clearRpcUrlsCache) this.#rpcUrlsCache.clear() - } + public clearRpcProvidersCache(evmNetworkId?: EvmNetworkId) { clearPublicClientCache(evmNetworkId) } - - private rotateRpcUrls(evmNetworkId: EvmNetworkId) { - const prevUrls = this.#rpcUrlsCache.get(evmNetworkId) as string[] - if (!prevUrls || prevUrls.length < 2) return prevUrls - - const nextUrls = prevUrls.slice(1).concat(prevUrls[0]) - this.#rpcUrlsCache.set(evmNetworkId, nextUrls) - - return nextUrls - } - - private async newProviderFromEvmNetwork( - evmNetwork: EvmNetwork | CustomEvmNetwork, - { batch }: GetProviderOptions = {} - ): Promise { - if (!Array.isArray(evmNetwork.rpcs)) return null - - const network: ethers.providers.Network = { - name: evmNetwork.name ?? "unknown network", - chainId: parseInt(evmNetwork.id, 10), - } - - // Fixes ENS lookups by adding `ensAddress` to the `network` - which is then passed to the RpcProvider - const ensNetwork = ethers.providers.getNetwork(network.chainId) - if (typeof ensNetwork?.ensAddress === "string") network.ensAddress = ensNetwork.ensAddress - - // initialize cache for rpc urls if empty - if (!this.#rpcUrlsCache.has(evmNetwork.id)) { - const rpcUrls = evmNetwork.rpcs.map(({ url }) => - addOnfinalityApiKey(url, this.#onfinalityApiKey) - ) - this.#rpcUrlsCache.set(evmNetwork.id, rpcUrls) - } - let rpcUrls = this.#rpcUrlsCache.get(evmNetwork.id) as string[] - - const url = await getHealthyRpc(rpcUrls, network) - if (!url) return null - - // if healthy rpc url isn't the first one, rotate rpc urls cache to reflect that - while (rpcUrls.includes(url) && rpcUrls[0] !== url) rpcUrls = this.rotateRpcUrls(evmNetwork.id) - - const urlCacheKey = getUrlProviderCacheKey(url, batch) - if (!this.#providerCache.has(urlCacheKey)) { - const connection: ethers.utils.ConnectionInfo = { - url, - errorPassThrough: true, - timeout: RPC_CALL_TIMEOUT, - - // number of attempts when a request is throttled (429) : default is 12, minimum is 1 - // use 1 so we change RPC as soon as we detect a throttling, without trying again - throttleLimit: 1, - } - - const provider = - batch === true - ? new BatchRpcProvider(connection, network) - : new StandardRpcProvider(connection, network) - - // in case an error is thrown, rotate rpc urls cache - // also clear provider cache to force logic going through getHealthyRpc again on next call - provider.on("error", (...args: unknown[]) => { - log.error("EVM RPC error %s (%s)", url, batch ? "batch" : "standard", args) - this.rotateRpcUrls(evmNetwork.id) - this.clearRpcProvidersCache(evmNetwork.id, false) - }) - - this.#providerCache.set(urlCacheKey, Promise.resolve(provider)) - } - - return (await this.#providerCache.get(urlCacheKey)) ?? null - } } diff --git a/packages/chain-connector-evm/src/EvmJsonRpcBatchProvider.ts b/packages/chain-connector-evm/src/EvmJsonRpcBatchProvider.ts deleted file mode 100644 index 3527050155..0000000000 --- a/packages/chain-connector-evm/src/EvmJsonRpcBatchProvider.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { ethers } from "ethers" -import debounce from "lodash/debounce" - -type PendingRequest = { - method: string - params: unknown[] - resolve: (value: unknown) => void - reject: (reason?: unknown) => void -} - -type BatchItemResult = { - id: number - result?: unknown - error?: { message: string; code?: unknown; data?: unknown } -} - -const BATCH_SIZE_LIMIT = 50 // requests -const BATCH_MAX_WAIT = 10 // milliseconds - -export class EvmJsonRpcBatchProvider extends ethers.providers.StaticJsonRpcProvider { - private queue: PendingRequest[] = [] - private processQueue = debounce(this.sendBatch, BATCH_MAX_WAIT) - - private sendBatch() { - if (this.queue.length === 0) return - - const batch = this.queue.splice(0, BATCH_SIZE_LIMIT).map((req, index) => ({ - ...(req as Required), - id: index + 1, - jsonrpc: "2.0", - })) - - const payload = batch.map(({ method, params, id, jsonrpc }) => ({ - method, - params, - id, - jsonrpc, - })) - - ethers.utils - .fetchJson(this.connection, JSON.stringify(payload)) - .then((results: BatchItemResult[]) => { - if (!Array.isArray(results)) throw new Error("Invalid batch response") - - // order is not guaranteed, prepare a map to access items by their id - const resultsMap = results.reduce( - (map, result) => map.set(result.id, result), - new Map() - ) - batch.forEach((request) => { - const batchItem = resultsMap.get(request.id) - if (!batchItem) return request.reject(new Error("Missing batch item")) - - const { result, error } = batchItem - if (error) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const err: any = new Error(error.message) - err.code = error.code - err.data = error.data - request.reject(err) - } else { - request.resolve(result) - } - }) - }) - .catch((err) => { - // emit error to cycle fallback provider - this.emit("error", err) - - // reject all requests using a custom error message that will prevent more fallback cycling - batch.forEach((request) => { - request.reject(new Error("BATCH_FAILED", { cause: err })) - }) - }) - } - - send(method: string, params: unknown[]): Promise { - const request: Partial = { method, params } - - const result = new Promise((resolve, reject) => { - // will be resolved/rejected from the sendBatch method - request.resolve = resolve - request.reject = reject - }) - - this.queue.push(request as PendingRequest) - - // force batch to be processed if batch size is reached - if (this.queue.length >= BATCH_SIZE_LIMIT) this.processQueue.flush() - - // call debounced batch processing anyway in case we're over the batch size limit - this.processQueue() - - return result - } -} diff --git a/packages/chain-connector-evm/src/getChainFromEvmNetwork.ts b/packages/chain-connector-evm/src/getChainFromEvmNetwork.ts index 70c0fc72a4..69f904cd0d 100644 --- a/packages/chain-connector-evm/src/getChainFromEvmNetwork.ts +++ b/packages/chain-connector-evm/src/getChainFromEvmNetwork.ts @@ -2,6 +2,8 @@ import { EvmNetwork, Token } from "@talismn/chaindata-provider" import { Chain } from "viem" import * as chains from "viem/chains" +import { addOnfinalityApiKey } from "./util" + // viem chains benefit from multicall config & other viem goodies const VIEM_CHAINS = Object.keys(chains).reduce((acc, curr) => { const chain = chains[curr as keyof typeof chains] @@ -16,14 +18,23 @@ export const clearChainsCache = (evmNetworkId?: string) => { else chainsCache.clear() } -export const getChainFromEvmNetwork = (evmNetwork: EvmNetwork, nativeToken: Token): Chain => { +export type ChainOptions = { + onFinalityApiKey?: string +} + +export const getChainFromEvmNetwork = ( + evmNetwork: EvmNetwork, + nativeToken: Token, + options: ChainOptions = {} +): Chain => { if (!evmNetwork?.nativeToken?.id) throw new Error("Undefined native token") if (evmNetwork.nativeToken.id !== nativeToken.id) throw new Error("Native token mismatch") const { symbol, decimals } = nativeToken if (!chainsCache.has(evmNetwork.id)) { - const chainRpcs = evmNetwork.rpcs?.map((rpc) => rpc.url) ?? [] + const chainRpcs = + evmNetwork.rpcs?.map((rpc) => addOnfinalityApiKey(rpc.url, options.onFinalityApiKey)) ?? [] const viemChain = VIEM_CHAINS[Number(evmNetwork.id)] ?? {} diff --git a/packages/chain-connector-evm/src/getEvmNetworkPublicClient.ts b/packages/chain-connector-evm/src/getEvmNetworkPublicClient.ts index 5d26f79f70..e6db608553 100644 --- a/packages/chain-connector-evm/src/getEvmNetworkPublicClient.ts +++ b/packages/chain-connector-evm/src/getEvmNetworkPublicClient.ts @@ -2,6 +2,7 @@ import { EvmNetwork, Token } from "@talismn/chaindata-provider" import { PublicClient, createPublicClient, fallback, http } from "viem" import { clearChainsCache, getChainFromEvmNetwork } from "./getChainFromEvmNetwork" +import { addOnfinalityApiKey } from "./util" const BATCH_WAIT = 25 const BATCH_SIZE = 30 @@ -16,9 +17,14 @@ export const clearPublicClientCache = (evmNetworkId?: string) => { else publicClientCache.clear() } +type PublicClientOptions = { + onFinalityApiKey?: string +} + export const getEvmNetworkPublicClient = ( evmNetwork: EvmNetwork, - nativeToken: Token + nativeToken: Token, + options: PublicClientOptions = {} ): PublicClient => { const chain = getChainFromEvmNetwork(evmNetwork, nativeToken) @@ -33,7 +39,13 @@ export const getEvmNetworkPublicClient = ( wait: BATCH_WAIT, }, }) - : fallback(evmNetwork.rpcs.map((rpc) => http(rpc.url, { batch: { wait: BATCH_WAIT } }))) + : fallback( + evmNetwork.rpcs.map((rpc) => + http(addOnfinalityApiKey(rpc.url, options.onFinalityApiKey), { + batch: { wait: BATCH_WAIT }, + }) + ) + ) publicClientCache.set(evmNetwork.id, createPublicClient({ chain, transport, batch })) } diff --git a/packages/chain-connector-evm/src/getEvmNetworkWalletClient.ts b/packages/chain-connector-evm/src/getEvmNetworkWalletClient.ts index 4cf90ace7c..08b0b71c4c 100644 --- a/packages/chain-connector-evm/src/getEvmNetworkWalletClient.ts +++ b/packages/chain-connector-evm/src/getEvmNetworkWalletClient.ts @@ -3,12 +3,19 @@ import { Account, WalletClient, createWalletClient, http } from "viem" import { getChainFromEvmNetwork } from "./getChainFromEvmNetwork" +type WalletClientOptions = { + account?: `0x${string}` | Account + onFinalityApiKey?: string +} + export const getEvmNetworkWalletClient = ( evmNetwork: EvmNetwork, nativeToken: Token, - account?: `0x${string}` | Account + options: WalletClientOptions = {} ): WalletClient => { - const chain = getChainFromEvmNetwork(evmNetwork, nativeToken) + const chain = getChainFromEvmNetwork(evmNetwork, nativeToken, { + onFinalityApiKey: options.onFinalityApiKey, + }) - return createWalletClient({ chain, transport: http(), account }) + return createWalletClient({ chain, transport: http(), account: options.account }) } diff --git a/packages/chain-connector-evm/src/util.ts b/packages/chain-connector-evm/src/util.ts index 520f72bcb4..ec7e4a7a1c 100644 --- a/packages/chain-connector-evm/src/util.ts +++ b/packages/chain-connector-evm/src/util.ts @@ -1,10 +1,3 @@ -import { throwAfter } from "@talismn/util" -import { ethers } from "ethers" - -import { RPC_HEALTHCHECK_TIMEOUT } from "./constants" -import { EvmJsonRpcBatchProvider } from "./EvmJsonRpcBatchProvider" -import log from "./log" - /** * Helper function to add our onfinality api key to a public onfinality RPC url. */ @@ -22,67 +15,3 @@ export const addOnfinalityApiKey = (rpcUrl: string, onfinalityApiKey?: string) = `https://$1.api.onfinality.io/rpc?apikey=${onfinalityApiKey}` ) } - -export const isHealthyRpc = async (url: string, chainId: number) => { - try { - // StaticJsonRpcProvider is better suited for this as it will not do health check requests on it's own - const provider = new ethers.providers.StaticJsonRpcProvider(url, { - chainId, - name: `EVM Network ${chainId}`, - }) - - // check that RPC responds in time - const rpcChainId = await Promise.race([ - provider.send("eth_chainId", []), - throwAfter(RPC_HEALTHCHECK_TIMEOUT, "timeout"), - ]) - - // with expected chain id - return parseInt(rpcChainId, 16) === chainId - } catch (err) { - log.error("Unhealthy EVM RPC %s", url, { err }) - return false - } -} - -export const getHealthyRpc = async (rpcUrls: string[], network: ethers.providers.Network) => { - for (const rpcUrl of rpcUrls) if (await isHealthyRpc(rpcUrl, network.chainId)) return rpcUrl - - log.warn("No healthy RPC for EVM network %s (%d)", network.name, network.chainId) - return null -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const isUnhealthyRpcError = (err: any) => { - // expected errors that are not related to RPC health - // ex : throw revert on a transaction call that fails - if (err?.message === "BATCH_FAILED") return false - if (err?.reason === "processing response error") return false - - // if unknown, assume RPC is unhealthy - return true -} - -export class StandardRpcProvider extends ethers.providers.JsonRpcProvider { - async send(method: string, params: Array): Promise { - try { - return await super.send(method, params) - } catch (err) { - // emit error so rpc manager considers this rpc unhealthy - if (isUnhealthyRpcError(err)) this.emit("error", err) - throw err - } - } -} - -export class BatchRpcProvider extends EvmJsonRpcBatchProvider { - async send(method: string, params: Array): Promise { - try { - return await super.send(method, params) - } catch (err) { - // emit error so rpc manager considers this rpc unhealthy - if (isUnhealthyRpcError(err)) this.emit("error", err) - throw err - } - } -} diff --git a/packages/on-chain-id/src/index.ts b/packages/on-chain-id/src/index.ts index 9cd71d0d86..d6bdd3dcaf 100644 --- a/packages/on-chain-id/src/index.ts +++ b/packages/on-chain-id/src/index.ts @@ -30,18 +30,18 @@ export class OnChainId { async resolveNames(names: string[]): Promise> { const resolvedNames = new Map(names.map((name) => [name, null])) - const provider = await this.chainConnectors.evm?.getProviderForEvmNetworkId( + const client = await this.chainConnectors.evm?.getPublicClientForEvmNetwork( this.networkIdEthereum ) - if (!provider) { - log.warn(`Could not find Ethereum provider in OnChainId::resolveNames`) + if (!client) { + log.warn(`Could not find Ethereum client in OnChainId::resolveNames`) return resolvedNames } const results = await Promise.allSettled( names.map(async (name) => { try { - const address = await provider.resolveName(name) + const address = await client.getEnsAddress({ name }) name !== null && resolvedNames.set(name, address) } catch (cause) { throw new Error(`Failed to resolve address for ENS domain '${name}'`, { cause }) @@ -159,11 +159,11 @@ export class OnChainId { async lookupEnsAddresses(addresses: string[]): Promise { const onChainIds: OnChainIds = new Map(addresses.map((address) => [address, null])) - const provider = await this.chainConnectors.evm?.getProviderForEvmNetworkId( + const client = await this.chainConnectors.evm?.getPublicClientForEvmNetwork( this.networkIdEthereum ) - if (!provider) { - log.warn(`Could not find Ethereum provider in OnChainId::lookupEnsAddresses`) + if (!client) { + log.warn(`Could not find Ethereum client in OnChainId::lookupEnsAddresses`) return onChainIds } @@ -173,7 +173,7 @@ export class OnChainId { if (!isEthereumAddress(address)) return try { - const domain = await provider.lookupAddress(address) + const domain = await client.getEnsName({ address }) domain !== null && onChainIds.set(address, domain) } catch (cause) { throw new Error( diff --git a/packages/util/src/isEthereumAddress.ts b/packages/util/src/isEthereumAddress.ts index a7fd77293d..feac1e011b 100644 --- a/packages/util/src/isEthereumAddress.ts +++ b/packages/util/src/isEthereumAddress.ts @@ -1,2 +1,2 @@ -export const isEthereumAddress = (address: string): address is `0x${string}` => - address.startsWith("0x") && address.length === 42 +export const isEthereumAddress = (address: string | undefined | null): address is `0x${string}` => + !!address && address.startsWith("0x") && address.length === 42 diff --git a/yarn.lock b/yarn.lock index 28c1ee512c..bde675d8ed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7779,7 +7779,6 @@ __metadata: "@types/lodash": ^4.14.180 anylogger: ^1.0.11 eslint: ^8.52.0 - ethers: 5.7.2 jest: ^28.1.0 lodash: 4.17.21 ts-jest: ^28.0.2 From ec0db3683aed19580f29a4a30709257e2a8f2e99 Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Thu, 26 Oct 2023 21:48:04 +0900 Subject: [PATCH 09/50] wip: extension publicClient --- .../src/core/domains/ethereum/types.ts | 3 ++ .../src/core/util/getErc20TokenInfo.ts | 4 +-- .../Ethereum/getExtensionEthereumProvider.ts | 30 ++++++++++++++----- .../domains/Ethereum/useEthereumProvider.ts | 18 ++++++++++- .../xTokens/EthSignMoonXTokensTransfer.tsx | 3 +- .../src/ui/hooks/useErc20TokenInfo.ts | 15 +++++----- .../balances-evm-erc20/src/EvmErc20Module.ts | 4 +-- .../src/SubstratePsp22Module.ts | 2 +- packages/balances/src/BalanceModule.ts | 6 ++-- 9 files changed, 59 insertions(+), 26 deletions(-) diff --git a/apps/extension/src/core/domains/ethereum/types.ts b/apps/extension/src/core/domains/ethereum/types.ts index 8387b1cfdd..37524128aa 100644 --- a/apps/extension/src/core/domains/ethereum/types.ts +++ b/apps/extension/src/core/domains/ethereum/types.ts @@ -5,9 +5,12 @@ import { BaseRequest, BaseRequestId, RequestIdOnly } from "@core/types/base" import { HexString } from "@polkadot/util/types" import { EvmNetworkId } from "@talismn/chaindata-provider" import { BigNumberish, ethers } from "ethers" +import type { Address as EvmAddress } from "viem" import { WalletTransactionTransferInfo } from "../transactions" +export type { EvmAddress } + export type { EvmNetwork, CustomEvmNetwork, diff --git a/apps/extension/src/core/util/getErc20TokenInfo.ts b/apps/extension/src/core/util/getErc20TokenInfo.ts index 05c9c24f48..0a23162747 100644 --- a/apps/extension/src/core/util/getErc20TokenInfo.ts +++ b/apps/extension/src/core/util/getErc20TokenInfo.ts @@ -1,4 +1,4 @@ -import { EvmNetworkId } from "@core/domains/ethereum/types" +import { EvmAddress, EvmNetworkId } from "@core/domains/ethereum/types" import { CustomErc20TokenCreate } from "@core/domains/tokens/types" import { ethers } from "ethers" import { Client } from "viem" @@ -12,7 +12,7 @@ import { getErc20ContractData, getErc20ContractDataOld } from "./getErc20Contrac export const getErc20TokenInfoOld = async ( provider: ethers.providers.JsonRpcProvider, evmNetworkId: EvmNetworkId, - contractAddress: string + contractAddress: EvmAddress ): Promise => { const [{ decimals, symbol }, coinGeckoData] = await Promise.all([ getErc20ContractDataOld(provider, contractAddress), diff --git a/apps/extension/src/ui/domains/Ethereum/getExtensionEthereumProvider.ts b/apps/extension/src/ui/domains/Ethereum/getExtensionEthereumProvider.ts index 9ad98d2048..b996965c46 100644 --- a/apps/extension/src/ui/domains/Ethereum/getExtensionEthereumProvider.ts +++ b/apps/extension/src/ui/domains/Ethereum/getExtensionEthereumProvider.ts @@ -1,17 +1,21 @@ -import { - ETH_ERROR_EIP1474_INTERNAL_ERROR, - EthProviderRpcError, -} from "@core/injectEth/EthProviderRpcError" +// import { +// ETH_ERROR_EIP1474_INTERNAL_ERROR, +// EthProviderRpcError, +// } from "@core/injectEth/EthProviderRpcError" import { EthRequestSignatures, EthRequestTypes } from "@core/injectEth/types" import { log } from "@core/log" import { EvmNetworkId } from "@talismn/chaindata-provider" import { api } from "@ui/api" import { ethers } from "ethers" +import { PublicClient, createPublicClient, custom } from "viem" + +type ViemRequest = (method: string, params?: unknown[]) => Promise const ethereumRequest = - (chainId: EvmNetworkId): ethers.providers.JsonRpcFetchFunc => + (chainId: EvmNetworkId): ViemRequest => async (method: string, params?: unknown[]) => { try { + // TODO cleanup typings return await api.ethRequest({ chainId, method: method as keyof EthRequestSignatures, @@ -19,12 +23,22 @@ const ethereumRequest = }) } catch (err) { log.error("[provider.request] error on %s", method, { err }) - - const { message, code, data } = err as EthProviderRpcError - throw new EthProviderRpcError(message, code ?? ETH_ERROR_EIP1474_INTERNAL_ERROR, data) + throw err + // TODO check that we get proper error codes + // const { message, code, data } = err as EthProviderRpcError + // throw new EthProviderRpcError(message, code ?? ETH_ERROR_EIP1474_INTERNAL_ERROR, data) } } export const getExtensionEthereumProvider = (evmNetworkId: EvmNetworkId) => { return new ethers.providers.Web3Provider(ethereumRequest(evmNetworkId)) } + +export const getExtensionPublicClient = (evmNetworkId: EvmNetworkId): PublicClient => { + return createPublicClient({ + // TODO check timers, decide if they should be here (remove them on backend) or clear them here and use defaults on backend + transport: custom({ + request: ethereumRequest(evmNetworkId), + }), + }) +} diff --git a/apps/extension/src/ui/domains/Ethereum/useEthereumProvider.ts b/apps/extension/src/ui/domains/Ethereum/useEthereumProvider.ts index 778dfb1358..69ce35a4c0 100644 --- a/apps/extension/src/ui/domains/Ethereum/useEthereumProvider.ts +++ b/apps/extension/src/ui/domains/Ethereum/useEthereumProvider.ts @@ -1,8 +1,15 @@ import { EvmNetworkId } from "@core/domains/ethereum/types" -import { getExtensionEthereumProvider } from "@ui/domains/Ethereum/getExtensionEthereumProvider" +import { + getExtensionEthereumProvider, + getExtensionPublicClient, +} from "@ui/domains/Ethereum/getExtensionEthereumProvider" import { ethers } from "ethers" import { useMemo } from "react" +import { PublicClient } from "viem" +/** + * @deprecated use usePublicClient instead + */ export const useEthereumProvider = ( evmNetworkId?: EvmNetworkId ): ethers.providers.JsonRpcProvider | undefined => { @@ -13,3 +20,12 @@ export const useEthereumProvider = ( return provider } + +export const usePublicClient = (evmNetworkId?: EvmNetworkId): PublicClient | undefined => { + const publicClient = useMemo(() => { + if (!evmNetworkId) return undefined + return getExtensionPublicClient(evmNetworkId) + }, [evmNetworkId]) + + return publicClient +} diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/xTokens/EthSignMoonXTokensTransfer.tsx b/apps/extension/src/ui/domains/Sign/Ethereum/xTokens/EthSignMoonXTokensTransfer.tsx index 0777c31421..6da342a995 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/xTokens/EthSignMoonXTokensTransfer.tsx +++ b/apps/extension/src/ui/domains/Sign/Ethereum/xTokens/EthSignMoonXTokensTransfer.tsx @@ -1,3 +1,4 @@ +import { EvmAddress } from "@core/domains/ethereum/types" import { hexToU8a } from "@polkadot/util" import { Address } from "@talismn/balances" import { encodeAnyAddress } from "@talismn/util" @@ -78,7 +79,7 @@ export const EthSignMoonXTokensTransfer: FC = () => { "destination" ) const amount = getContractCallArg(transactionInfo.contractCall, "amount") - const currencyAddress = getContractCallArg( + const currencyAddress = getContractCallArg( transactionInfo.contractCall, "currency_address" ) diff --git a/apps/extension/src/ui/hooks/useErc20TokenInfo.ts b/apps/extension/src/ui/hooks/useErc20TokenInfo.ts index 552b2ca64b..5ddf4062a0 100644 --- a/apps/extension/src/ui/hooks/useErc20TokenInfo.ts +++ b/apps/extension/src/ui/hooks/useErc20TokenInfo.ts @@ -1,26 +1,27 @@ +import { EvmAddress } from "@core/domains/ethereum/types" import { CustomErc20TokenCreate } from "@core/domains/tokens/types" -import { getErc20TokenInfoOld } from "@core/util/getErc20TokenInfo" +import { getErc20TokenInfo } from "@core/util/getErc20TokenInfo" import { EvmNetworkId } from "@talismn/chaindata-provider" -import { useEthereumProvider } from "@ui/domains/Ethereum/useEthereumProvider" +import { usePublicClient } from "@ui/domains/Ethereum/useEthereumProvider" import { useEffect, useState } from "react" -export const useErc20TokenInfo = (evmNetworkId?: EvmNetworkId, contractAddress?: string) => { +export const useErc20TokenInfo = (evmNetworkId?: EvmNetworkId, contractAddress?: EvmAddress) => { const [isLoading, setIsLoading] = useState(false) const [error, setError] = useState() const [token, setToken] = useState() - const provider = useEthereumProvider(evmNetworkId) + const publicClient = usePublicClient(evmNetworkId) useEffect(() => { setError(undefined) setToken(undefined) - if (!evmNetworkId || !provider || !contractAddress) return + if (!evmNetworkId || !publicClient || !contractAddress) return setIsLoading(true) - getErc20TokenInfoOld(provider, evmNetworkId, contractAddress) + getErc20TokenInfo(publicClient, evmNetworkId, contractAddress) .then(setToken) .catch(setError) .finally(() => setIsLoading(false)) - }, [contractAddress, evmNetworkId, provider]) + }, [contractAddress, evmNetworkId, publicClient]) return { isLoading, error, token } } diff --git a/packages/balances-evm-erc20/src/EvmErc20Module.ts b/packages/balances-evm-erc20/src/EvmErc20Module.ts index e7db568465..0c2abd00f6 100644 --- a/packages/balances-evm-erc20/src/EvmErc20Module.ts +++ b/packages/balances-evm-erc20/src/EvmErc20Module.ts @@ -35,7 +35,7 @@ export const evmErc20TokenId = ( export type EvmErc20Token = NewTokenType< ModuleType, { - contractAddress: string + contractAddress: `0x${string}` evmNetwork: { id: EvmNetworkId } | null } > @@ -60,7 +60,7 @@ export type EvmErc20ModuleConfig = { symbol?: string decimals?: number coingeckoId?: string - contractAddress?: string + contractAddress?: `0x${string}` }> } diff --git a/packages/balances-substrate-psp22/src/SubstratePsp22Module.ts b/packages/balances-substrate-psp22/src/SubstratePsp22Module.ts index 67c8111be8..95815b7f7a 100644 --- a/packages/balances-substrate-psp22/src/SubstratePsp22Module.ts +++ b/packages/balances-substrate-psp22/src/SubstratePsp22Module.ts @@ -267,7 +267,7 @@ export const SubPsp22Module: NewBalanceModule< }) if (token.contractAddress === undefined) { - log.debug(`Token ${tokenId} of type ${token.type} doesn't have a contractAddress`) + log.debug(`Token ${tokenId} of type substrate-psp22 doesn't have a contractAddress`) return [] } diff --git a/packages/balances/src/BalanceModule.ts b/packages/balances/src/BalanceModule.ts index 101887b4b4..5969a51d6a 100644 --- a/packages/balances/src/BalanceModule.ts +++ b/packages/balances/src/BalanceModule.ts @@ -2,7 +2,6 @@ import { UnsignedTransaction } from "@substrate/txwrapper-core" import { ChainConnector } from "@talismn/chain-connector" import { ChainConnectorEvm } from "@talismn/chain-connector-evm" import { ChainId, ChaindataProvider, IToken } from "@talismn/chaindata-provider" -import { ethers } from "ethers" import { AddressesByToken, Balances, SubscriptionCallback, UnsubscribeFn } from "./types" @@ -17,9 +16,8 @@ export type DefaultTransferParams = undefined export type NewTransferParamsType> = BaseTransferParams & T -export type TransferTokenTx = - | { type: "substrate"; tx: UnsignedTransaction } - | { type: "evm"; tx: ethers.providers.TransactionRequest } +export type TransferTokenTx = { type: "substrate"; tx: UnsignedTransaction } +// | { type: "evm"; tx: TransactionRequest } export type ChainConnectors = { substrate?: ChainConnector; evm?: ChainConnectorEvm } export type Hydrate = { From 5961adda41f3e7f8509ddeed033d1dd1e6f2e855 Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Fri, 27 Oct 2023 13:09:17 +0900 Subject: [PATCH 10/50] wip: migrate typings --- .../src/core/domains/ethereum/handler.tabs.ts | 10 ++--- .../injectEth/getInjectableEvmProvider.ts | 38 +++++++------------ apps/extension/src/core/injectEth/types.ts | 27 +++++++++++-- apps/extension/src/ui/api/types.ts | 3 +- .../Ethereum/getExtensionEthereumProvider.ts | 12 +----- 5 files changed, 42 insertions(+), 48 deletions(-) diff --git a/apps/extension/src/core/domains/ethereum/handler.tabs.ts b/apps/extension/src/core/domains/ethereum/handler.tabs.ts index fc0f194e82..b7dd2eafac 100644 --- a/apps/extension/src/core/domains/ethereum/handler.tabs.ts +++ b/apps/extension/src/core/domains/ethereum/handler.tabs.ts @@ -25,7 +25,6 @@ import { EthProviderMessage, EthRequestArguments, EthRequestSignArguments, - EthRequestSignatures, } from "@core/injectEth/types" import { TabsHandler } from "@core/libs/Handler" import { log } from "@core/log" @@ -387,10 +386,7 @@ export class EthTabsHandler extends TabsHandler { return site?.ethChainId ?? DEFAULT_ETH_CHAIN_ID } - private async getFallbackRequest( - url: string, - request: EthRequestArguments - ): Promise { + private async getFallbackRequest(url: string, request: AnyEthRequest): Promise { // obtain the chain id without checking auth. // note: this method is only called if method doesn't require auth, or if auth is already checked const chainId = await this.getChainId(url) @@ -678,10 +674,10 @@ export class EthTabsHandler extends TabsHandler { return this.getPermissions(url) } - private async ethRequest( + private async ethRequest( id: string, url: string, - request: EthRequestArguments, + request: AnyEthRequest, port: Port ): Promise { if ( diff --git a/apps/extension/src/core/injectEth/getInjectableEvmProvider.ts b/apps/extension/src/core/injectEth/getInjectableEvmProvider.ts index 22c9e0a6a2..6dd15851f3 100644 --- a/apps/extension/src/core/injectEth/getInjectableEvmProvider.ts +++ b/apps/extension/src/core/injectEth/getInjectableEvmProvider.ts @@ -9,18 +9,17 @@ import { ETH_ERROR_EIP1993_USER_REJECTED, EthProviderRpcError, } from "./EthProviderRpcError" -import type { - EthRequestArguments, - EthRequestSignatures, - EthRequestTypes, - EthResponseType, -} from "./types" + +interface RequestArguments { + readonly method: string + readonly params?: readonly unknown[] | object +} interface JsonRpcRequest { id: string | undefined jsonrpc: "2.0" method: string - params?: Array + params?: Array } interface JsonRpcResponse { @@ -81,14 +80,8 @@ export const getInjectableEvmProvider = (sendRequest: SendRequest) => { log.debug("Talisman provider initializing") const [resChainId, resAccounts] = await Promise.all([ - sendRequest("pub(eth.request)", { - method: "eth_chainId", - params: null, - }), - sendRequest("pub(eth.request)", { - method: "eth_accounts", - params: null, - }), + sendRequest("pub(eth.request)", { method: "eth_chainId" }), + sendRequest("pub(eth.request)", { method: "eth_accounts" }), ]) const chainId = resChainId as string @@ -146,9 +139,7 @@ export const getInjectableEvmProvider = (sendRequest: SendRequest) => { const waitReady = initialize() - const request = async ( - args: EthRequestArguments - ): Promise> => { + const request = async (args: RequestArguments): Promise => { try { log.debug("[talismanEth.request] request %s", args.method, args.params) await waitReady @@ -184,8 +175,8 @@ export const getInjectableEvmProvider = (sendRequest: SendRequest) => { if (typeof methodOrPayload === "string") return request({ - method: methodOrPayload as keyof EthRequestSignatures, - params: paramsOrCallback as any, + method: methodOrPayload, + params: paramsOrCallback, }) else { return request(methodOrPayload).then(paramsOrCallback) @@ -197,10 +188,7 @@ export const getInjectableEvmProvider = (sendRequest: SendRequest) => { const { method, params, ...rest } = payload try { - const result = await request({ - method: method as EthRequestTypes, - params: params as any, - }) + const result = await request({ method, params }) callback(null, { ...rest, method, result }) } catch (err) { const error = err as Error @@ -213,7 +201,7 @@ export const getInjectableEvmProvider = (sendRequest: SendRequest) => { log.debug("[talismanEth.enable]") // some frameworks such as web3modal requires this method to exist - return request({ method: "eth_requestAccounts", params: null }) + return request({ method: "eth_requestAccounts" }) } provider.isConnected = isConnected diff --git a/apps/extension/src/core/injectEth/types.ts b/apps/extension/src/core/injectEth/types.ts index b432d791b3..1d8e8ee095 100644 --- a/apps/extension/src/core/injectEth/types.ts +++ b/apps/extension/src/core/injectEth/types.ts @@ -17,6 +17,7 @@ import type { } from "@ethersproject/providers" // Compliant with https://eips.ethereum.org/EIPS/eip-1193 import type { InjectedAccount } from "@polkadot/extension-inject/types" +import { PublicRpcSchema, RpcSchema, WalletRpcSchema } from "viem" type Promisify = T | Promise @@ -56,7 +57,27 @@ export type EthRequestAddEthereumChain = [AddEthereumChainParameter] export type EthRequestSwitchEthereumChain = [{ chainId: string }] -// TODO : nuke all this and use viem's EIP1193RequestFn ? +type RpcSchemaMap = { + [K in TRpcSchema[number]["Method"]]: [ + Extract["Parameters"], + Extract["ReturnType"] + ] +} + +// type RpcSchemaMapBetter = { +// [K in TRpcSchema[number]["Method"]]: { +// parameters: Extract["Parameters"] +// returnType: Extract["ReturnType"] +// } +// } + +export type FullRpcSchema = [...PublicRpcSchema, ...WalletRpcSchema] + +export type EthRequestSignaturesPublic = RpcSchemaMap +export type EthRequestSignaturesWallet = RpcSchemaMap +export type EthRequestSignaturesFull = RpcSchemaMap + +// TODO : replace EthRequestSignatures by EthRequestSignaturesViem export interface EthRequestSignatures { eth_requestAccounts: [null, InjectedAccount[]] eth_gasPrice: [null, string] @@ -130,8 +151,8 @@ export type EthRequestSignArguments = EthRequestArguments< > export interface AnyEthRequest { - readonly method: EthRequestTypes - readonly params: EthRequestSignatures[EthRequestTypes][0] + readonly method: string + readonly params?: readonly unknown[] | object } export interface EthProviderMessage { diff --git a/apps/extension/src/ui/api/types.ts b/apps/extension/src/ui/api/types.ts index 08d090f9f4..395062dd21 100644 --- a/apps/extension/src/ui/api/types.ts +++ b/apps/extension/src/ui/api/types.ts @@ -58,7 +58,6 @@ import { ResponseAssetTransferFeeQuery, } from "@core/domains/transfers/types" import { MetadataDef } from "@core/inject/types" -import { EthResponseType } from "@core/injectEth/types" import { ValidRequests } from "@core/libs/requests/types" import { UnsubscribeFn } from "@core/types" import { AddressesByChain } from "@core/types/base" @@ -279,7 +278,7 @@ export default interface MessageTypes { signedTransaction: HexString ) => Promise ethCancelSign: (id: SigningRequestID<"eth-sign" | "eth-send">) => Promise - ethRequest: (request: T) => Promise> + ethRequest: (request: AnyEthRequestChainId) => Promise ethGetTransactionsCount: (address: `0x${string}`, evmNetworkId: EvmNetworkId) => Promise ethNetworkAddGetRequests: () => Promise ethNetworkAddApprove: (id: AddEthereumChainRequestId) => Promise diff --git a/apps/extension/src/ui/domains/Ethereum/getExtensionEthereumProvider.ts b/apps/extension/src/ui/domains/Ethereum/getExtensionEthereumProvider.ts index b996965c46..94ed97abaa 100644 --- a/apps/extension/src/ui/domains/Ethereum/getExtensionEthereumProvider.ts +++ b/apps/extension/src/ui/domains/Ethereum/getExtensionEthereumProvider.ts @@ -1,8 +1,3 @@ -// import { -// ETH_ERROR_EIP1474_INTERNAL_ERROR, -// EthProviderRpcError, -// } from "@core/injectEth/EthProviderRpcError" -import { EthRequestSignatures, EthRequestTypes } from "@core/injectEth/types" import { log } from "@core/log" import { EvmNetworkId } from "@talismn/chaindata-provider" import { api } from "@ui/api" @@ -15,12 +10,7 @@ const ethereumRequest = (chainId: EvmNetworkId): ViemRequest => async (method: string, params?: unknown[]) => { try { - // TODO cleanup typings - return await api.ethRequest({ - chainId, - method: method as keyof EthRequestSignatures, - params: params as EthRequestSignatures[EthRequestTypes][0], - }) + return await api.ethRequest({ chainId, method, params }) } catch (err) { log.error("[provider.request] error on %s", method, { err }) throw err From da39b0b6fc6e4e5e41bb84498075dfb88bf214ba Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Fri, 27 Oct 2023 13:43:16 +0900 Subject: [PATCH 11/50] wip: adjust types --- .../src/core/domains/ethereum/handler.tabs.ts | 18 +- .../src/core/domains/ethereum/types.ts | 162 +++++++++++++++- apps/extension/src/core/injectEth/types.ts | 177 ++---------------- 3 files changed, 182 insertions(+), 175 deletions(-) diff --git a/apps/extension/src/core/domains/ethereum/handler.tabs.ts b/apps/extension/src/core/domains/ethereum/handler.tabs.ts index b7dd2eafac..1d77ffa7af 100644 --- a/apps/extension/src/core/domains/ethereum/handler.tabs.ts +++ b/apps/extension/src/core/domains/ethereum/handler.tabs.ts @@ -20,12 +20,7 @@ import { ETH_ERROR_UNKNOWN_CHAIN_NOT_CONFIGURED, EthProviderRpcError, } from "@core/injectEth/EthProviderRpcError" -import { - AnyEthRequest, - EthProviderMessage, - EthRequestArguments, - EthRequestSignArguments, -} from "@core/injectEth/types" +import { AnyEthRequest } from "@core/injectEth/types" import { TabsHandler } from "@core/libs/Handler" import { log } from "@core/log" import { chainConnectorEvm } from "@core/rpcs/chain-connector-evm" @@ -56,7 +51,14 @@ import { sanitizeWatchAssetRequestParam, } from "./helpers" import { requestAddNetwork, requestWatchAsset } from "./requests" -import { Web3WalletPermission, Web3WalletPermissionTarget } from "./types" +import { + EthProviderMessage, + EthRequestArguments, + EthRequestArgumentsViem, + EthRequestSignArguments, + Web3WalletPermission, + Web3WalletPermissionTarget, +} from "./types" interface EthAuthorizedSite extends AuthorizedSite { ethChainId: number @@ -755,7 +757,7 @@ export class EthTabsHandler extends TabsHandler { case "personal_ecRecover": { const { params: [message, signature], - } = request as EthRequestArguments<"personal_ecRecover"> + } = request as EthRequestArgumentsViem<"personal_ecRecover"> return recoverMessageAddress({ message, signature }) //return recoverPersonalSignature({ data, signature }) } diff --git a/apps/extension/src/core/domains/ethereum/types.ts b/apps/extension/src/core/domains/ethereum/types.ts index 37524128aa..770710671e 100644 --- a/apps/extension/src/core/domains/ethereum/types.ts +++ b/apps/extension/src/core/domains/ethereum/types.ts @@ -1,11 +1,22 @@ import type { ETH_SEND, ETH_SIGN, KnownSigningRequestIdOnly } from "@core/domains/signing/types" import type { CustomErc20Token } from "@core/domains/tokens/types" -import { AnyEthRequest, EthProviderMessage, EthResponseTypes } from "@core/injectEth/types" +import { AnyEthRequest } from "@core/injectEth/types" import { BaseRequest, BaseRequestId, RequestIdOnly } from "@core/types/base" +import { BlockWithTransactions } from "@ethersproject/abstract-provider" +import type { + Block, + BlockTag, + TransactionReceipt, + TransactionRequest, + TransactionResponse, +} from "@ethersproject/providers" +// Compliant with https://eips.ethereum.org/EIPS/eip-1193 +import type { InjectedAccount } from "@polkadot/extension-inject/types" import { HexString } from "@polkadot/util/types" import { EvmNetworkId } from "@talismn/chaindata-provider" import { BigNumberish, ethers } from "ethers" import type { Address as EvmAddress } from "viem" +import { PublicRpcSchema, RpcSchema, WalletRpcSchema } from "viem" import { WalletTransactionTransferInfo } from "../transactions" @@ -19,6 +30,155 @@ export type { EthereumRpc, } from "@talismn/chaindata-provider" +type Promisify = T | Promise + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type PromisifyArray> = { + /* The inbuilt ethers provider methods take arguments which can + * be a value or the promise of a value, this utility type converts a normal + * object type into one where all the values may be promises + */ + [K in keyof T]: Promisify +} + +export type EthRequestGetBalance = PromisifyArray<[string, BlockTag]> + +export type EthRequestGetStorage = PromisifyArray<[string, BigNumberish, BlockTag]> + +export type EthRequestGetTxCount = PromisifyArray<[string, BlockTag]> + +export type EthRequestBlockTagOnly = PromisifyArray<[BlockTag]> + +export type EthRequestSendRawTx = PromisifyArray<[string]> + +export type EthRequestCall = [TransactionRequest, Promise] + +export type EthRequestEstimateGas = [TransactionRequest, string] + +export type EthRequestGetBlock = PromisifyArray<[BlockTag, boolean]> + +export type EthRequestTxHashOnly = PromisifyArray<[string]> + +export type EthRequestSign = [string, string] +export type EthRequestRecoverAddress = [string, `0x${string}`] + +export type EthRequestSendTx = [TransactionRequest] + +export type EthRequestAddEthereumChain = [AddEthereumChainParameter] + +export type EthRequestSwitchEthereumChain = [{ chainId: string }] + +type RpcSchemaMap = { + [K in TRpcSchema[number]["Method"]]: [ + Extract["Parameters"], + Extract["ReturnType"] + ] +} + +// type RpcSchemaMapBetter = { +// [K in TRpcSchema[number]["Method"]]: { +// parameters: Extract["Parameters"] +// returnType: Extract["ReturnType"] +// } +// } + +// define here the rpc methods that do not exist in viem or need to be overriden +type TalismanRpcSchema = [ + { + Method: "personal_ecRecover" + Parameters: [signedData: `0x${string}`, signature: `0x${string}`] + ReturnType: EvmAddress + } +] + +export type FullRpcSchema = [...PublicRpcSchema, ...WalletRpcSchema, ...TalismanRpcSchema] + +export type EthRequestSignaturesPublic = RpcSchemaMap +export type EthRequestSignaturesWallet = RpcSchemaMap +export type EthRequestSignaturesFull = RpcSchemaMap + +// TODO : replace EthRequestSignatures by EthRequestSignaturesViem +export interface EthRequestSignatures { + eth_requestAccounts: [null, InjectedAccount[]] + eth_gasPrice: [null, string] + eth_accounts: [null, string] + eth_blockNumber: [null, number] + eth_chainId: [null, string] + eth_coinbase: [null, string] + net_version: [null, string] + eth_getBalance: [EthRequestGetBalance, string] + eth_getStorageAt: [EthRequestGetStorage, string] + eth_getTransactionCount: [EthRequestGetTxCount, string] + eth_getBlockTransactionCountByHash: [EthRequestBlockTagOnly, string] + eth_getBlockTransactionCountByNumber: [EthRequestBlockTagOnly, string] + eth_getCode: [EthRequestBlockTagOnly, Block] + eth_sendRawTransaction: [EthRequestSendRawTx, TransactionResponse] + eth_call: [EthRequestCall, string] + estimateGas: [EthRequestEstimateGas, string] + eth_getBlockByHash: [EthRequestGetBlock, Block | BlockWithTransactions] + eth_getBlockByNumber: [EthRequestGetBlock, Block | BlockWithTransactions] + eth_getTransactionByHash: [EthRequestTxHashOnly, TransactionResponse] + eth_getTransactionReceipt: [EthRequestTxHashOnly, TransactionReceipt] + personal_sign: [EthRequestSign, string] + eth_signTypedData: [EthRequestSign, string] + eth_signTypedData_v1: [EthRequestSign, string] + eth_signTypedData_v3: [EthRequestSign, string] + eth_signTypedData_v4: [EthRequestSign, string] + eth_sendTransaction: [EthRequestSendTx, string] + personal_ecRecover: [EthRequestRecoverAddress, string] + + // EIP 747 https://github.com/ethereum/EIPs/blob/master/EIPS/eip-747.md + wallet_watchAsset: [WatchAssetBase, string] + + // pending EIP https://eips.ethereum.org/EIPS/eip-3085, defined by metamask to let dapp add chains. + // returns `null` if the request was successful, otherwise throws an error. + // metamask will automatically reject this when: + // - the rpc endpoint doesn't respond to rpc calls + // - the rpc endpoint returns a different chain id than the one specified + // - the chain id corresponds to any of the default metamask chains + wallet_addEthereumChain: [EthRequestAddEthereumChain, null] + + // pending EIP https://eips.ethereum.org/EIPS/eip-3326, defined by metamask to let dapp change chain. + // returns `null` if the request was successful, otherwise throws an error. + // if the `error.code` is `4902` then the dapp is more likely to call `wallet_addEthereumChain`. + // metamask will automatically reject this when: + // - the chain id is malformed + // - the chain with the specified id has not been added to metamask + wallet_switchEthereumChain: [EthRequestSwitchEthereumChain, null] + + // https://docs.metamask.io/guide/rpc-api.html#wallet-getpermissions + wallet_getPermissions: [null, Web3WalletPermission[]] + + // https://docs.metamask.io/guide/rpc-api.html#wallet-requestpermissions + wallet_requestPermissions: [[RequestedPermissions], Web3WalletPermission[]] +} + +export type EthRequestTypes = keyof EthRequestSignatures +export type EthResponseTypes = EthRequestSignatures[keyof EthRequestSignatures][1] +export type EthResponseType = EthRequestSignatures[T][1] +export type EthRequestParams = EthRequestSignatures[keyof EthRequestSignatures][0] +export interface EthRequestArguments { + readonly method: T + readonly params: EthRequestSignatures[T][0] +} + +export interface EthRequestArgumentsViem { + readonly method: T + readonly params: EthRequestSignaturesFull[T][0] +} + +export type EthRequestSignArguments = EthRequestArguments< + | "personal_sign" + | "eth_signTypedData" + | "eth_signTypedData_v1" + | "eth_signTypedData_v3" + | "eth_signTypedData_v4" +> + +export interface EthProviderMessage { + readonly type: string + readonly data: unknown +} export type AddEthereumChainParameter = { /** A 0x-prefixed hexadecimal string */ chainId: string diff --git a/apps/extension/src/core/injectEth/types.ts b/apps/extension/src/core/injectEth/types.ts index 1d8e8ee095..9e45cb008f 100644 --- a/apps/extension/src/core/injectEth/types.ts +++ b/apps/extension/src/core/injectEth/types.ts @@ -1,181 +1,26 @@ import EventEmitter from "events" -import type { - AddEthereumChainParameter, - RequestedPermissions, - Web3WalletPermission, -} from "@core/domains/ethereum/types" -import type { WatchAssetBase } from "@core/domains/ethereum/types" -import { BlockWithTransactions } from "@ethersproject/abstract-provider" -import { BigNumberish } from "@ethersproject/bignumber" -import type { - Block, - BlockTag, - TransactionReceipt, - TransactionRequest, - TransactionResponse, -} from "@ethersproject/providers" -// Compliant with https://eips.ethereum.org/EIPS/eip-1193 -import type { InjectedAccount } from "@polkadot/extension-inject/types" -import { PublicRpcSchema, RpcSchema, WalletRpcSchema } from "viem" +// export type EthSubscriptionId = string -type Promisify = T | Promise - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type PromisifyArray> = { - /* The inbuilt ethers provider methods take arguments which can - * be a value or the promise of a value, this utility type converts a normal - * object type into one where all the values may be promises - */ - [K in keyof T]: Promisify -} - -export type EthRequestGetBalance = PromisifyArray<[string, BlockTag]> - -export type EthRequestGetStorage = PromisifyArray<[string, BigNumberish, BlockTag]> - -export type EthRequestGetTxCount = PromisifyArray<[string, BlockTag]> - -export type EthRequestBlockTagOnly = PromisifyArray<[BlockTag]> - -export type EthRequestSendRawTx = PromisifyArray<[string]> - -export type EthRequestCall = [TransactionRequest, Promise] - -export type EthRequestEstimateGas = [TransactionRequest, string] - -export type EthRequestGetBlock = PromisifyArray<[BlockTag, boolean]> - -export type EthRequestTxHashOnly = PromisifyArray<[string]> - -export type EthRequestSign = [string, string] -export type EthRequestRecoverAddress = [string, `0x${string}`] - -export type EthRequestSendTx = [TransactionRequest] - -export type EthRequestAddEthereumChain = [AddEthereumChainParameter] - -export type EthRequestSwitchEthereumChain = [{ chainId: string }] - -type RpcSchemaMap = { - [K in TRpcSchema[number]["Method"]]: [ - Extract["Parameters"], - Extract["ReturnType"] - ] -} - -// type RpcSchemaMapBetter = { -// [K in TRpcSchema[number]["Method"]]: { -// parameters: Extract["Parameters"] -// returnType: Extract["ReturnType"] -// } +// export interface EthSubscriptionData { +// readonly subscription: EthSubscriptionId +// readonly result: unknown // } -export type FullRpcSchema = [...PublicRpcSchema, ...WalletRpcSchema] - -export type EthRequestSignaturesPublic = RpcSchemaMap -export type EthRequestSignaturesWallet = RpcSchemaMap -export type EthRequestSignaturesFull = RpcSchemaMap - -// TODO : replace EthRequestSignatures by EthRequestSignaturesViem -export interface EthRequestSignatures { - eth_requestAccounts: [null, InjectedAccount[]] - eth_gasPrice: [null, string] - eth_accounts: [null, string] - eth_blockNumber: [null, number] - eth_chainId: [null, string] - eth_coinbase: [null, string] - net_version: [null, string] - eth_getBalance: [EthRequestGetBalance, string] - eth_getStorageAt: [EthRequestGetStorage, string] - eth_getTransactionCount: [EthRequestGetTxCount, string] - eth_getBlockTransactionCountByHash: [EthRequestBlockTagOnly, string] - eth_getBlockTransactionCountByNumber: [EthRequestBlockTagOnly, string] - eth_getCode: [EthRequestBlockTagOnly, Block] - eth_sendRawTransaction: [EthRequestSendRawTx, TransactionResponse] - eth_call: [EthRequestCall, string] - estimateGas: [EthRequestEstimateGas, string] - eth_getBlockByHash: [EthRequestGetBlock, Block | BlockWithTransactions] - eth_getBlockByNumber: [EthRequestGetBlock, Block | BlockWithTransactions] - eth_getTransactionByHash: [EthRequestTxHashOnly, TransactionResponse] - eth_getTransactionReceipt: [EthRequestTxHashOnly, TransactionReceipt] - personal_sign: [EthRequestSign, string] - eth_signTypedData: [EthRequestSign, string] - eth_signTypedData_v1: [EthRequestSign, string] - eth_signTypedData_v3: [EthRequestSign, string] - eth_signTypedData_v4: [EthRequestSign, string] - eth_sendTransaction: [EthRequestSendTx, string] - personal_ecRecover: [EthRequestRecoverAddress, string] - - // EIP 747 https://github.com/ethereum/EIPs/blob/master/EIPS/eip-747.md - wallet_watchAsset: [WatchAssetBase, string] - - // pending EIP https://eips.ethereum.org/EIPS/eip-3085, defined by metamask to let dapp add chains. - // returns `null` if the request was successful, otherwise throws an error. - // metamask will automatically reject this when: - // - the rpc endpoint doesn't respond to rpc calls - // - the rpc endpoint returns a different chain id than the one specified - // - the chain id corresponds to any of the default metamask chains - wallet_addEthereumChain: [EthRequestAddEthereumChain, null] - - // pending EIP https://eips.ethereum.org/EIPS/eip-3326, defined by metamask to let dapp change chain. - // returns `null` if the request was successful, otherwise throws an error. - // if the `error.code` is `4902` then the dapp is more likely to call `wallet_addEthereumChain`. - // metamask will automatically reject this when: - // - the chain id is malformed - // - the chain with the specified id has not been added to metamask - wallet_switchEthereumChain: [EthRequestSwitchEthereumChain, null] - - // https://docs.metamask.io/guide/rpc-api.html#wallet-getpermissions - wallet_getPermissions: [null, Web3WalletPermission[]] - - // https://docs.metamask.io/guide/rpc-api.html#wallet-requestpermissions - wallet_requestPermissions: [[RequestedPermissions], Web3WalletPermission[]] -} - -export type EthRequestTypes = keyof EthRequestSignatures -export type EthResponseTypes = EthRequestSignatures[keyof EthRequestSignatures][1] -export type EthResponseType = EthRequestSignatures[T][1] -export type EthRequestParams = EthRequestSignatures[keyof EthRequestSignatures][0] -export interface EthRequestArguments { - readonly method: T - readonly params: EthRequestSignatures[T][0] -} +// export interface EthSubscriptionMessage extends EthProviderMessage { +// readonly type: "eth_subscription" +// readonly data: EthSubscriptionData +// } -export type EthRequestSignArguments = EthRequestArguments< - | "personal_sign" - | "eth_signTypedData" - | "eth_signTypedData_v1" - | "eth_signTypedData_v3" - | "eth_signTypedData_v4" -> +// export interface ProviderConnectInfo { +// readonly chainId: string +// } export interface AnyEthRequest { readonly method: string readonly params?: readonly unknown[] | object } -export interface EthProviderMessage { - readonly type: string - readonly data: unknown -} - -export type EthSubscriptionId = string - -export interface EthSubscriptionData { - readonly subscription: EthSubscriptionId - readonly result: unknown -} - -export interface EthSubscriptionMessage extends EthProviderMessage { - readonly type: "eth_subscription" - readonly data: EthSubscriptionData -} - -export interface ProviderConnectInfo { - readonly chainId: string -} - export interface EthProvider extends EventEmitter { request(args: AnyEthRequest): Promise } From 59839dfff8b396d31d664d651f4aa8375792acb5 Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Fri, 27 Oct 2023 15:27:40 +0900 Subject: [PATCH 12/50] wip: requests typings --- .../src/core/domains/ethereum/handler.tabs.ts | 28 ++++++----- .../src/core/domains/ethereum/requests.ts | 2 +- .../src/core/domains/ethereum/types.ts | 48 +++++++++++-------- .../src/core/util/getEthTransactionInfo.ts | 4 +- .../src/core/util/isContractAddress.ts | 21 +++++++- .../domains/Ethereum/NetworkDetailsButton.tsx | 12 ++--- 6 files changed, 73 insertions(+), 42 deletions(-) diff --git a/apps/extension/src/core/domains/ethereum/handler.tabs.ts b/apps/extension/src/core/domains/ethereum/handler.tabs.ts index 1d77ffa7af..4dc10fdccc 100644 --- a/apps/extension/src/core/domains/ethereum/handler.tabs.ts +++ b/apps/extension/src/core/domains/ethereum/handler.tabs.ts @@ -55,6 +55,7 @@ import { EthProviderMessage, EthRequestArguments, EthRequestArgumentsViem, + EthRequestResultViem, EthRequestSignArguments, Web3WalletPermission, Web3WalletPermissionTarget, @@ -286,9 +287,9 @@ export class EthTabsHandler extends TabsHandler { private addEthereumChain = async ( url: string, - request: EthRequestArguments<"wallet_addEthereumChain">, + request: EthRequestArgumentsViem<"wallet_addEthereumChain">, port: Port - ) => { + ): Promise> => { const { params: [network], } = request @@ -347,8 +348,8 @@ export class EthTabsHandler extends TabsHandler { private switchEthereumChain = async ( url: string, - request: EthRequestArguments<"wallet_switchEthereumChain"> - ) => { + request: EthRequestArgumentsViem<"wallet_switchEthereumChain"> + ): Promise> => { const { params: [{ chainId: hexChainId }], } = request @@ -457,9 +458,9 @@ export class EthTabsHandler extends TabsHandler { private addWatchAssetRequest = async ( url: string, - request: EthRequestArguments<"wallet_watchAsset">, + request: EthRequestArgumentsViem<"wallet_watchAsset">, port: Port - ) => { + ): Promise> => { if (!isValidWatchAssetRequestParam(request.params)) throw new EthProviderRpcError("Invalid parameter", ETH_ERROR_EIP1474_INVALID_PARAMS) @@ -553,7 +554,7 @@ export class EthTabsHandler extends TabsHandler { ) { const { params: [txRequest], - } = request as EthRequestArguments<"eth_sendTransaction"> + } = request const site = await this.getSiteDetails(url, txRequest.from) @@ -622,9 +623,9 @@ export class EthTabsHandler extends TabsHandler { private async requestPermissions( url: string, - request: EthRequestArguments<"wallet_requestPermissions">, + request: EthRequestArgumentsViem<"wallet_requestPermissions">, port: Port - ): Promise { + ): Promise> { if (request.params.length !== 1) throw new EthProviderRpcError( "This method expects an array with only 1 entry", @@ -697,6 +698,7 @@ export class EthTabsHandler extends TabsHandler { ) await this.checkAccountAuthorised(url) + // TODO typecheck return types against EthRequestArgumentsViem / EthRequestResultsViem switch (request.method) { case "eth_requestAccounts": await this.requestPermissions( @@ -773,7 +775,7 @@ export class EthTabsHandler extends TabsHandler { //auth-less test dapp : rsksmart.github.io/metamask-rsk-custom-network/ return this.addWatchAssetRequest( url, - request as EthRequestArguments<"wallet_watchAsset">, + request as EthRequestArgumentsViem<"wallet_watchAsset">, port ) @@ -781,7 +783,7 @@ export class EthTabsHandler extends TabsHandler { //auth-less test dapp : rsksmart.github.io/metamask-rsk-custom-network/ return this.addEthereumChain( url, - request as EthRequestArguments<"wallet_addEthereumChain">, + request as EthRequestArgumentsViem<"wallet_addEthereumChain">, port ) @@ -789,7 +791,7 @@ export class EthTabsHandler extends TabsHandler { //auth-less test dapp : rsksmart.github.io/metamask-rsk-custom-network/ return this.switchEthereumChain( url, - request as EthRequestArguments<"wallet_switchEthereumChain"> + request as EthRequestArgumentsViem<"wallet_switchEthereumChain"> ) // https://docs.metamask.io/guide/rpc-api.html#wallet-getpermissions @@ -800,7 +802,7 @@ export class EthTabsHandler extends TabsHandler { case "wallet_requestPermissions": return this.requestPermissions( url, - request as EthRequestArguments<"wallet_requestPermissions">, + request as EthRequestArgumentsViem<"wallet_requestPermissions">, port ) diff --git a/apps/extension/src/core/domains/ethereum/requests.ts b/apps/extension/src/core/domains/ethereum/requests.ts index 2bf8e2a2ac..6a2bd855c5 100644 --- a/apps/extension/src/core/domains/ethereum/requests.ts +++ b/apps/extension/src/core/domains/ethereum/requests.ts @@ -1,4 +1,3 @@ -import type { AddEthereumChainParameter } from "@core/domains/ethereum/types" import { ETH_NETWORK_ADD_PREFIX, WATCH_ASSET_PREFIX, @@ -9,6 +8,7 @@ import type { CustomErc20Token } from "@core/domains/tokens/types" import { requestStore } from "@core/libs/requests/store" import type { Port } from "@core/types/base" import { urlToDomain } from "@core/util/urlToDomain" +import { AddEthereumChainParameter } from "viem" class AddNetworkError extends Error {} diff --git a/apps/extension/src/core/domains/ethereum/types.ts b/apps/extension/src/core/domains/ethereum/types.ts index 770710671e..ca912a97c1 100644 --- a/apps/extension/src/core/domains/ethereum/types.ts +++ b/apps/extension/src/core/domains/ethereum/types.ts @@ -15,7 +15,7 @@ import type { InjectedAccount } from "@polkadot/extension-inject/types" import { HexString } from "@polkadot/util/types" import { EvmNetworkId } from "@talismn/chaindata-provider" import { BigNumberish, ethers } from "ethers" -import type { Address as EvmAddress } from "viem" +import type { AddEthereumChainParameter, Address as EvmAddress } from "viem" import { PublicRpcSchema, RpcSchema, WalletRpcSchema } from "viem" import { WalletTransactionTransferInfo } from "../transactions" @@ -88,13 +88,20 @@ type TalismanRpcSchema = [ Method: "personal_ecRecover" Parameters: [signedData: `0x${string}`, signature: `0x${string}`] ReturnType: EvmAddress + }, + { + // TODO see if we can remove this + // override for now because of result type mismatch + Method: "wallet_requestPermissions" + Parameters: [permissions: { eth_accounts: Record }] + ReturnType: Web3WalletPermission[] } ] export type FullRpcSchema = [...PublicRpcSchema, ...WalletRpcSchema, ...TalismanRpcSchema] -export type EthRequestSignaturesPublic = RpcSchemaMap -export type EthRequestSignaturesWallet = RpcSchemaMap +// export type EthRequestSignaturesPublic = RpcSchemaMap +// export type EthRequestSignaturesWallet = RpcSchemaMap export type EthRequestSignaturesFull = RpcSchemaMap // TODO : replace EthRequestSignatures by EthRequestSignaturesViem @@ -162,11 +169,14 @@ export interface EthRequestArguments { readonly params: EthRequestSignatures[T][0] } -export interface EthRequestArgumentsViem { +export type EthRequestArgumentsViem = { readonly method: T readonly params: EthRequestSignaturesFull[T][0] } +export type EthRequestResultViem = + EthRequestSignaturesFull[T][1] + export type EthRequestSignArguments = EthRequestArguments< | "personal_sign" | "eth_signTypedData" @@ -179,21 +189,21 @@ export interface EthProviderMessage { readonly type: string readonly data: unknown } -export type AddEthereumChainParameter = { - /** A 0x-prefixed hexadecimal string */ - chainId: string - chainName: string - nativeCurrency: { - name: string - /** 2-6 characters long */ - symbol: string - decimals: 18 - } - rpcUrls: string[] - blockExplorerUrls?: string[] - /** Currently ignored by metamask */ - iconUrls?: string[] -} +// export type AddEthereumChainParameter = { +// /** A 0x-prefixed hexadecimal string */ +// chainId: string +// chainName: string +// nativeCurrency: { +// name: string +// /** 2-6 characters long */ +// symbol: string +// decimals: 18 +// } +// rpcUrls: string[] +// blockExplorerUrls?: string[] +// /** Currently ignored by metamask */ +// iconUrls?: string[] +// } export type EthTxSignAndSend = { unsigned: ethers.providers.TransactionRequest diff --git a/apps/extension/src/core/util/getEthTransactionInfo.ts b/apps/extension/src/core/util/getEthTransactionInfo.ts index 22d6d64bfc..49ece06a6d 100644 --- a/apps/extension/src/core/util/getEthTransactionInfo.ts +++ b/apps/extension/src/core/util/getEthTransactionInfo.ts @@ -5,7 +5,7 @@ import { BigNumber, ethers } from "ethers" import { abiErc1155, abiErc20, abiErc721, abiErc721Metadata, abiMoonStaking } from "./abi" import { abiMoonConvictionVoting } from "./abi/abiMoonConvictionVoting" import { abiMoonXTokens } from "./abi/abiMoonXTokens" -import { isContractAddress } from "./isContractAddress" +import { isContractAddressOld } from "./isContractAddress" export type ContractType = | "ERC20" @@ -75,7 +75,7 @@ export const getEthTransactionInfo = async ( // transactions that provision a contract have an empty 'to' field const targetAddress = tx.to ? ethers.utils.getAddress(tx.to) : undefined - const isContractCall = targetAddress ? await isContractAddress(provider, targetAddress) : false + const isContractCall = targetAddress ? await isContractAddressOld(provider, targetAddress) : false const result: TransactionInfo = { targetAddress, diff --git a/apps/extension/src/core/util/isContractAddress.ts b/apps/extension/src/core/util/isContractAddress.ts index 10384df8dd..1145947f87 100644 --- a/apps/extension/src/core/util/isContractAddress.ts +++ b/apps/extension/src/core/util/isContractAddress.ts @@ -1,6 +1,12 @@ +import { EvmAddress } from "@core/domains/ethereum/types" import { ethers } from "ethers" +import { PublicClient } from "viem" -export const isContractAddress = async (provider: ethers.providers.Provider, address: string) => { +/** @deprecated */ +export const isContractAddressOld = async ( + provider: ethers.providers.Provider, + address: string +) => { try { const code = await provider.getCode(address) return code !== "0x" @@ -9,3 +15,16 @@ export const isContractAddress = async (provider: ethers.providers.Provider, add return false } } + +export const isContractAddress = async (client: PublicClient, address: EvmAddress) => { + try { + const code = await client.getBytecode({ address }) + // TODO remove log + // eslint-disable-next-line no-console + console.log("isContractAddress remove after checking", { code }) + return code !== "0x" + } catch (error) { + // not a contract + return false + } +} diff --git a/apps/extension/src/ui/domains/Ethereum/NetworkDetailsButton.tsx b/apps/extension/src/ui/domains/Ethereum/NetworkDetailsButton.tsx index dd93999a94..d59129d091 100644 --- a/apps/extension/src/ui/domains/Ethereum/NetworkDetailsButton.tsx +++ b/apps/extension/src/ui/domains/Ethereum/NetworkDetailsButton.tsx @@ -1,8 +1,8 @@ -import { AddEthereumChainParameter } from "@core/domains/ethereum/types" import { useOpenClose } from "@talisman/hooks/useOpenClose" import { FC, useCallback, useMemo } from "react" import { useTranslation } from "react-i18next" import { Button, Drawer, PillButton } from "talisman-ui" +import { AddEthereumChainParameter } from "viem" import { ViewDetailsField } from "../Sign/ViewDetails/ViewDetailsField" @@ -25,11 +25,11 @@ export const NetworksDetailsButton: FC<{ const { name, rpcs, chainId, tokenSymbol, blockExplorers } = useMemo(() => { return { - name: network?.chainName || "N/A", - rpcs: network?.rpcUrls?.join("\n") || "N/A", - chainId: tryParseIntFromHex(network?.chainId), - tokenSymbol: network?.nativeCurrency?.symbol || "N/A", - blockExplorers: network?.blockExplorerUrls?.join("\n"), + name: network.chainName || "N/A", + rpcs: network.rpcUrls?.join("\n") || "N/A", + chainId: tryParseIntFromHex(network.chainId), + tokenSymbol: network.nativeCurrency?.symbol || "N/A", + blockExplorers: network.blockExplorerUrls?.join("\n"), } }, [network, tryParseIntFromHex]) From 97d7fae429727570af3427eb2f07028939d53b6e Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Fri, 27 Oct 2023 18:58:59 +0900 Subject: [PATCH 13/50] wip: frontend with viem --- .../src/core/domains/ethereum/handler.tabs.ts | 30 ++- apps/extension/src/core/util/abi/abiErc20.ts | 230 +----------------- apps/extension/src/core/util/abi/abiErc721.ts | 4 + .../src/core/util/getErc20ContractData.ts | 10 +- .../src/core/util/getErc20TokenInfo.ts | 28 +-- .../src/core/util/getEthTransactionInfo.ts | 81 +++--- .../src/core/util/isContractAddress.ts | 3 - .../Ethereum/getExtensionEthereumProvider.ts | 27 +- .../ui/domains/Ethereum/useEthTransaction.ts | 16 +- .../Sign/Ethereum/EthSignBodyDefault.tsx | 2 +- 10 files changed, 129 insertions(+), 302 deletions(-) diff --git a/apps/extension/src/core/domains/ethereum/handler.tabs.ts b/apps/extension/src/core/domains/ethereum/handler.tabs.ts index 4dc10fdccc..0d97e8b3a8 100644 --- a/apps/extension/src/core/domains/ethereum/handler.tabs.ts +++ b/apps/extension/src/core/domains/ethereum/handler.tabs.ts @@ -10,6 +10,7 @@ import { import { CustomErc20Token } from "@core/domains/tokens/types" import i18next from "@core/i18nConfig" import { + ETH_ERROR_EIP1474_INTERNAL_ERROR, ETH_ERROR_EIP1474_INVALID_INPUT, ETH_ERROR_EIP1474_INVALID_PARAMS, ETH_ERROR_EIP1474_RESOURCE_UNAVAILABLE, @@ -35,7 +36,15 @@ import { isEthereumAddress } from "@polkadot/util-crypto" import { convertAddress } from "@talisman/util/convertAddress" import { githubUnknownTokenLogoUrl } from "@talismn/chaindata-provider" import { throwAfter } from "@talismn/util" -import { PublicClient, createClient, getAddress, http, recoverMessageAddress, toHex } from "viem" +import { + PublicClient, + RpcError, + createClient, + getAddress, + http, + recoverMessageAddress, + toHex, +} from "viem" import { hexToNumber } from "viem/utils" import { getErc20TokenInfo } from "../../util/getErc20TokenInfo" @@ -811,7 +820,7 @@ export class EthTabsHandler extends TabsHandler { } } - handle( + async handle( id: string, type: TMessageType, request: RequestTypes[TMessageType], @@ -822,9 +831,20 @@ export class EthTabsHandler extends TabsHandler { case "pub(eth.subscribe)": return this.ethSubscribe(id, url, port) - case "pub(eth.request)": - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return this.ethRequest(id, url, request as AnyEthRequest, port) as any + case "pub(eth.request)": { + try { + return await this.ethRequest(id, url, request as AnyEthRequest, port) + } catch (err) { + if (err instanceof EthProviderRpcError) throw err + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const { code, shortMessage, message } = err as RpcError + throw new EthProviderRpcError( + shortMessage ?? message ?? "Internal error", + code ?? ETH_ERROR_EIP1474_INTERNAL_ERROR, + shortMessage ? message : undefined + ) + } + } default: throw new Error(`Unable to handle message of type ${type}`) diff --git a/apps/extension/src/core/util/abi/abiErc20.ts b/apps/extension/src/core/util/abi/abiErc20.ts index f912c6e38a..1324f12799 100644 --- a/apps/extension/src/core/util/abi/abiErc20.ts +++ b/apps/extension/src/core/util/abi/abiErc20.ts @@ -1,222 +1,12 @@ export const abiErc20 = [ - { - type: "event", - name: "Approval", - inputs: [ - { - indexed: true, - name: "owner", - type: "address", - }, - { - indexed: true, - name: "spender", - type: "address", - }, - { - indexed: false, - name: "value", - type: "uint256", - }, - ], - }, - { - type: "event", - name: "Transfer", - inputs: [ - { - indexed: true, - name: "from", - type: "address", - }, - { - indexed: true, - name: "to", - type: "address", - }, - { - indexed: false, - name: "value", - type: "uint256", - }, - ], - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: "address", - name: "owner", - type: "address", - }, - { - indexed: true, - internalType: "address", - name: "operator", - type: "address", - }, - { - indexed: false, - internalType: "bool", - name: "approved", - type: "bool", - }, - ], - name: "ApprovalForAll", - type: "event", - }, - { - type: "function", - name: "allowance", - stateMutability: "view", - inputs: [ - { - name: "owner", - type: "address", - }, - { - name: "spender", - type: "address", - }, - ], - outputs: [ - { - name: "", - type: "uint256", - }, - ], - }, - { - type: "function", - name: "approve", - stateMutability: "nonpayable", - inputs: [ - { - name: "spender", - type: "address", - }, - { - name: "amount", - type: "uint256", - }, - ], - outputs: [ - { - name: "", - type: "bool", - }, - ], - }, - { - type: "function", - name: "balanceOf", - stateMutability: "view", - inputs: [ - { - name: "account", - type: "address", - }, - ], - outputs: [ - { - name: "", - type: "uint256", - }, - ], - }, - { - type: "function", - name: "decimals", - stateMutability: "view", - inputs: [], - outputs: [ - { - name: "", - type: "uint8", - }, - ], - }, - { - type: "function", - name: "name", - stateMutability: "view", - inputs: [], - outputs: [ - { - name: "", - type: "string", - }, - ], - }, - { - type: "function", - name: "symbol", - stateMutability: "view", - inputs: [], - outputs: [ - { - name: "", - type: "string", - }, - ], - }, - { - type: "function", - name: "totalSupply", - stateMutability: "view", - inputs: [], - outputs: [ - { - name: "", - type: "uint256", - }, - ], - }, - { - type: "function", - name: "transfer", - stateMutability: "nonpayable", - inputs: [ - { - name: "recipient", - type: "address", - }, - { - name: "amount", - type: "uint256", - }, - ], - outputs: [ - { - name: "", - type: "bool", - }, - ], - }, - { - type: "function", - name: "transferFrom", - stateMutability: "nonpayable", - inputs: [ - { - name: "sender", - type: "address", - }, - { - name: "recipient", - type: "address", - }, - { - name: "amount", - type: "uint256", - }, - ], - outputs: [ - { - name: "", - type: "bool", - }, - ], - }, + "function balanceOf(address owner) view returns (uint256)", + "function decimals() view returns (uint8)", + "function symbol() view returns (string)", + "function name() view returns (string)", + + "function totalSupply() view returns (uint256)", + "function transfer(address to, uint256 amount) returns (bool)", + "function allowance(address owner, address spender) view returns (uint256)", + "function approve(address spender, uint256 amount) returns (bool)", + "function transferFrom(address from, address to, uint256 amount) returns (bool)", ] as const diff --git a/apps/extension/src/core/util/abi/abiErc721.ts b/apps/extension/src/core/util/abi/abiErc721.ts index 77ac47000d..d5513244e0 100644 --- a/apps/extension/src/core/util/abi/abiErc721.ts +++ b/apps/extension/src/core/util/abi/abiErc721.ts @@ -1,4 +1,8 @@ export const abiErc721 = [ + "function symbol() view returns (string)", + "function name() view returns (string)", + "function tokenURI(uint256 tokenId) view returns (string)", + "function supportsInterface(bytes4 interfaceId) external view returns (bool)", "function balanceOf(address owner) external view returns (uint256)", "function ownerOf(uint256 tokenId) external view returns (address)", diff --git a/apps/extension/src/core/util/getErc20ContractData.ts b/apps/extension/src/core/util/getErc20ContractData.ts index 76a0e7384e..49cefae228 100644 --- a/apps/extension/src/core/util/getErc20ContractData.ts +++ b/apps/extension/src/core/util/getErc20ContractData.ts @@ -1,12 +1,12 @@ import { ethers } from "ethers" -import { Client, getContract } from "viem" - -import { abiErc20 } from "./abi" +import { Client, getContract, parseAbi } from "viem" const ABI_ERC20 = [ "function symbol() view returns (string)", "function decimals() view returns (uint8)", -] +] as const + +const PARSED_ABI_ERC20 = parseAbi(ABI_ERC20) export type Erc20ContractData = { symbol: string @@ -31,7 +31,7 @@ export const getErc20ContractData = async ( ): Promise => { const contract = getContract({ address: contractAddress, - abi: abiErc20, + abi: PARSED_ABI_ERC20, publicClient: client, }) const [symbol, decimals] = await Promise.all([contract.read.symbol(), contract.read.decimals()]) diff --git a/apps/extension/src/core/util/getErc20TokenInfo.ts b/apps/extension/src/core/util/getErc20TokenInfo.ts index 0a23162747..9ed556a224 100644 --- a/apps/extension/src/core/util/getErc20TokenInfo.ts +++ b/apps/extension/src/core/util/getErc20TokenInfo.ts @@ -1,38 +1,14 @@ import { EvmAddress, EvmNetworkId } from "@core/domains/ethereum/types" import { CustomErc20TokenCreate } from "@core/domains/tokens/types" -import { ethers } from "ethers" import { Client } from "viem" import { getCoinGeckoErc20Coin } from "./coingecko/getCoinGeckoErc20Coin" -import { getErc20ContractData, getErc20ContractDataOld } from "./getErc20ContractData" - -/** - * @deprecated use viem - */ -export const getErc20TokenInfoOld = async ( - provider: ethers.providers.JsonRpcProvider, - evmNetworkId: EvmNetworkId, - contractAddress: EvmAddress -): Promise => { - const [{ decimals, symbol }, coinGeckoData] = await Promise.all([ - getErc20ContractDataOld(provider, contractAddress), - getCoinGeckoErc20Coin(evmNetworkId, contractAddress), - ]) - - return { - evmNetworkId, - contractAddress, - decimals, - symbol, - image: coinGeckoData?.image.small, - coingeckoId: coinGeckoData?.id, - } -} +import { getErc20ContractData } from "./getErc20ContractData" export const getErc20TokenInfo = async ( client: Client, evmNetworkId: EvmNetworkId, - contractAddress: `0x${string}` + contractAddress: EvmAddress ): Promise => { const [{ decimals, symbol }, coinGeckoData] = await Promise.all([ getErc20ContractData(client, contractAddress), diff --git a/apps/extension/src/core/util/getEthTransactionInfo.ts b/apps/extension/src/core/util/getEthTransactionInfo.ts index 49ece06a6d..a733fccfcf 100644 --- a/apps/extension/src/core/util/getEthTransactionInfo.ts +++ b/apps/extension/src/core/util/getEthTransactionInfo.ts @@ -1,11 +1,13 @@ +import { EvmAddress } from "@core/domains/ethereum/types" import * as Sentry from "@sentry/browser" import { getContractCallArg } from "@ui/domains/Sign/Ethereum/getContractCallArg" import { BigNumber, ethers } from "ethers" +import { PublicClient, getAddress, getContract, parseAbi } from "viem" -import { abiErc1155, abiErc20, abiErc721, abiErc721Metadata, abiMoonStaking } from "./abi" +import { abiErc1155, abiErc20, abiErc721, abiMoonStaking } from "./abi" import { abiMoonConvictionVoting } from "./abi/abiMoonConvictionVoting" import { abiMoonXTokens } from "./abi/abiMoonXTokens" -import { isContractAddressOld } from "./isContractAddress" +import { isContractAddress } from "./isContractAddress" export type ContractType = | "ERC20" @@ -17,7 +19,7 @@ export type ContractType = | "unknown" const MOON_CHAIN_PRECOMPILE_ADDRESSES: Record< - string, + EvmAddress, { contractType: ContractType; abi: unknown } > = { "0x0000000000000000000000000000000000000800": { @@ -51,10 +53,19 @@ const knownContracts: { contractType: ContractType; abi: any }[] = [ }, ] +const KNOWN_ABI = { + ERC20: parseAbi(abiErc20), + ERC721: parseAbi(abiErc721), + ERC1155: parseAbi(abiErc1155), + MoonStaking: abiMoonStaking, + MoonConvictionVoting: abiMoonConvictionVoting, + MoonXTokens: abiMoonXTokens, +} + export type TransactionInfo = { - targetAddress?: string + targetAddress?: EvmAddress isContractCall: boolean - value?: BigNumber + value?: bigint contractType?: ContractType contractCall?: ethers.utils.TransactionDescription asset?: { @@ -62,37 +73,39 @@ export type TransactionInfo = { symbol: string decimals: number image?: string - tokenId?: BigNumber + tokenId?: bigint tokenURI?: string } } export type KnownTransactionInfo = Required export const getEthTransactionInfo = async ( - provider: ethers.providers.Provider, + publicClient: PublicClient, tx: ethers.providers.TransactionRequest ): Promise => { // transactions that provision a contract have an empty 'to' field - const targetAddress = tx.to ? ethers.utils.getAddress(tx.to) : undefined + const targetAddress = tx.to ? getAddress(tx.to) : undefined - const isContractCall = targetAddress ? await isContractAddressOld(provider, targetAddress) : false + const isContractCall = targetAddress + ? await isContractAddress(publicClient, targetAddress) + : false const result: TransactionInfo = { targetAddress, isContractCall, contractType: isContractCall ? "unknown" : undefined, - value: tx.value ? BigNumber.from(tx.value) : undefined, + value: tx.value ? BigNumber.from(tx.value).toBigInt() : undefined, } // moon chains precompiles if ( tx.data && - tx.to && + targetAddress && tx.chainId && [1284, 1285, 1287].includes(tx.chainId) && - !!MOON_CHAIN_PRECOMPILE_ADDRESSES[tx.to] + !!MOON_CHAIN_PRECOMPILE_ADDRESSES[targetAddress] ) { - const { contractType, abi } = MOON_CHAIN_PRECOMPILE_ADDRESSES[tx.to] + const { contractType, abi } = MOON_CHAIN_PRECOMPILE_ADDRESSES[targetAddress] try { const data = ethers.utils.hexlify(tx.data) @@ -116,38 +129,46 @@ export const getEthTransactionInfo = async ( try { const contractInterface = new ethers.utils.Interface(abi) + // TODO find a way to parse method arguments without ethers // error will be thrown here if contract doesn't match the abi const contractCall = contractInterface.parseTransaction({ data, value: tx.value }) result.contractType = contractType result.contractCall = contractCall if (contractType === "ERC20") { - const contract = new ethers.Contract(targetAddress, contractInterface, provider) + const contract = getContract({ + address: targetAddress, + abi: KNOWN_ABI.ERC20, + publicClient, + }) + const [name, symbol, decimals] = await Promise.all([ - contract.name(), - contract.symbol(), - contract.decimals(), + contract.read.name(), + contract.read.symbol(), + contract.read.decimals(), ]) - result.asset = { name, symbol, decimals } + result.asset = { + name, + symbol, + decimals, + } } else if (contractType === "ERC721") { - const tokenId = getContractCallArg(contractCall, "tokenId") + const tokenId = getContractCallArg(contractCall, "tokenId")!.toBigInt() try { - const contract = new ethers.Contract(targetAddress, abiErc721Metadata, provider) + const contract = getContract({ + address: targetAddress, + abi: KNOWN_ABI.ERC721, + publicClient, + }) const [name, symbol, tokenURI] = await Promise.all([ - contract.name(), - contract.symbol(), - tokenId ? contract.tokenURI(tokenId) : undefined, + contract.read.name(), + contract.read.symbol(), + tokenId ? contract.read.tokenURI([tokenId]) : undefined, ]) - result.asset = { - name, - symbol, - tokenId, - tokenURI, - decimals: 1, - } + result.asset = { name, symbol, tokenId, tokenURI, decimals: 1 } } catch (err) { // some NFTs don't implement the metadata functions } diff --git a/apps/extension/src/core/util/isContractAddress.ts b/apps/extension/src/core/util/isContractAddress.ts index 1145947f87..5b49f03e9b 100644 --- a/apps/extension/src/core/util/isContractAddress.ts +++ b/apps/extension/src/core/util/isContractAddress.ts @@ -19,9 +19,6 @@ export const isContractAddressOld = async ( export const isContractAddress = async (client: PublicClient, address: EvmAddress) => { try { const code = await client.getBytecode({ address }) - // TODO remove log - // eslint-disable-next-line no-console - console.log("isContractAddress remove after checking", { code }) return code !== "0x" } catch (error) { // not a contract diff --git a/apps/extension/src/ui/domains/Ethereum/getExtensionEthereumProvider.ts b/apps/extension/src/ui/domains/Ethereum/getExtensionEthereumProvider.ts index 94ed97abaa..15fad4bf23 100644 --- a/apps/extension/src/ui/domains/Ethereum/getExtensionEthereumProvider.ts +++ b/apps/extension/src/ui/domains/Ethereum/getExtensionEthereumProvider.ts @@ -1,14 +1,31 @@ +import { + ETH_ERROR_EIP1474_INTERNAL_ERROR, + EthProviderRpcError, +} from "@core/injectEth/EthProviderRpcError" import { log } from "@core/log" import { EvmNetworkId } from "@talismn/chaindata-provider" import { api } from "@ui/api" import { ethers } from "ethers" import { PublicClient, createPublicClient, custom } from "viem" -type ViemRequest = (method: string, params?: unknown[]) => Promise +type EthersRequest = (method: string, params?: unknown[]) => Promise +type ViemRequest = (arg: { method: string; params?: unknown[] }) => Promise -const ethereumRequest = - (chainId: EvmNetworkId): ViemRequest => +const ethersRequest = + (chainId: EvmNetworkId): EthersRequest => async (method: string, params?: unknown[]) => { + try { + return await api.ethRequest({ chainId, method, params }) + } catch (err) { + log.error("[provider.request] error on %s", method, { err }) + const { message, code, data } = err as EthProviderRpcError + throw new EthProviderRpcError(message, code ?? ETH_ERROR_EIP1474_INTERNAL_ERROR, data) + } + } + +const viemRequest = + (chainId: EvmNetworkId): ViemRequest => + async ({ method, params }) => { try { return await api.ethRequest({ chainId, method, params }) } catch (err) { @@ -21,14 +38,14 @@ const ethereumRequest = } export const getExtensionEthereumProvider = (evmNetworkId: EvmNetworkId) => { - return new ethers.providers.Web3Provider(ethereumRequest(evmNetworkId)) + return new ethers.providers.Web3Provider(ethersRequest(evmNetworkId)) } export const getExtensionPublicClient = (evmNetworkId: EvmNetworkId): PublicClient => { return createPublicClient({ // TODO check timers, decide if they should be here (remove them on backend) or clear them here and use defaults on backend transport: custom({ - request: ethereumRequest(evmNetworkId), + request: viemRequest(evmNetworkId), }), }) } diff --git a/apps/extension/src/ui/domains/Ethereum/useEthTransaction.ts b/apps/extension/src/ui/domains/Ethereum/useEthTransaction.ts index 7f0d76a1b2..3a9c8d311d 100644 --- a/apps/extension/src/ui/domains/Ethereum/useEthTransaction.ts +++ b/apps/extension/src/ui/domains/Ethereum/useEthTransaction.ts @@ -23,10 +23,11 @@ import { getEthTransactionInfo } from "@core/util/getEthTransactionInfo" import { FeeHistoryAnalysis, getFeeHistoryAnalysis } from "@core/util/getFeeHistoryAnalysis" import { useQuery } from "@tanstack/react-query" import { api } from "@ui/api" -import { useEthereumProvider } from "@ui/domains/Ethereum/useEthereumProvider" +import { useEthereumProvider, usePublicClient } from "@ui/domains/Ethereum/useEthereumProvider" import { BigNumber, ethers } from "ethers" import { useEffect, useMemo, useState } from "react" import { useTranslation } from "react-i18next" +import { PublicClient } from "viem" import { useIsValidEthTransaction } from "./useIsValidEthTransaction" @@ -184,19 +185,19 @@ const useBlockFeeData = ( } const useTransactionInfo = ( - provider: ethers.providers.JsonRpcProvider | undefined, + publicClient: PublicClient | undefined, tx: ethers.providers.TransactionRequest | undefined ) => { const { data, ...rest } = useQuery({ // check tx as boolean as it's not pure - queryKey: ["transactionInfo", provider?.network?.chainId, tx], + queryKey: ["transactionInfo", publicClient?.chain?.id, tx], queryFn: async () => { - if (!provider || !tx) return null - return await getEthTransactionInfo(provider, tx) + if (!publicClient || !tx) return null + return await getEthTransactionInfo(publicClient, tx) }, refetchInterval: false, refetchOnWindowFocus: false, // prevents error to be cleared when window gets focus - enabled: !!provider && !!tx, + enabled: !!publicClient && !!tx, }) return { transactionInfo: data ?? undefined, ...rest } @@ -374,7 +375,8 @@ export const useEthTransaction = ( isReplacement = false ) => { const provider = useEthereumProvider(tx?.chainId?.toString()) - const { transactionInfo, error: errorTransactionInfo } = useTransactionInfo(provider, tx) + const publicClient = usePublicClient(tx?.chainId?.toString()) + const { transactionInfo, error: errorTransactionInfo } = useTransactionInfo(publicClient, tx) const { hasEip1559Support, error: errorEip1559Support } = useHasEip1559Support(provider) const { nonce, error: nonceError } = useNonce( tx?.from as `0x${string}` | undefined, diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyDefault.tsx b/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyDefault.tsx index 8b576329da..af924d60d2 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyDefault.tsx +++ b/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyDefault.tsx @@ -18,7 +18,7 @@ export const EthSignBodyDefault: FC = () => { const nativeTokenRates = useTokenRates(nativeToken?.id) const amount = useMemo(() => { - return nativeToken && transactionInfo?.value?.gt(0) + return nativeToken && transactionInfo?.value && transactionInfo.value > 0n ? new BalanceFormatter( transactionInfo.value.toString(), nativeToken.decimals, From 2b03fb1d4f61a59f2da00816bebc86829cf3cce3 Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Mon, 30 Oct 2023 13:22:20 +0900 Subject: [PATCH 14/50] fix: transport errors --- .../src/getEvmNetworkPublicClient.ts | 41 +++++++++---------- .../src/getEvmNetworkWalletClient.ts | 7 +++- .../src/getTransportForEvmNetwork.ts | 28 +++++++++++++ 3 files changed, 52 insertions(+), 24 deletions(-) create mode 100644 packages/chain-connector-evm/src/getTransportForEvmNetwork.ts diff --git a/packages/chain-connector-evm/src/getEvmNetworkPublicClient.ts b/packages/chain-connector-evm/src/getEvmNetworkPublicClient.ts index e6db608553..11683b33b3 100644 --- a/packages/chain-connector-evm/src/getEvmNetworkPublicClient.ts +++ b/packages/chain-connector-evm/src/getEvmNetworkPublicClient.ts @@ -1,13 +1,13 @@ import { EvmNetwork, Token } from "@talismn/chaindata-provider" -import { PublicClient, createPublicClient, fallback, http } from "viem" +import { PublicClient, createPublicClient } from "viem" import { clearChainsCache, getChainFromEvmNetwork } from "./getChainFromEvmNetwork" -import { addOnfinalityApiKey } from "./util" +import { getTransportForEvmNetwork } from "./getTransportForEvmNetwork" -const BATCH_WAIT = 25 -const BATCH_SIZE = 30 +const MUTLICALL_BATCH_WAIT = 25 +const MUTLICALL_BATCH_SIZE = 1000 -// create clients as needed, to prevent unnecessary health checks +// cache to reuse previously created public clients const publicClientCache = new Map() export const clearPublicClientCache = (evmNetworkId?: string) => { @@ -31,23 +31,20 @@ export const getEvmNetworkPublicClient = ( if (!publicClientCache.has(evmNetwork.id)) { if (!evmNetwork.rpcs?.length) throw new Error("No RPCs found for EVM network") - const batch = chain.contracts?.multicall3 ? { multicall: { wait: BATCH_WAIT } } : undefined - const transport = chain.contracts?.multicall3 - ? http(undefined, { - batch: { - batchSize: BATCH_SIZE, - wait: BATCH_WAIT, - }, - }) - : fallback( - evmNetwork.rpcs.map((rpc) => - http(addOnfinalityApiKey(rpc.url, options.onFinalityApiKey), { - batch: { wait: BATCH_WAIT }, - }) - ) - ) - - publicClientCache.set(evmNetwork.id, createPublicClient({ chain, transport, batch })) + const batch = chain.contracts?.multicall3 + ? { multicall: { wait: MUTLICALL_BATCH_WAIT, batchSize: MUTLICALL_BATCH_SIZE } } + : undefined + + const transport = getTransportForEvmNetwork(evmNetwork, options) + + publicClientCache.set( + evmNetwork.id, + createPublicClient({ + chain, + transport, + batch, + }) + ) } return publicClientCache.get(evmNetwork.id) as PublicClient diff --git a/packages/chain-connector-evm/src/getEvmNetworkWalletClient.ts b/packages/chain-connector-evm/src/getEvmNetworkWalletClient.ts index 08b0b71c4c..15bf756951 100644 --- a/packages/chain-connector-evm/src/getEvmNetworkWalletClient.ts +++ b/packages/chain-connector-evm/src/getEvmNetworkWalletClient.ts @@ -1,7 +1,8 @@ import { EvmNetwork, Token } from "@talismn/chaindata-provider" -import { Account, WalletClient, createWalletClient, http } from "viem" +import { Account, WalletClient, createWalletClient } from "viem" import { getChainFromEvmNetwork } from "./getChainFromEvmNetwork" +import { getTransportForEvmNetwork } from "./getTransportForEvmNetwork" type WalletClientOptions = { account?: `0x${string}` | Account @@ -17,5 +18,7 @@ export const getEvmNetworkWalletClient = ( onFinalityApiKey: options.onFinalityApiKey, }) - return createWalletClient({ chain, transport: http(), account: options.account }) + const transport = getTransportForEvmNetwork(evmNetwork, options) + + return createWalletClient({ chain, transport, account: options.account }) } diff --git a/packages/chain-connector-evm/src/getTransportForEvmNetwork.ts b/packages/chain-connector-evm/src/getTransportForEvmNetwork.ts new file mode 100644 index 0000000000..981d15337d --- /dev/null +++ b/packages/chain-connector-evm/src/getTransportForEvmNetwork.ts @@ -0,0 +1,28 @@ +import { EvmNetwork } from "@talismn/chaindata-provider" +import { fallback, http } from "viem" + +import { addOnfinalityApiKey } from "./util" + +const HTTP_BATCH_WAIT = 25 +const HTTP_BATCH_SIZE = 30 + +type TransportOptions = { + onFinalityApiKey?: string +} + +export const getTransportForEvmNetwork = ( + evmNetwork: EvmNetwork, + options: TransportOptions = {} +) => { + if (!evmNetwork.rpcs?.length) throw new Error("No RPCs found for EVM network") + + return fallback( + evmNetwork.rpcs.map((rpc) => + http(addOnfinalityApiKey(rpc.url, options.onFinalityApiKey), { + batch: { wait: HTTP_BATCH_WAIT, batchSize: HTTP_BATCH_SIZE }, + retryCount: 0, + }) + ), + { retryCount: 0 } + ) +} From 2ac0320459bb961f1ebeaf5038d01942d72277bb Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Mon, 30 Oct 2023 16:40:52 +0900 Subject: [PATCH 15/50] feat: use multicall in evm-native module --- .../src/EvmNativeModule.ts | 98 ++++++++++++++----- .../balances-evm-native/src/abi/multicall.ts | 24 +++++ 2 files changed, 97 insertions(+), 25 deletions(-) create mode 100644 packages/balances-evm-native/src/abi/multicall.ts diff --git a/packages/balances-evm-native/src/EvmNativeModule.ts b/packages/balances-evm-native/src/EvmNativeModule.ts index ab63a70259..b1ea4cc99a 100644 --- a/packages/balances-evm-native/src/EvmNativeModule.ts +++ b/packages/balances-evm-native/src/EvmNativeModule.ts @@ -18,6 +18,7 @@ import { hasOwnProperty, isEthereumAddress } from "@talismn/util" import isEqual from "lodash/isEqual" import { PublicClient } from "viem" +import { abiMulticall } from "./abi/multicall" import log from "./log" type ModuleType = "evm-native" @@ -201,9 +202,13 @@ export const EvmNativeModule: NewBalanceModule< throw new Error(`Could not get rpc provider for evm network ${evmNetworkId}`) // fetch all balances - const balanceRequests = addresses.map( - async (address) => - new Balance({ + const freeBalances = await getFreeBalances(publicClient, addresses) + + const balanceResults = addresses + .map((address, i) => { + if (freeBalances[i] === "error") return false + + return new Balance({ source: "evm-native", status: "live", @@ -213,26 +218,13 @@ export const EvmNativeModule: NewBalanceModule< evmNetworkId, tokenId, - free: await getFreeBalance(publicClient, address), + free: freeBalances[i].toString(), }) - ) - - // wait for balance fetches to complete - const balanceResults = await Promise.allSettled(balanceRequests) - - // filter out errors - const balances = balanceResults - .map((result) => { - if (result.status === "rejected") { - log.debug(result.reason) - return false - } - return result.value }) .filter((balance): balance is Balance => balance !== false) // return to caller - return new Balances(balances) + return new Balances(balanceResults) }) ) ) @@ -250,11 +242,14 @@ export const EvmNativeModule: NewBalanceModule< } } -async function getFreeBalance(publicClient: PublicClient, address: Address): Promise { - if (!isEthereumAddress(address)) return "0" +async function getFreeBalance( + publicClient: PublicClient, + address: Address +): Promise { + if (!isEthereumAddress(address)) return 0n try { - return (await publicClient.getBalance({ address })).toString() + return await publicClient.getBalance({ address }) } catch (error) { const errorMessage = hasOwnProperty(error, "shortMessage") ? error.shortMessage @@ -264,9 +259,62 @@ async function getFreeBalance(publicClient: PublicClient, address: Address): Pro log.warn( `Failed to get balance from chain ${publicClient.chain?.id} for address ${address}: ${errorMessage}` ) - throw new Error( - `Failed to get balance from chain ${publicClient.chain?.id} for address ${address}`, - { cause: error as Error } - ) + return "error" + } +} + +async function getFreeBalances( + publicClient: PublicClient, + addresses: Address[] +): Promise<(bigint | "error")[]> { + // if multicall is available, use it to save RPC rate limits + if (publicClient.batch?.multicall && publicClient.chain?.contracts?.multicall3?.address) { + try { + const ethAddresses = addresses.filter(isEthereumAddress) + + const addressMulticall = publicClient.chain.contracts.multicall3.address + + const callResults = await publicClient.multicall({ + contracts: ethAddresses.map((address) => ({ + address: addressMulticall, + abi: abiMulticall, + functionName: "getEthBalance", + args: [address], + })), + }) + + const ethBalanceResults = Object.fromEntries( + ethAddresses.map((address, i) => { + const { error } = callResults[i] + if (error) { + const errorMessage = hasOwnProperty(error, "shortMessage") + ? error.shortMessage + : hasOwnProperty(error, "message") + ? error.message + : error + log.warn( + `Failed to get balance from chain ${publicClient.chain?.id} for address ${address}: ${errorMessage}` + ) + } + + return [address, callResults[i].result ?? ("error" as const)] + }) + ) + + // default to 0 for non evm addresses + return addresses.map((address) => ethBalanceResults[address] ?? 0n) + } catch (err) { + const errorMessage = hasOwnProperty(err, "shortMessage") + ? err.shortMessage + : hasOwnProperty(err, "message") + ? err.message + : err + log.warn( + `Failed to get balance from chain ${publicClient.chain?.id} for ${addresses.length} addresses: ${errorMessage}` + ) + return addresses.map(() => "error") + } } + + return Promise.all(addresses.map((address) => getFreeBalance(publicClient, address))) } diff --git a/packages/balances-evm-native/src/abi/multicall.ts b/packages/balances-evm-native/src/abi/multicall.ts new file mode 100644 index 0000000000..756f5acc6d --- /dev/null +++ b/packages/balances-evm-native/src/abi/multicall.ts @@ -0,0 +1,24 @@ +import { parseAbi } from "viem" + +export const abiMulticall = parseAbi([ + "struct Call { address target; bytes callData; }", + "struct Call3 { address target; bool allowFailure; bytes callData; }", + "struct Call3Value { address target; bool allowFailure; uint256 value; bytes callData; }", + "struct Result { bool success; bytes returnData; }", + "function aggregate(Call[] calldata calls) public payable returns (uint256 blockNumber, bytes[] memory returnData)", + "function aggregate3(Call3[] calldata calls) public payable returns (Result[] memory returnData)", + "function aggregate3Value(Call3Value[] calldata calls) public payable returns (Result[] memory returnData)", + "function blockAndAggregate(Call[] calldata calls) public payable returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData)", + "function getBasefee() view returns (uint256 basefee)", + "function getBlockHash(uint256 blockNumber) view returns (bytes32 blockHash)", + "function getBlockNumber() view returns (uint256 blockNumber)", + "function getChainId() view returns (uint256 chainid)", + "function getCurrentBlockCoinbase() view returns (address coinbase)", + "function getCurrentBlockDifficulty() view returns (uint256 difficulty)", + "function getCurrentBlockGasLimit() view returns (uint256 gaslimit)", + "function getCurrentBlockTimestamp() view returns (uint256 timestamp)", + "function getEthBalance(address addr) view returns (uint256 balance)", + "function getLastBlockHash() view returns (bytes32 blockHash)", + "function tryAggregate(bool requireSuccess, Call[] calldata calls) public payable returns (Result[] memory returnData)", + "function tryBlockAndAggregate(bool requireSuccess, Call[] calldata calls) public payable returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData)", +] as const) From 4922569db12da972f9f28812c3eab2882a924757 Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Tue, 31 Oct 2023 11:28:49 +0900 Subject: [PATCH 16/50] chore: viem TransactionRequest --- .../ethereum/__tests__/ethereum.helpers.ts | 33 +- .../domains/ethereum/handler.extension.ts | 29 +- .../src/core/domains/ethereum/handler.tabs.ts | 60 +-- .../src/core/domains/ethereum/helpers.ts | 386 +++++++++++------- .../src/core/domains/ethereum/types.ts | 121 ++++-- .../core/domains/ethereum/viemMigration.ts | 189 ++++++--- .../src/core/domains/signing/requests.ts | 4 +- .../src/core/domains/signing/types.ts | 18 +- .../src/core/domains/transactions/helpers.ts | 21 +- .../src/core/domains/transactions/types.ts | 4 +- .../transactions/watchEthereumTransaction.ts | 18 +- .../src/core/domains/transfers/handler.ts | 16 +- .../src/core/domains/transfers/types.ts | 6 +- .../src/core/util/getEthTransactionInfo.ts | 45 +- .../src/core/util/getFeeHistoryAnalysis.ts | 98 +++-- apps/extension/src/ui/api/api.ts | 17 +- apps/extension/src/ui/api/types.ts | 25 +- .../popup/pages/Sign/ethereum/Transaction.tsx | 30 +- .../CustomGasSettingsFormEip1559.tsx | 55 +-- .../CustomGasSettingsFormLegacy.tsx | 36 +- .../Ethereum/GasSettings/EthFeeSelect.tsx | 4 +- .../Ethereum/GasSettings/FeeOptionsForm.tsx | 5 +- .../Ethereum/getExtensionEthereumProvider.ts | 52 +-- .../src/ui/domains/Ethereum/useEthBalance.ts | 13 +- .../Ethereum/useEthReplaceTransaction.ts | 48 ++- .../ui/domains/Ethereum/useEthTransaction.ts | 240 ++++++----- .../domains/Ethereum/useEthereumProvider.ts | 76 +++- .../Ethereum/useIsValidEthTransaction.ts | 37 +- .../domains/SendFunds/SendFundsProgress.tsx | 38 +- .../src/ui/domains/SendFunds/useSendFunds.ts | 44 +- .../Sign/Ethereum/EthSignBodyDefault.tsx | 10 +- .../EthereumSignTransactionRequestContext.ts | 24 +- .../Sign/ViewDetails/ViewDetailsEth.tsx | 21 +- .../domains/Transactions/TxReplaceDrawer.tsx | 18 +- 34 files changed, 1056 insertions(+), 785 deletions(-) diff --git a/apps/extension/src/core/domains/ethereum/__tests__/ethereum.helpers.ts b/apps/extension/src/core/domains/ethereum/__tests__/ethereum.helpers.ts index dbb189e3bb..9a5a97f1a4 100644 --- a/apps/extension/src/core/domains/ethereum/__tests__/ethereum.helpers.ts +++ b/apps/extension/src/core/domains/ethereum/__tests__/ethereum.helpers.ts @@ -1,4 +1,5 @@ import { ethers } from "ethers" +import { parseGwei } from "viem" import { getEthDerivationPath, @@ -8,8 +9,8 @@ import { isSafeImageUrl, } from "../helpers" -const baseFeePerGas = ethers.utils.parseUnits("2", "gwei") -const maxPriorityFeePerGas = ethers.utils.parseUnits("8", "gwei") +const baseFeePerGas = parseGwei("2") +const maxPriorityFeePerGas = parseGwei("2") describe("Test ethereum helpers", () => { test("getMaxFeePerGas 0 block", async () => { @@ -29,12 +30,12 @@ describe("Test ethereum helpers", () => { test("getTotalFeesFromGasSettings - EIP1559 maxFee lower than baseFee", () => { const { estimatedFee, maxFee } = getTotalFeesFromGasSettings( { - type: 2, - maxFeePerGas: ethers.utils.parseUnits("1.5", "gwei"), - maxPriorityFeePerGas: ethers.utils.parseUnits("0.5", "gwei"), - gasLimit: 22000, + type: "eip1559", + maxFeePerGas: parseGwei("1.5"), + maxPriorityFeePerGas: parseGwei("0.5"), + gas: 22000n, }, - 21000, + 21000n, baseFeePerGas ) // @@ -48,12 +49,12 @@ describe("Test ethereum helpers", () => { test("getTotalFeesFromGasSettings - EIP1559 classic", () => { const { estimatedFee, maxFee } = getTotalFeesFromGasSettings( { - type: 2, - maxFeePerGas: ethers.utils.parseUnits("3.5", "gwei"), - maxPriorityFeePerGas: ethers.utils.parseUnits("0.5", "gwei"), - gasLimit: 22000, + type: "eip1559", + maxFeePerGas: parseGwei("3.5"), + maxPriorityFeePerGas: parseGwei("0.5"), + gas: 22000n, }, - 21000, + 21000n, baseFeePerGas ) @@ -67,11 +68,11 @@ describe("Test ethereum helpers", () => { test("getTotalFeesFromGasSettings - Legacy", () => { const { estimatedFee, maxFee } = getTotalFeesFromGasSettings( { - type: 0, - gasPrice: baseFeePerGas.add(maxPriorityFeePerGas), - gasLimit: 22000, + type: "legacy", + gasPrice: baseFeePerGas + maxPriorityFeePerGas, + gas: 22000n, }, - 21000, + 21000n, baseFeePerGas ) diff --git a/apps/extension/src/core/domains/ethereum/handler.extension.ts b/apps/extension/src/core/domains/ethereum/handler.extension.ts index 6e8eb4d6af..688b714dfb 100644 --- a/apps/extension/src/core/domains/ethereum/handler.extension.ts +++ b/apps/extension/src/core/domains/ethereum/handler.extension.ts @@ -26,9 +26,8 @@ import { privateKeyToAccount } from "viem/accounts" import { getHostName } from "../app/helpers" import { getHumanReadableErrorMessage } from "./errors" -import { rebuildTransactionRequestNumbers } from "./helpers" +import { parseTransactionRequest } from "./helpers" import { getTransactionCount, incrementTransactionCount } from "./transactionCountManager" -import { getViemSendTransactionParams } from "./viemMigration" export class EthHandler extends ExtensionHandler { private signAndSendApproveHardware: MessageHandler<"pri(eth.signing.approveSignAndSendHardware)"> = @@ -90,7 +89,7 @@ export class EthHandler extends ExtensionHandler { assert(isEthereumAddress(account.address), "Invalid ethereum address") // rebuild BigNumber property values (converted to json when serialized) - const tx = rebuildTransactionRequestNumbers(transaction) + const tx = parseTransactionRequest(transaction) if (tx.nonce === undefined) tx.nonce = await getTransactionCount(account.address, ethChainId) const result = await getPairForAddressSafely(account.address, async (pair) => { @@ -106,13 +105,13 @@ export class EthHandler extends ExtensionHandler { return await client.sendTransaction({ chain: client.chain, account, - ...getViemSendTransactionParams(tx), + ...tx, }) }) if (result.ok) { // long running operation, we do not want this inside getPairForAddressSafely - watchEthereumTransaction(ethChainId, result.val, tx, { + watchEthereumTransaction(ethChainId, result.val, transaction, { siteUrl: queued.url, notifications: true, }) @@ -142,24 +141,21 @@ export class EthHandler extends ExtensionHandler { } private sendSigned: MessageHandler<"pri(eth.signing.sendSigned)"> = async ({ + evmNetworkId, unsigned, signed, transferInfo, }) => { - assert(unsigned.chainId, "chainId is not defined") - const evmNetworkId = unsigned.chainId.toString() + assert(evmNetworkId, "chainId is not defined") const client = await chainConnectorEvm.getWalletClientForEvmNetwork(evmNetworkId) assert(client, "Missing client for chain " + evmNetworkId) - // rebuild BigNumber property values (converted to json when serialized) - const tx = rebuildTransactionRequestNumbers(unsigned) - try { const hash = await client.sendRawTransaction({ serializedTransaction: signed }) // long running operation, we do not want this inside getPairForAddressSafely - watchEthereumTransaction(evmNetworkId, hash, tx, { + watchEthereumTransaction(evmNetworkId, hash, unsigned, { notifications: true, transferInfo, }) @@ -177,15 +173,14 @@ export class EthHandler extends ExtensionHandler { } private signAndSend: MessageHandler<"pri(eth.signing.signAndSend)"> = async ({ + evmNetworkId, unsigned, transferInfo, }) => { - assert(unsigned.chainId, "chainId is not defined") + assert(evmNetworkId, "chainId is not defined") assert(unsigned.from, "from is not defined") - const evmNetworkId = unsigned.chainId.toString() // rebuild BigNumber property values (converted to json when serialized) - const tx = rebuildTransactionRequestNumbers(unsigned) const result = await getPairForAddressSafely(unsigned.from, async (pair) => { const client = await chainConnectorEvm.getWalletClientForEvmNetwork(evmNetworkId) @@ -196,16 +191,18 @@ export class EthHandler extends ExtensionHandler { const privateKey = getPrivateKey(pair, password, "hex") const account = privateKeyToAccount(privateKey) + const tx = parseTransactionRequest(unsigned) + return await client.sendTransaction({ chain: client.chain, account, - ...getViemSendTransactionParams(tx), + ...tx, }) }) if (result.ok) { // long running operation, we do not want this inside getPairForAddressSafely - watchEthereumTransaction(evmNetworkId, result.val, tx, { + watchEthereumTransaction(evmNetworkId, result.val, unsigned, { notifications: true, transferInfo, }) diff --git a/apps/extension/src/core/domains/ethereum/handler.tabs.ts b/apps/extension/src/core/domains/ethereum/handler.tabs.ts index 0d97e8b3a8..689671f401 100644 --- a/apps/extension/src/core/domains/ethereum/handler.tabs.ts +++ b/apps/extension/src/core/domains/ethereum/handler.tabs.ts @@ -62,7 +62,7 @@ import { import { requestAddNetwork, requestWatchAsset } from "./requests" import { EthProviderMessage, - EthRequestArguments, + EthRequestArgsViem, EthRequestArgumentsViem, EthRequestResultViem, EthRequestSignArguments, @@ -84,7 +84,10 @@ export class EthTabsHandler extends TabsHandler { } } - async getSiteDetails(url: string, authorisedAddress?: string): Promise { + private async getSiteDetails( + url: string, + authorisedAddress?: string + ): Promise { let site try { @@ -101,7 +104,7 @@ export class EthTabsHandler extends TabsHandler { return site as EthAuthorizedSite } - async getPublicClient(url: string, authorisedAddress?: string): Promise { + private async getPublicClient(url: string, authorisedAddress?: string): Promise { const site = await this.getSiteDetails(url, authorisedAddress) const ethereumNetwork = await chaindataProvider.getEvmNetwork(site.ethChainId.toString()) @@ -429,12 +432,17 @@ export class EthTabsHandler extends TabsHandler { method ) // on https://astar.network, params are in reverse order - if (isMessageFirst && isEthereumAddress(params[0]) && !isEthereumAddress(params[1])) + if ( + typeof params[0] === "string" && + isMessageFirst && + isEthereumAddress(params[0]) && + !isEthereumAddress(params[1]) + ) isMessageFirst = false const [uncheckedMessage, from] = isMessageFirst ? [params[0], getAddress(params[1])] - : [params[1], getAddress(params[0])] + : [params[1], getAddress(params[0] as string)] // message is either a raw string or a hex string or an object (signTypedData_v1) const message = @@ -558,24 +566,28 @@ export class EthTabsHandler extends TabsHandler { private async sendTransaction( url: string, - request: EthRequestArguments<"eth_sendTransaction">, + { params: [txRequest] }: EthRequestArgumentsViem<"eth_sendTransaction">, port: Port ) { - const { - params: [txRequest], - } = request - const site = await this.getSiteDetails(url, txRequest.from) - // ensure chainId isn't an hex (ex: Zerion) - if (typeof txRequest.chainId === "string" && (txRequest.chainId as string).startsWith("0x")) - txRequest.chainId = parseInt(txRequest.chainId, 16) + { + // eventhough not standard, some transactions specify a chainId in the request + // throw an error if it's not the current tab's chainId - // checks that the request targets currently selected network - if (txRequest.chainId && site.ethChainId !== txRequest.chainId) - throw new EthProviderRpcError("Wrong network", ETH_ERROR_EIP1474_INVALID_PARAMS) + let specifiedChainId = (txRequest as unknown as { chainId?: string | number }).chainId + + // ensure chainId isn't an hex (ex: Zerion) + if (typeof specifiedChainId === "string" && (specifiedChainId as string).startsWith("0x")) + specifiedChainId = parseInt(specifiedChainId, 16) + + // checks that the request targets currently selected network + if (specifiedChainId && Number(site.ethChainId) !== Number(specifiedChainId)) + throw new EthProviderRpcError("Wrong network", ETH_ERROR_EIP1474_INVALID_PARAMS) + } try { + // ensure that we have a valid provider for the current network await this.getPublicClient(url, txRequest.from) } catch (error) { throw new EthProviderRpcError("Network not supported", ETH_ERROR_EIP1993_CHAIN_DISCONNECTED) @@ -585,7 +597,7 @@ export class EthTabsHandler extends TabsHandler { // allow only the currently selected account in "from" field if (txRequest.from?.toLowerCase() !== address.toLowerCase()) - throw new EthProviderRpcError("Unknown from account", ETH_ERROR_EIP1474_INVALID_INPUT) + throw new EthProviderRpcError("Invalid from account", ETH_ERROR_EIP1474_INVALID_INPUT) const pair = keyring.getPair(address) @@ -598,11 +610,7 @@ export class EthTabsHandler extends TabsHandler { return signAndSendEth( url, - { - // locks the chainId in case the dapp's chainId changes after signing request creation - chainId: site.ethChainId, - ...txRequest, - }, + txRequest, site.ethChainId.toString(), { address, @@ -618,7 +626,7 @@ export class EthTabsHandler extends TabsHandler { // url validation carried out inside stores.sites.getSiteFromUrl site = await this.stores.sites.getSiteFromUrl(url) } catch (error) { - //no-op + // no-op } return site?.ethPermissions @@ -689,7 +697,7 @@ export class EthTabsHandler extends TabsHandler { private async ethRequest( id: string, url: string, - request: AnyEthRequest, + request: EthRequestArgsViem, port: Port ): Promise { if ( @@ -776,7 +784,7 @@ export class EthTabsHandler extends TabsHandler { case "eth_sendTransaction": return this.sendTransaction( url, - request as EthRequestArguments<"eth_sendTransaction">, + request as EthRequestArgumentsViem<"eth_sendTransaction">, port ) @@ -833,7 +841,7 @@ export class EthTabsHandler extends TabsHandler { case "pub(eth.request)": { try { - return await this.ethRequest(id, url, request as AnyEthRequest, port) + return await this.ethRequest(id, url, request as EthRequestArgsViem, port) } catch (err) { if (err instanceof EthProviderRpcError) throw err // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/apps/extension/src/core/domains/ethereum/helpers.ts b/apps/extension/src/core/domains/ethereum/helpers.ts index 5d09970409..b698cf23bc 100644 --- a/apps/extension/src/core/domains/ethereum/helpers.ts +++ b/apps/extension/src/core/domains/ethereum/helpers.ts @@ -2,6 +2,7 @@ import { ChainId } from "@core/domains/chains/types" import { EthGasSettings, EthGasSettingsEip1559, + EvmAddress, EvmNetworkId, LedgerEthDerivationPathType, } from "@core/domains/ethereum/types" @@ -9,8 +10,17 @@ import { Token } from "@core/domains/tokens/types" import { assert } from "@polkadot/util" import { erc20Abi } from "@talismn/balances-evm-erc20" import { isEthereumAddress } from "@talismn/util" -import { BigNumber, BigNumberish, ethers } from "ethers" -import { encodeFunctionData, getAddress, isAddress } from "viem" +import { + Hex, + TransactionRequest, + TransactionRequestBase, + encodeFunctionData, + getAddress, + hexToBigInt, + hexToNumber, + isAddress, + isHex, +} from "viem" import * as yup from "yup" const DERIVATION_PATHS_PATTERNS = { @@ -31,45 +41,42 @@ export const getEthLedgerDerivationPath = (type: LedgerEthDerivationPathType, in return getDerivationPathFromPattern(index, DERIVATION_PATHS_PATTERNS[type]) } -/** - * @deprecated use viem - */ -export const getEthTransferTransactionBaseOld = async ( - evmNetworkId: EvmNetworkId, - from: string, - to: string, - token: Token, - planck: string -): Promise => { - assert(evmNetworkId, "evmNetworkId is required") - assert(token, "token is required") - assert(planck, "planck is required") - assert(isEthereumAddress(from), "from address is required") - assert(isEthereumAddress(to), "to address is required") - - let tx: ethers.providers.TransactionRequest - - if (token.type === "evm-native") { - tx = { - value: ethers.BigNumber.from(planck), - to: ethers.utils.getAddress(to), - } - } else if (token.type === "evm-erc20") { - const contract = new ethers.Contract(token.contractAddress, erc20Abi) - tx = await contract.populateTransaction["transfer"](to, ethers.BigNumber.from(planck)) - } else throw new Error(`Invalid token type ${token.type} - token ${token.id}`) - - return { - chainId: parseInt(evmNetworkId, 10), - from, - ...tx, - } -} +// export const getEthTransferTransactionBaseOld = async ( +// evmNetworkId: EvmNetworkId, +// from: string, +// to: string, +// token: Token, +// planck: string +// ): Promise => { +// assert(evmNetworkId, "evmNetworkId is required") +// assert(token, "token is required") +// assert(planck, "planck is required") +// assert(isEthereumAddress(from), "from address is required") +// assert(isEthereumAddress(to), "to address is required") + +// let tx: ethers.providers.TransactionRequest + +// if (token.type === "evm-native") { +// tx = { +// value: ethers.BigNumber.from(planck), +// to: ethers.utils.getAddress(to), +// } +// } else if (token.type === "evm-erc20") { +// const contract = new ethers.Contract(token.contractAddress, erc20Abi) +// tx = await contract.populateTransaction["transfer"](to, ethers.BigNumber.from(planck)) +// } else throw new Error(`Invalid token type ${token.type} - token ${token.id}`) + +// return { +// chainId: parseInt(evmNetworkId, 10), +// from, +// ...tx, +// } +// } export const getEthTransferTransactionBase = async ( evmNetworkId: EvmNetworkId, - from: string, - to: string, + from: EvmAddress, + to: EvmAddress, token: Token, planck: bigint ) => { @@ -105,82 +112,161 @@ export const getErc20TokenId = ( contractAddress: string ) => `${chainOrNetworkId}-evm-erc20-${contractAddress}`.toLowerCase() -const safeBigNumberish = (value?: BigNumberish) => - BigNumber.isBigNumber(value) ? value.toString() : value +export const serializeTransactionRequest = ( + tx: TransactionRequest +): TransactionRequest => { + const serialized: TransactionRequest = { from: tx.from } + + if (tx.to !== undefined) serialized.to = tx.to + if (tx.data !== undefined) serialized.data = tx.data + if (tx.accessList !== undefined) serialized.accessList = tx.accessList + if (tx.type !== undefined) serialized.type = tx.type + if (tx.nonce !== undefined) serialized.nonce = tx.nonce + + // bigint fields need to be serialized + if (tx.value !== undefined) serialized.value = tx.value.toString() + if (tx.gas !== undefined) serialized.gas = tx.gas.toString() + if (tx.gasPrice !== undefined) serialized.gasPrice = tx.gasPrice.toString() + if (tx.maxFeePerGas !== undefined) serialized.maxFeePerGas = tx.maxFeePerGas.toString() + if (tx.maxPriorityFeePerGas !== undefined) + serialized.maxPriorityFeePerGas = tx.maxPriorityFeePerGas.toString() + + return serialized +} -export const serializeTransactionRequestBigNumbers = ( - transaction: ethers.providers.TransactionRequest -) => { - const tx = { ...transaction } +export const serializeTransactionRequestBase = ( + txb: TransactionRequestBase +): TransactionRequestBase => { + const serialized: TransactionRequestBase = { + from: txb.from, + } - if (tx.gasLimit) tx.gasLimit = safeBigNumberish(tx.gasLimit) - if (tx.gasPrice) tx.gasPrice = safeBigNumberish(tx.gasPrice) - if (tx.maxFeePerGas) tx.maxFeePerGas = safeBigNumberish(tx.maxFeePerGas) - if (tx.maxPriorityFeePerGas) tx.maxPriorityFeePerGas = safeBigNumberish(tx.maxPriorityFeePerGas) - if (tx.value) tx.value = safeBigNumberish(tx.value) - if (tx.nonce) tx.nonce = safeBigNumberish(tx.nonce) + if (txb.data !== undefined) serialized.data = txb.data + if (txb.to !== undefined) serialized.to = txb.to + if (txb.value !== undefined) serialized.value = txb.value.toString() + if (txb.gas !== undefined) serialized.gas = txb.gas.toString() + if (txb.nonce !== undefined) serialized.nonce = txb.nonce - return tx + return serialized } -// BigNumbers need to be reconstructed if they are serialized then deserialized -export const rebuildTransactionRequestNumbers = ( - transaction: ethers.providers.TransactionRequest -) => { - const tx = structuredClone(transaction) - - if (tx.gasLimit) tx.gasLimit = BigNumber.from(tx.gasLimit) - if (tx.gasPrice) tx.gasPrice = BigNumber.from(tx.gasPrice) - if (tx.maxFeePerGas) tx.maxFeePerGas = BigNumber.from(tx.maxFeePerGas) - if (tx.maxPriorityFeePerGas) tx.maxPriorityFeePerGas = BigNumber.from(tx.maxPriorityFeePerGas) - if (tx.value) tx.value = BigNumber.from(tx.value) - if (tx.nonce) tx.nonce = BigNumber.from(tx.nonce) +export const parseGasSettings = (gasSettings: EthGasSettings): EthGasSettings => { + return gasSettings.type === "eip1559" + ? { + type: "eip1559", + gas: BigInt(gasSettings.gas), + maxFeePerGas: BigInt(gasSettings.maxFeePerGas), + maxPriorityFeePerGas: BigInt(gasSettings.maxPriorityFeePerGas), + } + : { + type: gasSettings.type, + gas: BigInt(gasSettings.gas), + gasPrice: BigInt(gasSettings.gasPrice), + } +} - return tx +export const serializeGasSettings = ( + gasSettings: EthGasSettings +): EthGasSettings => { + return gasSettings.type === "eip1559" + ? { + type: "eip1559", + gas: gasSettings.gas.toString(), + maxFeePerGas: gasSettings.maxFeePerGas.toString(), + maxPriorityFeePerGas: gasSettings.maxPriorityFeePerGas.toString(), + } + : { + type: gasSettings.type, + gas: gasSettings.gas.toString(), + gasPrice: gasSettings.gasPrice.toString(), + } } -export const rebuildGasSettings = (gasSettings: EthGasSettings) => { - const gs = structuredClone(gasSettings) +// BigNumbers need to be reconstructed if they are serialized then deserialized +export const parseTransactionRequest = ( + tx: TransactionRequest +): TransactionRequest => { + const parsed: TransactionRequest = { from: tx.from } + + if (tx.to !== undefined) parsed.to = tx.to + if (tx.data !== undefined) parsed.data = tx.data + if (tx.accessList !== undefined) parsed.accessList = tx.accessList + if (tx.type !== undefined) parsed.type = tx.type + if (tx.nonce !== undefined) parsed.nonce = tx.nonce + + // bigint fields need to be parsed + if (typeof tx.value === "string") parsed.value = BigInt(tx.value) + if (typeof tx.gas === "string") parsed.gas = BigInt(tx.gas) + if (typeof tx.gasPrice === "string") parsed.gasPrice = BigInt(tx.gasPrice) + if (typeof tx.maxFeePerGas === "string") parsed.maxFeePerGas = BigInt(tx.maxFeePerGas) + if (typeof tx.maxPriorityFeePerGas === "string") + parsed.maxPriorityFeePerGas = BigInt(tx.maxPriorityFeePerGas) + + return parsed +} - gs.gasLimit = BigNumber.from(gs.gasLimit) +export const parseRpcTransactionRequestBase = ( + rtx: TransactionRequestBase +): TransactionRequestBase => { + const txBase: TransactionRequestBase = { from: rtx.from } - if (gs.type === 2) { - gs.maxFeePerGas = BigNumber.from(gs.maxFeePerGas) - gs.maxPriorityFeePerGas = BigNumber.from(gs.maxPriorityFeePerGas) - } else if (gs.type === 0) { - gs.gasPrice = BigNumber.from(gs.gasPrice) - } else throw new Error("Unexpected gas settings type") + if (isHex(rtx.to)) txBase.to = rtx.to + if (isHex(rtx.data)) txBase.data = rtx.data + if (isHex(rtx.value)) txBase.value = hexToBigInt(rtx.value) + if (isHex(rtx.gas)) txBase.gas = hexToBigInt(rtx.gas) + if (isHex(rtx.nonce)) txBase.nonce = hexToNumber(rtx.nonce) - return gs + return txBase +} +/** @deprecated */ +export const rebuildGasSettings = ( + gasSettings: EthGasSettings +): EthGasSettings => { + return gasSettings.type === "eip1559" + ? { + type: "eip1559", + gas: isHex(gasSettings.gas) ? hexToBigInt(gasSettings.gas) : gasSettings.gas, + maxFeePerGas: isHex(gasSettings.maxFeePerGas) + ? hexToBigInt(gasSettings.maxFeePerGas) + : gasSettings.maxFeePerGas, + maxPriorityFeePerGas: isHex(gasSettings.maxPriorityFeePerGas) + ? hexToBigInt(gasSettings.maxPriorityFeePerGas) + : gasSettings.maxPriorityFeePerGas, + } + : { + type: gasSettings.type, + gas: isHex(gasSettings.gas) ? hexToBigInt(gasSettings.gas) : gasSettings.gas, + gasPrice: isHex(gasSettings.gasPrice) + ? hexToBigInt(gasSettings.gasPrice) + : gasSettings.gasPrice, + } } -const TX_GAS_LIMIT_DEFAULT = BigNumber.from("250000") -const TX_GAS_LIMIT_MIN = BigNumber.from("21000") -const TX_GAS_LIMIT_SAFETY_RATIO = 2 +const TX_GAS_LIMIT_DEFAULT = 250000n +const TX_GAS_LIMIT_MIN = 21000n +const TX_GAS_LIMIT_SAFETY_RATIO = 2n export const getGasLimit = ( - blockGasLimit: BigNumberish, - estimatedGas: BigNumberish, - tx: ethers.providers.TransactionRequest | undefined, + blockGasLimit: bigint, + estimatedGas: bigint, + tx: TransactionRequestBase | undefined, isContractCall?: boolean ) => { - // some dapps use legacy gas field instead of gasLimit // eslint-disable-next-line @typescript-eslint/no-explicit-any - const bnSuggestedGasLimit = BigNumber.from(tx?.gasLimit ?? (tx as any)?.gas ?? 0) - const bnEstimatedGas = BigNumber.from(estimatedGas) + const suggestedGasLimit = tx?.gas ?? 0n // for contract calls, gas cost can evolve overtime : add a safety margin - const bnSafeGasLimit = isContractCall - ? bnEstimatedGas.mul(100 + TX_GAS_LIMIT_SAFETY_RATIO).div(100) - : bnEstimatedGas + const safeGasLimit = isContractCall + ? (estimatedGas * (100n + TX_GAS_LIMIT_SAFETY_RATIO)) / 100n + : estimatedGas // RPC estimated gas may be too low (reliable ex: https://portal.zksync.io/bridge), // so if dapp suggests higher gas limit as the estimate, use that - const highestLimit = bnSafeGasLimit.gt(bnSuggestedGasLimit) ? bnSafeGasLimit : bnSuggestedGasLimit + const highestLimit = safeGasLimit > suggestedGasLimit ? safeGasLimit : suggestedGasLimit - let gasLimit = BigNumber.from(highestLimit) - if (gasLimit.gt(blockGasLimit)) { + let gasLimit = highestLimit + if (gasLimit > blockGasLimit) { // probably bad formatting or error from the dapp, fallback to default value gasLimit = TX_GAS_LIMIT_DEFAULT - } else if (gasLimit.lt(TX_GAS_LIMIT_MIN)) { + } else if (gasLimit < TX_GAS_LIMIT_MIN) { // invalid, all chains use 21000 as minimum, fallback to default value gasLimit = TX_GAS_LIMIT_DEFAULT } @@ -192,113 +278,109 @@ export const getGasLimit = ( const FEE_MAX_RAISE_RATIO_PER_BLOCK = 0.125 export const getMaxFeePerGas = ( - baseFeePerGas: BigNumberish, - maxPriorityFeePerGas: BigNumberish, + baseFeePerGas: bigint, + maxPriorityFeePerGas: bigint, maxBlocksWait = 8, increase = true ) => { - let base = BigNumber.from(baseFeePerGas) + let base = baseFeePerGas //baseFeePerGas can augment 12.5% per block for (let i = 0; i < maxBlocksWait; i++) - base = base.mul((1 + (increase ? 1 : -1) * FEE_MAX_RAISE_RATIO_PER_BLOCK) * 1000).div(1000) + base = (base * BigInt((1 + (increase ? 1 : -1) * FEE_MAX_RAISE_RATIO_PER_BLOCK) * 1000)) / 1000n - return base.add(maxPriorityFeePerGas) + return base + maxPriorityFeePerGas } export const getGasSettingsEip1559 = ( - baseFee: BigNumber, - maxPriorityFeePerGas: BigNumber, - gasLimit: BigNumber, + baseFee: bigint, + maxPriorityFeePerGas: bigint, + gas: bigint, maxBlocksWait?: number ): EthGasSettingsEip1559 => ({ - type: 2, + type: "eip1559", maxPriorityFeePerGas, maxFeePerGas: getMaxFeePerGas(baseFee, maxPriorityFeePerGas, maxBlocksWait), - gasLimit, + gas, }) export const getTotalFeesFromGasSettings = ( gasSettings: EthGasSettings, - estimatedGas: BigNumberish, - baseFeePerGas?: BigNumberish | null + estimatedGas: bigint, + baseFeePerGas?: bigint | null ) => { - if (gasSettings.type === 2) { - if (baseFeePerGas === undefined) + if (gasSettings.type === "eip1559") { + if (baseFeePerGas === undefined || baseFeePerGas === null) throw new Error("baseFeePerGas argument is required for type 2 fee computation") return { - estimatedFee: BigNumber.from( - BigNumber.from(baseFeePerGas).lt(gasSettings.maxFeePerGas) + estimatedFee: + (baseFeePerGas < gasSettings.maxFeePerGas ? baseFeePerGas - : gasSettings.maxFeePerGas - ) - .add(gasSettings.maxPriorityFeePerGas) - .mul( - BigNumber.from(estimatedGas).lt(gasSettings.gasLimit) - ? estimatedGas - : gasSettings.gasLimit - ), - maxFee: BigNumber.from(gasSettings.maxFeePerGas) - .add(gasSettings.maxPriorityFeePerGas) - .mul(gasSettings.gasLimit), + : gasSettings.maxFeePerGas + gasSettings.maxPriorityFeePerGas) * + (estimatedGas < gasSettings.gas ? estimatedGas : gasSettings.gas), + maxFee: (gasSettings.maxFeePerGas + gasSettings.maxPriorityFeePerGas) * gasSettings.gas, } } else { return { - estimatedFee: BigNumber.from(gasSettings.gasPrice).mul( - BigNumber.from(estimatedGas).lt(gasSettings.gasLimit) ? estimatedGas : gasSettings.gasLimit - ), - maxFee: BigNumber.from(gasSettings.gasPrice).mul(gasSettings.gasLimit), + estimatedFee: + gasSettings.gasPrice * (estimatedGas < gasSettings.gas ? estimatedGas : gasSettings.gas), + maxFee: gasSettings.gasPrice * gasSettings.gas, } } } -export const getMaxTransactionCost = (transaction: ethers.providers.TransactionRequest) => { - if (transaction.gasLimit === undefined) - throw new Error("gasLimit is required for fee computation") +export const getMaxTransactionCost = (transaction: TransactionRequest) => { + if (transaction.gas === undefined) throw new Error("gasLimit is required for fee computation") - const value = BigNumber.from(transaction.value ?? 0) + const value = transaction.value ?? 0n - if (transaction.type === 2) { + if (transaction.type === "eip1559") { if (transaction.maxFeePerGas === undefined) throw new Error("maxFeePerGas is required for type 2 fee computation") - return BigNumber.from(transaction.maxFeePerGas).mul(transaction.gasLimit).add(value) + return transaction.maxFeePerGas * transaction.gas + value } else { if (transaction.gasPrice === undefined) throw new Error("gasPrice is required for legacy fee computation") - return BigNumber.from(transaction.gasPrice).mul(transaction.gasLimit).add(value) + return transaction.gasPrice * transaction.gas + value } } export const prepareTransaction = ( - tx: ethers.providers.TransactionRequest, + txBase: TransactionRequestBase, gasSettings: EthGasSettings, nonce: number -) => { - const { - chainId, - data, - from, - to, - value = BigNumber.from(0), - accessList, - ccipReadEnabled, - customData, - } = tx - - const transaction: ethers.providers.TransactionRequest = { - chainId, - from, - to, - value, - nonce: BigNumber.from(nonce), - data, +): TransactionRequest => { + return { + ...txBase, ...gasSettings, + nonce, } - if (accessList) transaction.accessList = accessList - if (customData) transaction.customData = customData - if (ccipReadEnabled !== undefined) transaction.ccipReadEnabled = ccipReadEnabled - return transaction + // const { + // chainId, + // data, + // from, + // to, + // value = BigNumber.from(0), + // accessList, + // ccipReadEnabled, + // customData, + // } = tx + + // const transaction: ethers.providers.TransactionRequest = { + // chainId, + // from, + // to, + // value, + // nonce: BigNumber.from(nonce), + // data, + // ...gasSettings, + // } + // if (accessList) transaction.accessList = accessList + // if (customData) transaction.customData = customData + // if (ccipReadEnabled !== undefined) transaction.ccipReadEnabled = ccipReadEnabled + + // return transaction } const testNoScriptTag = (text?: string) => !text?.toLowerCase().includes("> = { [K in keyof T]: Promisify } -export type EthRequestGetBalance = PromisifyArray<[string, BlockTag]> +export type EthRequestGetBalance = PromisifyArray<[string, ethers.providers.BlockTag]> -export type EthRequestGetStorage = PromisifyArray<[string, BigNumberish, BlockTag]> +export type EthRequestGetStorage = PromisifyArray<[string, BigNumberish, ethers.providers.BlockTag]> -export type EthRequestGetTxCount = PromisifyArray<[string, BlockTag]> +export type EthRequestGetTxCount = PromisifyArray<[string, ethers.providers.BlockTag]> -export type EthRequestBlockTagOnly = PromisifyArray<[BlockTag]> +export type EthRequestBlockTagOnly = PromisifyArray<[ethers.providers.BlockTag]> export type EthRequestSendRawTx = PromisifyArray<[string]> -export type EthRequestCall = [TransactionRequest, Promise] +export type EthRequestCall = [ + ethers.providers.TransactionRequest, + Promise +] -export type EthRequestEstimateGas = [TransactionRequest, string] +export type EthRequestEstimateGas = [ethers.providers.TransactionRequest, string] -export type EthRequestGetBlock = PromisifyArray<[BlockTag, boolean]> +export type EthRequestGetBlock = PromisifyArray<[ethers.providers.BlockTag, boolean]> export type EthRequestTxHashOnly = PromisifyArray<[string]> export type EthRequestSign = [string, string] export type EthRequestRecoverAddress = [string, `0x${string}`] -export type EthRequestSendTx = [TransactionRequest] +export type EthRequestSendTx = [ethers.providers.TransactionRequest] export type EthRequestAddEthereumChain = [AddEthereumChainParameter] @@ -89,6 +98,21 @@ type TalismanRpcSchema = [ Parameters: [signedData: `0x${string}`, signature: `0x${string}`] ReturnType: EvmAddress }, + { + Method: "eth_signTypedData" + Parameters: [message: unknown[], from: EvmAddress] + ReturnType: `0x${string}` + }, + { + Method: "eth_signTypedData_v1" + Parameters: [message: unknown[], from: EvmAddress] + ReturnType: `0x${string}` + }, + { + Method: "eth_signTypedData_v3" + Parameters: [from: EvmAddress, message: string] + ReturnType: `0x${string}` + }, { // TODO see if we can remove this // override for now because of result type mismatch @@ -118,14 +142,14 @@ export interface EthRequestSignatures { eth_getTransactionCount: [EthRequestGetTxCount, string] eth_getBlockTransactionCountByHash: [EthRequestBlockTagOnly, string] eth_getBlockTransactionCountByNumber: [EthRequestBlockTagOnly, string] - eth_getCode: [EthRequestBlockTagOnly, Block] - eth_sendRawTransaction: [EthRequestSendRawTx, TransactionResponse] + eth_getCode: [EthRequestBlockTagOnly, ethers.providers.Block] + eth_sendRawTransaction: [EthRequestSendRawTx, ethers.providers.TransactionResponse] eth_call: [EthRequestCall, string] estimateGas: [EthRequestEstimateGas, string] - eth_getBlockByHash: [EthRequestGetBlock, Block | BlockWithTransactions] - eth_getBlockByNumber: [EthRequestGetBlock, Block | BlockWithTransactions] - eth_getTransactionByHash: [EthRequestTxHashOnly, TransactionResponse] - eth_getTransactionReceipt: [EthRequestTxHashOnly, TransactionReceipt] + eth_getBlockByHash: [EthRequestGetBlock, ethers.providers.Block | BlockWithTransactions] + eth_getBlockByNumber: [EthRequestGetBlock, ethers.providers.Block | BlockWithTransactions] + eth_getTransactionByHash: [EthRequestTxHashOnly, ethers.providers.TransactionResponse] + eth_getTransactionReceipt: [EthRequestTxHashOnly, ethers.providers.TransactionReceipt] personal_sign: [EthRequestSign, string] eth_signTypedData: [EthRequestSign, string] eth_signTypedData_v1: [EthRequestSign, string] @@ -160,24 +184,27 @@ export interface EthRequestSignatures { wallet_requestPermissions: [[RequestedPermissions], Web3WalletPermission[]] } -export type EthRequestTypes = keyof EthRequestSignatures +// export type EthRequestTypes = keyof EthRequestSignatures +// TODO yeet export type EthResponseTypes = EthRequestSignatures[keyof EthRequestSignatures][1] -export type EthResponseType = EthRequestSignatures[T][1] -export type EthRequestParams = EthRequestSignatures[keyof EthRequestSignatures][0] -export interface EthRequestArguments { - readonly method: T - readonly params: EthRequestSignatures[T][0] -} +//export type EthResponseType = EthRequestSignatures[T][1] +//export type EthRequestParams = EthRequestSignatures[keyof EthRequestSignatures][0] +// export interface EthRequestArguments { +// readonly method: T +// readonly params: EthRequestSignatures[T][0] +// } export type EthRequestArgumentsViem = { readonly method: T readonly params: EthRequestSignaturesFull[T][0] } +export type EthRequestArgsViem = EIP1193Parameters + export type EthRequestResultViem = EthRequestSignaturesFull[T][1] -export type EthRequestSignArguments = EthRequestArguments< +export type EthRequestSignArguments = EthRequestArgumentsViem< | "personal_sign" | "eth_signTypedData" | "eth_signTypedData_v1" @@ -206,17 +233,19 @@ export interface EthProviderMessage { // } export type EthTxSignAndSend = { - unsigned: ethers.providers.TransactionRequest + evmNetworkId: EvmNetworkId + unsigned: TransactionRequest transferInfo?: WalletTransactionTransferInfo } export type EthTxSendSigned = { - unsigned: ethers.providers.TransactionRequest + evmNetworkId: EvmNetworkId + unsigned: TransactionRequest signed: `0x${string}` transferInfo?: WalletTransactionTransferInfo } export declare type EthApproveSignAndSend = KnownSigningRequestIdOnly & { - transaction: ethers.providers.TransactionRequest + transaction: TransactionRequest } export type EthRequestSigningApproveSignature = KnownSigningRequestIdOnly & { @@ -224,7 +253,7 @@ export type EthRequestSigningApproveSignature = KnownSigningRequestIdOnly & { - unsigned: ethers.providers.TransactionRequest + unsigned: TransactionRequest signedPayload: `0x${string}` } @@ -299,18 +328,20 @@ export interface EthMessages { "pri(eth.networks.upsert)": [RequestUpsertCustomEvmNetwork, boolean] } -export type EthGasSettingsLegacy = { - type: 0 - gasLimit: BigNumberish - gasPrice: BigNumberish +export type EthGasSettingsLegacy = { + type: "legacy" | "eip2930" + gas: TQuantity + gasPrice: TQuantity } -export type EthGasSettingsEip1559 = { - type: 2 - gasLimit: BigNumberish - maxFeePerGas: BigNumberish - maxPriorityFeePerGas: BigNumberish +export type EthGasSettingsEip1559 = { + type: "eip1559" + gas: TQuantity + maxFeePerGas: TQuantity + maxPriorityFeePerGas: TQuantity } -export type EthGasSettings = EthGasSettingsLegacy | EthGasSettingsEip1559 +export type EthGasSettings = + | EthGasSettingsLegacy + | EthGasSettingsEip1559 export type LedgerEthDerivationPathType = "LedgerLive" | "Legacy" | "BIP44" diff --git a/apps/extension/src/core/domains/ethereum/viemMigration.ts b/apps/extension/src/core/domains/ethereum/viemMigration.ts index adf452d36d..d5e98b5e0a 100644 --- a/apps/extension/src/core/domains/ethereum/viemMigration.ts +++ b/apps/extension/src/core/domains/ethereum/viemMigration.ts @@ -1,71 +1,134 @@ // TODO delete this file after ethers => viem migration -import { log } from "@core/log" -import { BigNumber, ethers } from "ethers" -import { accessListify } from "ethers/lib/utils" -import { AccessList } from "viem" +// import { log } from "@core/log" +// import { BigNumber, ethers } from "ethers" +// import { accessListify } from "ethers/lib/utils" +// import { +// AccessList, +// RpcTransactionRequest, +// TransactionRequest, +// hexToBigInt, +// hexToNumber, +// isHex, +// } from "viem" +// // import { RecursiveArray } from "viem/_types/utils/encoding/toRlp" +// // import { parseAccessList } from "viem/_types/utils/transaction/parseTransaction" -import { EthGasSettings } from "./types" +// // import { EthGasSettings } from "./types" -type ViemGasSettings = - | { - type: "legacy" - gas?: bigint - gasPrice?: bigint - } - | { - type: "eip1559" - gas?: bigint - maxFeePerGas?: bigint - maxPriorityFeePerGas?: bigint - } +// // type ViemGasSettings = +// // | { +// // type: "legacy" +// // gas?: bigint +// // gasPrice?: bigint +// // } +// // | { +// // type: "eip1559" +// // gas?: bigint +// // maxFeePerGas?: bigint +// // maxPriorityFeePerGas?: bigint +// // } -export const ethGasSettingsToViemGasSettings = (settings: EthGasSettings): ViemGasSettings => { - return settings.type === 2 - ? ({ - type: "eip1559", - gas: settings.gasLimit ? BigNumber.from(settings.gasLimit).toBigInt() : undefined, - maxFeePerGas: settings.maxFeePerGas - ? BigNumber.from(settings.maxFeePerGas).toBigInt() - : undefined, - maxPriorityFeePerGas: settings.maxPriorityFeePerGas - ? BigNumber.from(settings.maxPriorityFeePerGas).toBigInt() - : undefined, - } as const) - : ({ - type: "legacy", - gas: settings.gasLimit ? BigNumber.from(settings.gasLimit).toBigInt() : undefined, - gasPrice: settings.gasPrice ? BigNumber.from(settings.gasPrice).toBigInt() : undefined, - } as const) -} +// // export const ethGasSettingsToViemGasSettings = (settings: EthGasSettings): ViemGasSettings => { +// // return settings.type === 2 +// // ? ({ +// // type: "eip1559", +// // gas: settings.gasLimit ? BigNumber.from(settings.gasLimit).toBigInt() : undefined, +// // maxFeePerGas: settings.maxFeePerGas +// // ? BigNumber.from(settings.maxFeePerGas).toBigInt() +// // : undefined, +// // maxPriorityFeePerGas: settings.maxPriorityFeePerGas +// // ? BigNumber.from(settings.maxPriorityFeePerGas).toBigInt() +// // : undefined, +// // } as const) +// // : ({ +// // type: "legacy", +// // gas: settings.gasLimit ? BigNumber.from(settings.gasLimit).toBigInt() : undefined, +// // gasPrice: settings.gasPrice ? BigNumber.from(settings.gasPrice).toBigInt() : undefined, +// // } as const) +// // } -export const getViemGasSettings = (tx: ethers.providers.TransactionRequest) => { - return tx.type === 2 - ? ({ - type: "eip1559", - gas: tx.gasLimit ? BigNumber.from(tx.gasLimit).toBigInt() : undefined, - maxFeePerGas: tx.maxFeePerGas ? BigNumber.from(tx.maxFeePerGas).toBigInt() : undefined, - maxPriorityFeePerGas: tx.maxPriorityFeePerGas - ? BigNumber.from(tx.maxPriorityFeePerGas).toBigInt() - : undefined, - accessList: tx.accessList ? (accessListify(tx.accessList) as AccessList) : undefined, - } as const) - : ({ - type: "legacy" as const, - gas: tx.gasLimit ? BigNumber.from(tx.gasLimit).toBigInt() : undefined, - gasPrice: tx.gasPrice ? BigNumber.from(tx.gasPrice).toBigInt() : undefined, - } as const) -} +// export const getViemGasSettings = (tx: ethers.providers.TransactionRequest) => { +// return tx.type === 2 +// ? ({ +// type: "eip1559", +// gas: tx.gasLimit ? BigNumber.from(tx.gasLimit).toBigInt() : undefined, +// maxFeePerGas: tx.maxFeePerGas ? BigNumber.from(tx.maxFeePerGas).toBigInt() : undefined, +// maxPriorityFeePerGas: tx.maxPriorityFeePerGas +// ? BigNumber.from(tx.maxPriorityFeePerGas).toBigInt() +// : undefined, +// accessList: tx.accessList ? (accessListify(tx.accessList) as AccessList) : undefined, +// } as const) +// : ({ +// type: "legacy" as const, +// gas: tx.gasLimit ? BigNumber.from(tx.gasLimit).toBigInt() : undefined, +// gasPrice: tx.gasPrice ? BigNumber.from(tx.gasPrice).toBigInt() : undefined, +// } as const) +// } -export const getViemSendTransactionParams = (ethersTx: ethers.providers.TransactionRequest) => { - const viemTx = { - to: ethersTx.to ? (ethersTx.to as `0x${string}`) : undefined, - data: ethersTx.data ? (ethersTx.data as `0x${string}`) : undefined, - value: ethersTx.value !== undefined ? BigNumber.from(ethersTx.value).toBigInt() : undefined, - nonce: ethersTx.nonce !== undefined ? BigNumber.from(ethersTx.nonce).toNumber() : undefined, - ...getViemGasSettings(ethersTx), - } +// export const getViemSendTransactionParams = (ethersTx: ethers.providers.TransactionRequest) => { +// const viemTx = { +// to: ethersTx.to ? (ethersTx.to as `0x${string}`) : undefined, +// data: ethersTx.data ? (ethersTx.data as `0x${string}`) : undefined, +// value: ethersTx.value !== undefined ? BigNumber.from(ethersTx.value).toBigInt() : undefined, +// nonce: ethersTx.nonce !== undefined ? BigNumber.from(ethersTx.nonce).toNumber() : undefined, +// ...getViemGasSettings(ethersTx), +// } - log.log("getViemSendTransactionParams", { ethersTx, viemTx }) - return viemTx -} +// log.log("getViemSendTransactionParams", { ethersTx, viemTx }) +// return viemTx +// } + +// export const parseTransactionRequest = (rtx: RpcTransactionRequest): TransactionRequest => { +// switch (rtx.type) { +// case "0x0": { +// const tx: TransactionRequest = { +// type: "legacy", +// from: rtx.from, +// } +// if (isHex(rtx.to)) tx.to = rtx.to +// if (isHex(rtx.data)) tx.data = rtx.data +// if (isHex(rtx.value)) tx.value = hexToBigInt(rtx.value) +// if (isHex(rtx.nonce)) tx.nonce = hexToNumber(rtx.nonce) +// if (isHex(rtx.gas)) tx.gas = hexToBigInt(rtx.gas) +// if (isHex(rtx.gasPrice)) tx.gasPrice = hexToBigInt(rtx.gasPrice) +// return tx +// } +// case "0x1": { +// const tx: TransactionRequest = { +// type: "eip2930", +// from: rtx.from, +// } +// if (isHex(rtx.to)) tx.to = rtx.to +// if (isHex(rtx.data)) tx.data = rtx.data +// if (isHex(rtx.value)) tx.value = hexToBigInt(rtx.value) +// if (isHex(rtx.nonce)) tx.nonce = hexToNumber(rtx.nonce) +// if (isHex(rtx.gas)) tx.gas = hexToBigInt(rtx.gas) +// if (isHex(rtx.gasPrice)) tx.gasPrice = hexToBigInt(rtx.gasPrice) +// if (rtx.accessList) tx.accessList = rtx.accessList +// return tx +// } +// case "0x2": { +// const tx: TransactionRequest = { +// type: "eip1559", +// from: rtx.from, +// } +// if (isHex(rtx.to)) tx.to = rtx.to +// if (isHex(rtx.data)) tx.data = rtx.data +// if (isHex(rtx.value)) tx.value = hexToBigInt(rtx.value) +// if (isHex(rtx.nonce)) tx.nonce = hexToNumber(rtx.nonce) +// if (isHex(rtx.gas)) tx.gas = hexToBigInt(rtx.gas) +// if (isHex(rtx.maxFeePerGas)) tx.maxFeePerGas = hexToBigInt(rtx.maxFeePerGas) +// if (isHex(rtx.maxPriorityFeePerGas)) +// tx.maxPriorityFeePerGas = hexToBigInt(rtx.maxPriorityFeePerGas) +// if (rtx.accessList) tx.accessList = rtx.accessList +// return tx +// } +// } + +// if (rtx.gasPrice && rtx.accessList) return parseTransactionRequest({ type: "0x1", ...rtx }) +// if (rtx.gasPrice) return parseTransactionRequest({ type: "0x0", ...rtx } as RpcTransactionRequest) +// return parseTransactionRequest({ type: "0x2", ...rtx } as RpcTransactionRequest) +// } + +export default 0 diff --git a/apps/extension/src/core/domains/signing/requests.ts b/apps/extension/src/core/domains/signing/requests.ts index 2a909bf6b3..73f7369a95 100644 --- a/apps/extension/src/core/domains/signing/requests.ts +++ b/apps/extension/src/core/domains/signing/requests.ts @@ -3,11 +3,11 @@ import { EvmNetworkId } from "@core/domains/ethereum/types" import type { EthSignRequest, SubstrateSigningRequest } from "@core/domains/signing/types" import { requestStore } from "@core/libs/requests/store" import type { Port } from "@core/types/base" -import type { TransactionRequest } from "@ethersproject/providers" +import { RpcTransactionRequest } from "viem" export const signAndSendEth = ( url: string, - request: TransactionRequest, + request: RpcTransactionRequest, ethChainId: EvmNetworkId, account: AccountJson, port: Port diff --git a/apps/extension/src/core/domains/signing/types.ts b/apps/extension/src/core/domains/signing/types.ts index ecd6dd8387..6260697d63 100644 --- a/apps/extension/src/core/domains/signing/types.ts +++ b/apps/extension/src/core/domains/signing/types.ts @@ -5,14 +5,13 @@ import { EvmNetworkId, } from "@core/domains/ethereum/types" import { BaseRequest, BaseRequestId } from "@core/types/base" -import type { TransactionRequest as EthTransactionRequest } from "@ethersproject/abstract-provider" import { RequestSigningApproveSignature as PolkadotRequestSigningApproveSignature, RequestSign, ResponseSigning, } from "@polkadot/extension-base/background/types" import type { SignerPayloadJSON, SignerPayloadRaw } from "@polkadot/types/types" -import { BigNumberish } from "ethers" +import { RpcTransactionRequest } from "viem" export type { ResponseSigning, SignerPayloadJSON, SignerPayloadRaw } // Make this available elsewhere also @@ -55,7 +54,7 @@ export interface SubstrateSigningRequest extends BaseSigningRequest extends BaseSigningRequest { ethChainId: EvmNetworkId account: AccountJsonAny - request: string | EthTransactionRequest + request: string | RpcTransactionRequest } export type ETH_SIGN = "eth-sign" @@ -84,7 +83,7 @@ export interface EthSignRequest extends EthBaseSignRequest { } export interface EthSignAndSendRequest extends EthBaseSignRequest { - request: EthTransactionRequest + request: RpcTransactionRequest ethChainId: EvmNetworkId method: "eth_sendTransaction" } @@ -152,11 +151,12 @@ export type GasSettingsByPriority = GasSettingsByPriorityEip1559 | GasSettingsBy export type EthBaseFeeTrend = "idle" | "decreasing" | "increasing" | "toTheMoon" export type EthTransactionDetails = { - estimatedGas: BigNumberish - gasPrice: BigNumberish - estimatedFee: BigNumberish - maxFee: BigNumberish // TODO yeet ! - baseFeePerGas?: BigNumberish | null + evmNetworkId: EvmNetworkId + estimatedGas: bigint + gasPrice: bigint + estimatedFee: bigint + maxFee: bigint // TODO yeet ! + baseFeePerGas?: bigint | null baseFeeTrend?: EthBaseFeeTrend } diff --git a/apps/extension/src/core/domains/transactions/helpers.ts b/apps/extension/src/core/domains/transactions/helpers.ts index 7ca403b2ad..d6280c9285 100644 --- a/apps/extension/src/core/domains/transactions/helpers.ts +++ b/apps/extension/src/core/domains/transactions/helpers.ts @@ -4,10 +4,10 @@ import { TypeRegistry } from "@polkadot/types" import { HexString } from "@polkadot/util/types" import { SignerPayloadJSON } from "@substrate/txwrapper-core" import { Address } from "@talismn/balances" -import { ethers } from "ethers" +import { EvmNetworkId } from "@talismn/chaindata-provider" import merge from "lodash/merge" +import { Hex, TransactionRequest } from "viem" -import { serializeTransactionRequestBigNumbers } from "../ethereum/helpers" import { TransactionStatus } from "./types" type AddTransactionOptions = { @@ -23,23 +23,24 @@ const DEFAULT_OPTIONS: AddTransactionOptions = { } export const addEvmTransaction = async ( - hash: string, - unsigned: ethers.providers.TransactionRequest, + evmNetworkId: EvmNetworkId, + hash: Hex, + unsigned: TransactionRequest, options: AddTransactionOptions = {} ) => { const { siteUrl, label, tokenId, value, to } = merge(structuredClone(DEFAULT_OPTIONS), options) try { - if (!unsigned.chainId || !unsigned.from || unsigned.nonce === undefined) + if (!evmNetworkId || !unsigned.from || unsigned.nonce === undefined) throw new Error("Invalid transaction") - const evmNetworkId = String(unsigned.chainId) - const nonce = ethers.BigNumber.from(unsigned.nonce).toNumber() const isReplacement = (await db.transactions .filter( (row) => - row.networkType === "evm" && row.evmNetworkId === evmNetworkId && row.nonce === nonce + row.networkType === "evm" && + row.evmNetworkId === evmNetworkId && + row.nonce === unsigned.nonce ) .count()) > 0 @@ -48,9 +49,9 @@ export const addEvmTransaction = async ( networkType: "evm", evmNetworkId, account: unsigned.from, - nonce, + nonce: unsigned.nonce, isReplacement, - unsigned: serializeTransactionRequestBigNumbers(unsigned), + unsigned, status: "pending", siteUrl, label, diff --git a/apps/extension/src/core/domains/transactions/types.ts b/apps/extension/src/core/domains/transactions/types.ts index f30ac611a9..4d3f660896 100644 --- a/apps/extension/src/core/domains/transactions/types.ts +++ b/apps/extension/src/core/domains/transactions/types.ts @@ -1,7 +1,7 @@ import { SignerPayloadJSON } from "@core/domains/signing/types" import { Address } from "@talismn/balances" import { EvmNetworkId, TokenId } from "@talismn/chaindata-provider" -import { ethers } from "ethers" +import { TransactionRequest } from "viem" // unknown for substrate txs from dapps export type TransactionStatus = "unknown" | "pending" | "success" | "error" | "replaced" @@ -33,7 +33,7 @@ export type WalletTransactionBase = WalletTransactionTransferInfo & { export type EvmWalletTransaction = WalletTransactionBase & { networkType: "evm" evmNetworkId: EvmNetworkId - unsigned: ethers.providers.TransactionRequest + unsigned: TransactionRequest } export type SubWalletTransaction = WalletTransactionBase & { diff --git a/apps/extension/src/core/domains/transactions/watchEthereumTransaction.ts b/apps/extension/src/core/domains/transactions/watchEthereumTransaction.ts index 6e85bbf3f7..84cf8cf4a0 100644 --- a/apps/extension/src/core/domains/transactions/watchEthereumTransaction.ts +++ b/apps/extension/src/core/domains/transactions/watchEthereumTransaction.ts @@ -5,27 +5,27 @@ import { chainConnectorEvm } from "@core/rpcs/chain-connector-evm" import { chaindataProvider } from "@core/rpcs/chaindata" import * as Sentry from "@sentry/browser" import { EvmNetworkId } from "@talismn/chaindata-provider" -import { ethers } from "ethers" import { nanoid } from "nanoid" import urlJoin from "url-join" +import { TransactionRequest } from "viem" import { WatchTransactionOptions } from "./types" export const watchEthereumTransaction = async ( - ethChainId: EvmNetworkId, + evmNetworkId: EvmNetworkId, hash: `0x${string}`, - unsigned: ethers.providers.TransactionRequest, + unsigned: TransactionRequest, options: WatchTransactionOptions = {} ) => { try { const { siteUrl, notifications, transferInfo = {} } = options const withNotifications = !!(notifications && (await settingsStore.get("allowNotifications"))) - const ethereumNetwork = await chaindataProvider.getEvmNetwork(ethChainId) - if (!ethereumNetwork) throw new Error(`Could not find ethereum network ${ethChainId}`) + const ethereumNetwork = await chaindataProvider.getEvmNetwork(evmNetworkId) + if (!ethereumNetwork) throw new Error(`Could not find ethereum network ${evmNetworkId}`) - const client = await chainConnectorEvm.getPublicClientForEvmNetwork(ethChainId) - if (!client) throw new Error(`No client for network ${ethChainId} (${ethereumNetwork.name})`) + const client = await chainConnectorEvm.getPublicClientForEvmNetwork(evmNetworkId) + if (!client) throw new Error(`No client for network ${evmNetworkId} (${ethereumNetwork.name})`) const networkName = ethereumNetwork.name ?? "unknown network" const txUrl = ethereumNetwork.explorerUrl @@ -36,7 +36,7 @@ export const watchEthereumTransaction = async ( if (withNotifications) await createNotification("submitted", networkName, txUrl) try { - await addEvmTransaction(hash, unsigned, { siteUrl, ...transferInfo }) + await addEvmTransaction(evmNetworkId, hash, unsigned, { siteUrl, ...transferInfo }) const receipt = await client.waitForTransactionReceipt({ hash, @@ -64,6 +64,6 @@ export const watchEthereumTransaction = async ( else console.error("Failed to watch transaction", { err }) } } catch (err) { - Sentry.captureException(err, { tags: { ethChainId } }) + Sentry.captureException(err, { tags: { ethChainId: evmNetworkId } }) } } diff --git a/apps/extension/src/core/domains/transfers/handler.ts b/apps/extension/src/core/domains/transfers/handler.ts index 5fce7935c0..36a1ee2186 100644 --- a/apps/extension/src/core/domains/transfers/handler.ts +++ b/apps/extension/src/core/domains/transfers/handler.ts @@ -1,4 +1,4 @@ -import { getEthTransferTransactionBase } from "@core/domains/ethereum/helpers" +import { getEthTransferTransactionBase, parseGasSettings } from "@core/domains/ethereum/helpers" import { getTransactionCount, incrementTransactionCount, @@ -26,7 +26,6 @@ import * as Sentry from "@sentry/browser" import { isEthereumAddress, planckToTokens } from "@talismn/util" import { privateKeyToAccount } from "viem/accounts" -import { ethGasSettingsToViemGasSettings } from "../ethereum/viemMigration" import { transferAnalytics } from "./helpers" export default class AssetTransferHandler extends ExtensionHandler { @@ -211,13 +210,8 @@ export default class AssetTransferHandler extends ExtensionHandler { BigInt(amount ?? 0) ) - const viemGasSettings = ethGasSettingsToViemGasSettings(gasSettings) - - const transaction = { - ...transfer, - ...viemGasSettings, - nonce: await getTransactionCount(fromAddress, evmNetworkId), - } + const parsedGasSettings = parseGasSettings(gasSettings) + const nonce = await getTransactionCount(fromAddress, evmNetworkId) const result = await getPairForAddressSafely(fromAddress, async (pair) => { const client = await chainConnectorEvm.getWalletClientForEvmNetwork(evmNetworkId) @@ -232,7 +226,9 @@ export default class AssetTransferHandler extends ExtensionHandler { const hash = await client.sendTransaction({ chain: client.chain, account, - ...transaction, + ...transfer, + ...parsedGasSettings, + nonce, }) incrementTransactionCount(fromAddress, evmNetworkId) diff --git a/apps/extension/src/core/domains/transfers/types.ts b/apps/extension/src/core/domains/transfers/types.ts index 881838d7f2..d90a800a1f 100644 --- a/apps/extension/src/core/domains/transfers/types.ts +++ b/apps/extension/src/core/domains/transfers/types.ts @@ -3,7 +3,7 @@ import { SignerPayloadJSON } from "@core/domains/signing/types" import { TokenId } from "@core/domains/tokens/types" import { HexString } from "@polkadot/util/types" import { Address } from "@talismn/balances" -import { ethers } from "ethers" +import { TransactionRequest } from "viem" import { EthGasSettings, EvmNetworkId } from "../ethereum/types" import { WalletTransactionTransferInfo } from "../transactions" @@ -25,14 +25,14 @@ export interface RequestAssetTransferEth { fromAddress: string toAddress: string amount: string - gasSettings: EthGasSettings + gasSettings: EthGasSettings } export interface RequestAssetTransferEthHardware { evmNetworkId: EvmNetworkId tokenId: TokenId amount: string to: Address - unsigned: ethers.providers.TransactionRequest + unsigned: TransactionRequest signedTransaction: `0x${string}` } diff --git a/apps/extension/src/core/util/getEthTransactionInfo.ts b/apps/extension/src/core/util/getEthTransactionInfo.ts index a733fccfcf..72f8d9f1a0 100644 --- a/apps/extension/src/core/util/getEthTransactionInfo.ts +++ b/apps/extension/src/core/util/getEthTransactionInfo.ts @@ -2,7 +2,7 @@ import { EvmAddress } from "@core/domains/ethereum/types" import * as Sentry from "@sentry/browser" import { getContractCallArg } from "@ui/domains/Sign/Ethereum/getContractCallArg" import { BigNumber, ethers } from "ethers" -import { PublicClient, getAddress, getContract, parseAbi } from "viem" +import { PublicClient, TransactionRequestBase, getAddress, getContract, parseAbi } from "viem" import { abiErc1155, abiErc20, abiErc721, abiMoonStaking } from "./abi" import { abiMoonConvictionVoting } from "./abi/abiMoonConvictionVoting" @@ -81,7 +81,7 @@ export type KnownTransactionInfo = Required export const getEthTransactionInfo = async ( publicClient: PublicClient, - tx: ethers.providers.TransactionRequest + tx: TransactionRequestBase ): Promise => { // transactions that provision a contract have an empty 'to' field const targetAddress = tx.to ? getAddress(tx.to) : undefined @@ -101,8 +101,8 @@ export const getEthTransactionInfo = async ( if ( tx.data && targetAddress && - tx.chainId && - [1284, 1285, 1287].includes(tx.chainId) && + publicClient?.chain?.id && + [1284, 1285, 1287].includes(publicClient.chain.id) && !!MOON_CHAIN_PRECOMPILE_ADDRESSES[targetAddress] ) { const { contractType, abi } = MOON_CHAIN_PRECOMPILE_ADDRESSES[targetAddress] @@ -118,7 +118,7 @@ export const getEthTransactionInfo = async ( return result } catch (err) { - Sentry.captureException(err, { extra: { to: tx.to, chainId: tx.chainId } }) + Sentry.captureException(err, { extra: { to: tx.to, chainId: publicClient.chain.id } }) } } @@ -154,23 +154,24 @@ export const getEthTransactionInfo = async ( decimals, } } else if (contractType === "ERC721") { - const tokenId = getContractCallArg(contractCall, "tokenId")!.toBigInt() - - try { - const contract = getContract({ - address: targetAddress, - abi: KNOWN_ABI.ERC721, - publicClient, - }) - const [name, symbol, tokenURI] = await Promise.all([ - contract.read.name(), - contract.read.symbol(), - tokenId ? contract.read.tokenURI([tokenId]) : undefined, - ]) - - result.asset = { name, symbol, tokenId, tokenURI, decimals: 1 } - } catch (err) { - // some NFTs don't implement the metadata functions + const tokenId = getContractCallArg(contractCall, "tokenId")?.toBigInt() + if (tokenId) { + try { + const contract = getContract({ + address: targetAddress, + abi: KNOWN_ABI.ERC721, + publicClient, + }) + const [name, symbol, tokenURI] = await Promise.all([ + contract.read.name(), + contract.read.symbol(), + tokenId ? contract.read.tokenURI([tokenId]) : undefined, + ]) + + result.asset = { name, symbol, tokenId, tokenURI, decimals: 1 } + } catch (err) { + // some NFTs don't implement the metadata functions + } } } diff --git a/apps/extension/src/core/util/getFeeHistoryAnalysis.ts b/apps/extension/src/core/util/getFeeHistoryAnalysis.ts index 0d757cad62..24d1470f42 100644 --- a/apps/extension/src/core/util/getFeeHistoryAnalysis.ts +++ b/apps/extension/src/core/util/getFeeHistoryAnalysis.ts @@ -1,75 +1,85 @@ import { EthBaseFeeTrend } from "@core/domains/signing/types" import * as Sentry from "@sentry/browser" -import { BigNumber, ethers } from "ethers" -import { parseUnits } from "ethers/lib/utils" +import { PublicClient, parseGwei } from "viem" const BLOCKS_HISTORY_LENGTH = 4 const REWARD_PERCENTILES = [10, 20, 30] -type EthBasePriorityOptionsEip1559 = Record<"low" | "medium" | "high", BigNumber> +type EthBasePriorityOptionsEip1559 = Record<"low" | "medium" | "high", bigint> export const DEFAULT_ETH_PRIORITY_OPTIONS: EthBasePriorityOptionsEip1559 = { - low: parseUnits("1.5", "gwei"), - medium: parseUnits("1.6", "gwei"), - high: parseUnits("1.7", "gwei"), + low: parseGwei("1.5"), + medium: parseGwei("1.6"), + high: parseGwei("1.7"), } -type FeeHistory = { - oldestBlock: number - baseFeePerGas: BigNumber[] - gasUsedRatio: (number | null)[] // can have null values (ex astar) - reward?: BigNumber[][] // TODO find network that doesn't return this property, for testing -} +// type FeeHistory = { +// oldestBlock: bigint +// baseFeePerGas: bigint[] +// gasUsedRatio: (number)[] // can have null values (ex astar) +// reward?: BigNumber[][] // TODO find network that doesn't return this property, for testing +// } export type FeeHistoryAnalysis = { maxPriorityPerGasOptions: EthBasePriorityOptionsEip1559 - avgGasUsedRatio: number | null + avgGasUsedRatio: number isValid: boolean - avgBaseFeePerGas: BigNumber + avgBaseFeePerGas: bigint isBaseFeeIdle: boolean - nextBaseFee: BigNumber + nextBaseFee: bigint baseFeeTrend: EthBaseFeeTrend } export const getFeeHistoryAnalysis = async ( - provider: ethers.providers.JsonRpcProvider + publicClient: PublicClient ): Promise => { try { - const rawHistoryFee = await provider.send("eth_feeHistory", [ - ethers.utils.hexValue(BLOCKS_HISTORY_LENGTH), - "latest", - REWARD_PERCENTILES, - ]) + // const rawHistoryFee = await provider.send("eth_feeHistory", [ + // ethers.utils.hexValue(BLOCKS_HISTORY_LENGTH), + // "latest", + // REWARD_PERCENTILES, + // ]) + const feeHistory = await publicClient.getFeeHistory({ + blockTag: "latest", + blockCount: BLOCKS_HISTORY_LENGTH, + rewardPercentiles: REWARD_PERCENTILES, + }) // instrument for information - remove asap - if (!rawHistoryFee.reward) - Sentry.captureMessage(`No reward on fee history`, { extra: { chain: provider.network.name } }) + // if (!feeHistory.reward) + // Sentry.captureMessage(`No reward on fee history`, { + // extra: { chain: publicClient.chain?.id }, + // }) // parse hex values - const feeHistory: FeeHistory = { - oldestBlock: parseInt(rawHistoryFee.oldestBlock, 16), - baseFeePerGas: rawHistoryFee.baseFeePerGas.map((fee: string) => BigNumber.from(fee)), - gasUsedRatio: rawHistoryFee.gasUsedRatio as (number | null)[], - reward: rawHistoryFee.reward - ? rawHistoryFee.reward.map((reward: string[]) => reward.map((r) => BigNumber.from(r))) - : null, - } + // const feeHistory: FeeHistory = { + // oldestBlock: parseInt(rawHistoryFee.oldestBlock, 16), + // baseFeePerGas: rawHistoryFee.baseFeePerGas.map((fee: string) => BigNumber.from(fee)), + // gasUsedRatio: rawHistoryFee.gasUsedRatio as (number | null)[], + // reward: rawHistoryFee.reward + // ? rawHistoryFee.reward.map((reward: string[]) => reward.map((r) => BigNumber.from(r))) + // : null, + // } // how busy the network is over this period // values can be null (ex astar) - const avgGasUsedRatio = feeHistory.gasUsedRatio.includes(null) - ? null - : (feeHistory.gasUsedRatio as number[]).reduce((prev, curr) => prev + curr, 0) / - feeHistory.gasUsedRatio.length + // TODO check that with viem values can't be null + // const avgGasUsedRatio = feeHistory.gasUsedRatio.includes(null) + // ? null + // : (feeHistory.gasUsedRatio as number[]).reduce((prev, curr) => prev + curr, 0) / + // feeHistory.gasUsedRatio.length + const avgGasUsedRatio = + (feeHistory.gasUsedRatio as number[]).reduce((prev, curr) => prev + curr, 0) / + feeHistory.gasUsedRatio.length // lookup the max priority fee per gas based on our percentiles options // use a median to exclude extremes, to limits edge cases in low network activity conditions - const medMaxPriorityFeePerGas: BigNumber[] = [] + const medMaxPriorityFeePerGas: bigint[] = [] if (feeHistory.reward) { const percentilesCount = REWARD_PERCENTILES.length for (let i = 0; i < percentilesCount; i++) { - const values = feeHistory.reward.map((arr) => BigNumber.from(arr[i])) - const sorted = values.sort((a, b) => (a.eq(b) ? 0 : a.gt(b) ? 1 : -1)) + const values = feeHistory.reward.map((arr) => arr[i]) + const sorted = values.sort((a, b) => (a === b ? 0 : a > b ? 1 : -1)) const median = sorted[Math.floor((sorted.length - 1) / 2)] medMaxPriorityFeePerGas.push(median) } @@ -81,17 +91,17 @@ export const getFeeHistoryAnalysis = async ( ) // last entry of the array is the base fee for next block, exclude it from further averages - const nextBaseFee = feeHistory.baseFeePerGas.pop() as BigNumber + const nextBaseFee = feeHistory.baseFeePerGas.pop() as bigint - const isBaseFeeIdle = feeHistory.baseFeePerGas.every((fee) => fee.eq(nextBaseFee)) + const isBaseFeeIdle = feeHistory.baseFeePerGas.every((fee) => fee === nextBaseFee) - const avgBaseFeePerGas = feeHistory.baseFeePerGas - .reduce((prev, curr) => prev.add(curr), BigNumber.from(0)) - .div(feeHistory.baseFeePerGas.length) + const avgBaseFeePerGas = + feeHistory.baseFeePerGas.reduce((prev, curr) => prev + curr, 0n) / + BigInt(feeHistory.baseFeePerGas.length) const baseFeeTrend = isBaseFeeIdle ? "idle" - : nextBaseFee.lt(avgBaseFeePerGas) + : nextBaseFee < avgBaseFeePerGas ? "decreasing" : !avgGasUsedRatio || avgGasUsedRatio < 0.9 ? "increasing" diff --git a/apps/extension/src/ui/api/api.ts b/apps/extension/src/ui/api/api.ts index 3d523ccd87..2f1465c2da 100644 --- a/apps/extension/src/ui/api/api.ts +++ b/apps/extension/src/ui/api/api.ts @@ -260,10 +260,19 @@ export const api: MessageTypes = { }), // eth related messages - ethSignAndSend: (unsigned, transferInfo) => - messageService.sendMessage("pri(eth.signing.signAndSend)", { unsigned, transferInfo }), - ethSendSigned: (unsigned, signed, transferInfo) => - messageService.sendMessage("pri(eth.signing.sendSigned)", { unsigned, signed, transferInfo }), + ethSignAndSend: (evmNetworkId, unsigned, transferInfo) => + messageService.sendMessage("pri(eth.signing.signAndSend)", { + evmNetworkId, + unsigned, + transferInfo, + }), + ethSendSigned: (evmNetworkId, unsigned, signed, transferInfo) => + messageService.sendMessage("pri(eth.signing.sendSigned)", { + evmNetworkId, + unsigned, + signed, + transferInfo, + }), ethApproveSign: (id) => messageService.sendMessage("pri(eth.signing.approveSign)", { id, diff --git a/apps/extension/src/ui/api/types.ts b/apps/extension/src/ui/api/types.ts index 395062dd21..0ef81b5af8 100644 --- a/apps/extension/src/ui/api/types.ts +++ b/apps/extension/src/ui/api/types.ts @@ -26,7 +26,7 @@ import { } from "@core/domains/balances/types" import { ChainId, RequestUpsertCustomChain } from "@core/domains/chains/types" import type { DecryptRequestId, EncryptRequestId } from "@core/domains/encrypt/types" -import { AddEthereumChainRequestId } from "@core/domains/ethereum/types" +import { AddEthereumChainRequestId, EvmAddress } from "@core/domains/ethereum/types" import { AddEthereumChainRequest, AnyEthRequestChainId, @@ -64,8 +64,7 @@ import { AddressesByChain } from "@core/types/base" import type { KeyringPair$Json } from "@polkadot/keyring/types" import { KeypairType } from "@polkadot/util-crypto/types" import type { HexString } from "@polkadot/util/types" -import { Address } from "@talismn/balances" -import { ethers } from "ethers" +import { TransactionRequest } from "viem" export default interface MessageTypes { unsubscribe: (id: string) => Promise @@ -225,17 +224,17 @@ export default interface MessageTypes { assetTransferEth: ( evmNetworkId: EvmNetworkId, tokenId: TokenId, - fromAddress: string, - toAddress: string, + fromAddress: EvmAddress, + toAddress: EvmAddress, amount: string, - gasSettings: EthGasSettings + gasSettings: EthGasSettings ) => Promise assetTransferEthHardware: ( evmNetworkId: EvmNetworkId, tokenId: TokenId, amount: string, - to: Address, - unsigned: ethers.providers.TransactionRequest, + to: EvmAddress, + unsigned: TransactionRequest, signedTransaction: HexString ) => Promise assetTransferCheckFees: ( @@ -255,11 +254,13 @@ export default interface MessageTypes { // eth related messages ethSignAndSend: ( - unsigned: ethers.providers.TransactionRequest, + evmNetworkId: EvmNetworkId, + unsigned: TransactionRequest, transferInfo?: WalletTransactionTransferInfo ) => Promise ethSendSigned: ( - unsigned: ethers.providers.TransactionRequest, + evmNetworkId: EvmNetworkId, + unsigned: TransactionRequest, signed: HexString, transferInfo?: WalletTransactionTransferInfo ) => Promise @@ -270,11 +271,11 @@ export default interface MessageTypes { ) => Promise ethApproveSignAndSend: ( id: SigningRequestID<"eth-send">, - transaction: ethers.providers.TransactionRequest + transaction: TransactionRequest ) => Promise ethApproveSignAndSendHardware: ( id: SigningRequestID<"eth-send">, - unsigned: ethers.providers.TransactionRequest, + unsigned: TransactionRequest, signedTransaction: HexString ) => Promise ethCancelSign: (id: SigningRequestID<"eth-sign" | "eth-send">) => Promise diff --git a/apps/extension/src/ui/apps/popup/pages/Sign/ethereum/Transaction.tsx b/apps/extension/src/ui/apps/popup/pages/Sign/ethereum/Transaction.tsx index 4963ef7729..f08bf45f55 100644 --- a/apps/extension/src/ui/apps/popup/pages/Sign/ethereum/Transaction.tsx +++ b/apps/extension/src/ui/apps/popup/pages/Sign/ethereum/Transaction.tsx @@ -1,6 +1,7 @@ import { EthPriorityOptionName } from "@core/domains/signing/types" import { AppPill } from "@talisman/components/AppPill" import { WithTooltip } from "@talisman/components/Tooltip" +import { EvmNetworkId, TokenId } from "@talismn/chaindata-provider" import { InfoIcon } from "@talismn/icons" import { PopupContent, @@ -11,20 +12,21 @@ import { import { TokensAndFiat } from "@ui/domains/Asset/TokensAndFiat" import { EthFeeSelect } from "@ui/domains/Ethereum/GasSettings/EthFeeSelect" import { useEthBalance } from "@ui/domains/Ethereum/useEthBalance" -import { useEthereumProvider } from "@ui/domains/Ethereum/useEthereumProvider" +import { usePublicClient } from "@ui/domains/Ethereum/useEthereumProvider" import { EthSignBody } from "@ui/domains/Sign/Ethereum/EthSignBody" import { SignAlertMessage } from "@ui/domains/Sign/SignAlertMessage" import { SignHardwareEthereum } from "@ui/domains/Sign/SignHardwareEthereum" import { useEthSignTransactionRequest } from "@ui/domains/Sign/SignRequestContext" +import { useAlec } from "@ui/hooks/useAlec" import { Suspense, useCallback, useEffect, useMemo } from "react" import { useTranslation } from "react-i18next" import { Button, Tooltip, TooltipContent, TooltipTrigger } from "talisman-ui" import { SignAccountAvatar } from "../SignAccountAvatar" -const useEvmBalance = (address: string, evmNetworkId: string | undefined) => { - const provider = useEthereumProvider(evmNetworkId) - return useEthBalance(provider, address) +const useEvmBalance = (address: string, evmNetworkId: EvmNetworkId | undefined) => { + const publicClient = usePublicClient(evmNetworkId) + return useEthBalance(publicClient, address) } const FeeTooltip = ({ @@ -33,10 +35,10 @@ const FeeTooltip = ({ tokenId, balance, }: { - estimatedFee: string | bigint | undefined - maxFee: string | bigint | undefined - tokenId: string | undefined - balance: string | bigint | null | undefined + estimatedFee: bigint | undefined + maxFee: bigint | undefined + tokenId: TokenId | undefined + balance: bigint | null | undefined }) => { const { t } = useTranslation("request") @@ -130,6 +132,8 @@ export const EthSignTransactionRequest = () => { [setPriority, setReady] ) + useAlec("EthSignTransactionRequest", { transaction, txDetails, network }) + return ( }> @@ -164,14 +168,14 @@ export const EthSignTransactionRequest = () => { -
{transaction?.type === 2 && t("Priority")}
+
{transaction?.type === "eip1559" && t("Priority")}
@@ -182,7 +186,7 @@ export const EthSignTransactionRequest = () => {
({ - type: 2, - maxFeePerGas: BigNumber.from( + type: "eip1559", + maxFeePerGas: BigInt( Math.round((formData.maxBaseFee + formData.maxPriorityFee) * Math.pow(10, 9)) ), - maxPriorityFeePerGas: BigNumber.from(Math.round(formData.maxPriorityFee * Math.pow(10, 9))), - gasLimit: BigNumber.from(formData.gasLimit), + maxPriorityFeePerGas: BigInt(Math.round(formData.maxPriorityFee * Math.pow(10, 9))), + gas: BigInt(formData.gasLimit), }) const schema = yup @@ -51,7 +53,8 @@ const schema = yup .required() const useIsValidGasSettings = ( - tx: ethers.providers.TransactionRequest, + evmNetworkId: EvmNetworkId, + tx: TransactionRequest, maxBaseFee: number, maxPriorityFee: number, gasLimit: number @@ -79,7 +82,7 @@ const useIsValidGasSettings = ( [maxBaseFee, maxPriorityFee, gasLimit] ) - const provider = useEthereumProvider(tx.chainId?.toString()) + const publicClient = usePublicClient(evmNetworkId) const txPrepared = useMemo(() => { try { @@ -87,7 +90,7 @@ const useIsValidGasSettings = ( return { ...tx, ...gasSettingsFromFormData(debouncedFormData), - } as ethers.providers.TransactionRequest + } as TransactionRequest } catch (err) { // any bad input throws here, ignore return undefined @@ -95,7 +98,7 @@ const useIsValidGasSettings = ( }, [debouncedFormData, tx]) const { isLoading: isValidationLoading, ...rest } = useIsValidEthTransaction( - provider, + publicClient, txPrepared, "custom" ) @@ -107,8 +110,8 @@ const useIsValidGasSettings = ( } type CustomGasSettingsFormEip1559Props = { - tx: ethers.providers.TransactionRequest - tokenId: string + tx: TransactionRequest + tokenId: TokenId txDetails: EthTransactionDetails gasSettingsByPriority: GasSettingsByPriorityEip1559 onConfirm: (gasSettings: EthGasSettingsEip1559) => void @@ -128,10 +131,10 @@ export const CustomGasSettingsFormEip1559: FC useEffect(() => { genericEvent("open custom gas settings", { - network: tx.chainId, + network: Number(txDetails.evmNetworkId), gasType: gasSettingsByPriority?.type, }) - }, [gasSettingsByPriority?.type, genericEvent, tx.chainId]) + }, [gasSettingsByPriority?.type, genericEvent, txDetails.evmNetworkId]) const { customSettings, highSettings } = useMemo( () => ({ @@ -143,12 +146,12 @@ export const CustomGasSettingsFormEip1559: FC const baseFee = useMemo( () => - formatDecimals( - ethers.utils.formatUnits(txDetails.baseFeePerGas as string, "gwei"), - undefined, - { notation: "standard" } - ), - [txDetails.baseFeePerGas] + txDetails.baseFeePerGas + ? formatDecimals(formatGwei(txDetails.baseFeePerGas), undefined, { + notation: "standard", + }) + : t("N/A"), + [t, txDetails.baseFeePerGas] ) const defaultValues: FormData = useMemo( @@ -170,7 +173,7 @@ export const CustomGasSettingsFormEip1559: FC { notation: "standard" } ) ), - gasLimit: BigNumber.from(customSettings.gasLimit).toNumber(), + gasLimit: BigNumber.from(customSettings.gas).toNumber(), }), [customSettings] ) @@ -224,7 +227,7 @@ export const CustomGasSettingsFormEip1559: FC maxBaseFee && txDetails.baseFeePerGas && BigNumber.from(ethers.utils.parseUnits(String(maxBaseFee), "gwei")).lt( - getMaxFeePerGas(txDetails.baseFeePerGas, 0, 20, false) + getMaxFeePerGas(txDetails.baseFeePerGas, 0n, 20, false) ) ) warningFee = t("Max Base Fee seems too low for current network conditions") @@ -233,7 +236,7 @@ export const CustomGasSettingsFormEip1559: FC txDetails.baseFeePerGas && maxBaseFee && BigNumber.from(ethers.utils.parseUnits(String(maxBaseFee), "gwei")).gt( - getMaxFeePerGas(txDetails.baseFeePerGas, 0, 20) + getMaxFeePerGas(txDetails.baseFeePerGas, 0n, 20) ) ) warningFee = t("Max Base Fee seems higher than required") @@ -273,7 +276,7 @@ export const CustomGasSettingsFormEip1559: FC const gasSettings = gasSettingsFromFormData(formData) genericEvent("set custom gas settings", { - network: tx.chainId, + network: Number(txDetails.evmNetworkId), gasType: gasSettings.type, }) @@ -283,11 +286,11 @@ export const CustomGasSettingsFormEip1559: FC notify({ title: "Error", subtitle: (err as Error).message, type: "error" }) } }, - [genericEvent, onConfirm, tx.chainId] + [genericEvent, onConfirm, txDetails.evmNetworkId] ) const { isValid: isGasSettingsValid, isLoading: isLoadingGasSettingsValid } = - useIsValidGasSettings(tx, maxBaseFee, maxPriorityFee, gasLimit) + useIsValidGasSettings(txDetails.evmNetworkId, tx, maxBaseFee, maxPriorityFee, gasLimit) const showMaxFeeTotal = isFormValid && isGasSettingsValid && !isLoadingGasSettingsValid diff --git a/apps/extension/src/ui/domains/Ethereum/GasSettings/CustomGasSettingsFormLegacy.tsx b/apps/extension/src/ui/domains/Ethereum/GasSettings/CustomGasSettingsFormLegacy.tsx index c4fd1cc04f..ecba6e19e6 100644 --- a/apps/extension/src/ui/domains/Ethereum/GasSettings/CustomGasSettingsFormLegacy.tsx +++ b/apps/extension/src/ui/domains/Ethereum/GasSettings/CustomGasSettingsFormLegacy.tsx @@ -1,4 +1,4 @@ -import { EthGasSettingsLegacy } from "@core/domains/ethereum/types" +import { EthGasSettingsLegacy, EvmNetworkId } from "@core/domains/ethereum/types" import { EthTransactionDetails, GasSettingsByPriorityLegacy } from "@core/domains/signing/types" import { log } from "@core/log" import { yupResolver } from "@hookform/resolvers/yup" @@ -15,9 +15,10 @@ import { useTranslation } from "react-i18next" import { useDebounce } from "react-use" import { IconButton } from "talisman-ui" import { Button, FormFieldContainer, FormFieldInputText } from "talisman-ui" +import { TransactionRequest, formatGwei } from "viem" import * as yup from "yup" -import { useEthereumProvider } from "../useEthereumProvider" +import { usePublicClient } from "../useEthereumProvider" import { useIsValidEthTransaction } from "../useIsValidEthTransaction" import { Indicator, MessageRow } from "./common" @@ -31,9 +32,9 @@ type FormData = { } const gasSettingsFromFormData = (formData: FormData): EthGasSettingsLegacy => ({ - type: 0, - gasPrice: BigNumber.from(Math.round(formData.gasPrice * Math.pow(10, 9))), - gasLimit: BigNumber.from(formData.gasLimit), + type: "legacy", + gasPrice: BigInt(Math.round(formData.gasPrice * Math.pow(10, 9))), + gas: BigInt(formData.gasLimit), }) const schema = yup @@ -44,7 +45,8 @@ const schema = yup .required() const useIsValidGasSettings = ( - tx: ethers.providers.TransactionRequest, + evmNetworkId: EvmNetworkId, + tx: TransactionRequest, gasPrice: number, gasLimit: number ) => { @@ -70,7 +72,7 @@ const useIsValidGasSettings = ( [gasPrice, gasLimit] ) - const provider = useEthereumProvider(tx.chainId?.toString()) + const provider = usePublicClient(evmNetworkId) const txPrepared = useMemo(() => { try { @@ -78,7 +80,7 @@ const useIsValidGasSettings = ( return { ...tx, ...gasSettingsFromFormData(debouncedFormData), - } as ethers.providers.TransactionRequest + } as TransactionRequest } catch (err) { // any bad input throws here, ignore return undefined @@ -99,7 +101,7 @@ const useIsValidGasSettings = ( type CustomGasSettingsFormLegacyProps = { networkUsage?: number - tx: ethers.providers.TransactionRequest + tx: TransactionRequest tokenId: string txDetails: EthTransactionDetails gasSettingsByPriority: GasSettingsByPriorityLegacy @@ -121,16 +123,16 @@ export const CustomGasSettingsFormLegacy: FC = useEffect(() => { genericEvent("open custom gas settings", { - network: tx.chainId, + network: Number(txDetails.evmNetworkId), gasType: gasSettingsByPriority?.type, }) - }, [gasSettingsByPriority?.type, genericEvent, tx.chainId]) + }, [gasSettingsByPriority?.type, genericEvent, txDetails.evmNetworkId]) const customSettings = gasSettingsByPriority.custom const networkGasPrice = useMemo( () => - formatDecimals(ethers.utils.formatUnits(txDetails.gasPrice as string, "gwei"), undefined, { + formatDecimals(formatGwei(txDetails.gasPrice), undefined, { notation: "standard", }), [txDetails.gasPrice] @@ -143,9 +145,9 @@ export const CustomGasSettingsFormLegacy: FC = notation: "standard", }) ), - gasLimit: BigNumber.from(customSettings.gasLimit).toNumber(), + gasLimit: BigNumber.from(customSettings.gas).toNumber(), }), - [customSettings.gasLimit, customSettings.gasPrice] + [customSettings.gas, customSettings.gasPrice] ) const { @@ -223,7 +225,7 @@ export const CustomGasSettingsFormLegacy: FC = const gasSettings = gasSettingsFromFormData(formData) genericEvent("set custom gas settings", { - network: tx.chainId, + network: Number(txDetails.evmNetworkId), gasType: gasSettings.type, }) @@ -233,11 +235,11 @@ export const CustomGasSettingsFormLegacy: FC = notify({ title: "Error", subtitle: (err as Error).message, type: "error" }) } }, - [genericEvent, onConfirm, tx.chainId] + [genericEvent, onConfirm, txDetails.evmNetworkId] ) const { isValid: isGasSettingsValid, isLoading: isLoadingGasSettingsValid } = - useIsValidGasSettings(tx, gasPrice, gasLimit) + useIsValidGasSettings(txDetails.evmNetworkId, tx, gasPrice, gasLimit) const showMaxFeeTotal = isFormValid && isGasSettingsValid && !isLoadingGasSettingsValid diff --git a/apps/extension/src/ui/domains/Ethereum/GasSettings/EthFeeSelect.tsx b/apps/extension/src/ui/domains/Ethereum/GasSettings/EthFeeSelect.tsx index 825f6c552b..aedea16437 100644 --- a/apps/extension/src/ui/domains/Ethereum/GasSettings/EthFeeSelect.tsx +++ b/apps/extension/src/ui/domains/Ethereum/GasSettings/EthFeeSelect.tsx @@ -8,9 +8,9 @@ import { TokenId } from "@core/domains/tokens/types" import { useOpenClose } from "@talisman/hooks/useOpenClose" import { classNames } from "@talismn/util" import { useAnalytics } from "@ui/hooks/useAnalytics" -import { ethers } from "ethers" import { FC, useCallback, useEffect, useState } from "react" import { Drawer, PillButton } from "talisman-ui" +import { TransactionRequest } from "viem" import { useFeePriorityOptionsUI } from "./common" import { CustomGasSettingsFormEip1559 } from "./CustomGasSettingsFormEip1559" @@ -28,7 +28,7 @@ const OpenFeeSelectTracker = () => { } type EthFeeSelectProps = { - tx: ethers.providers.TransactionRequest + tx: TransactionRequest tokenId: TokenId disabled?: boolean txDetails: EthTransactionDetails diff --git a/apps/extension/src/ui/domains/Ethereum/GasSettings/FeeOptionsForm.tsx b/apps/extension/src/ui/domains/Ethereum/GasSettings/FeeOptionsForm.tsx index 49cfa02a59..e20081258a 100644 --- a/apps/extension/src/ui/domains/Ethereum/GasSettings/FeeOptionsForm.tsx +++ b/apps/extension/src/ui/domains/Ethereum/GasSettings/FeeOptionsForm.tsx @@ -11,7 +11,6 @@ import { ChevronRightIcon } from "@talismn/icons" import { classNames } from "@talismn/util" import { TokensAndFiat } from "@ui/domains/Asset/TokensAndFiat" import useToken from "@ui/hooks/useToken" -import { BigNumber } from "ethers" import { FC, useCallback, useMemo } from "react" import { Trans, useTranslation } from "react-i18next" import { Tooltip, TooltipContent, TooltipTrigger } from "talisman-ui" @@ -34,8 +33,8 @@ const getGasSettings = ( } const Eip1559FeeTooltip: FC<{ - estimatedFee: BigNumber - maxFee: BigNumber + estimatedFee: bigint + maxFee: bigint tokenId: string }> = ({ estimatedFee, maxFee, tokenId }) => { const { t } = useTranslation("request") diff --git a/apps/extension/src/ui/domains/Ethereum/getExtensionEthereumProvider.ts b/apps/extension/src/ui/domains/Ethereum/getExtensionEthereumProvider.ts index 15fad4bf23..029f788d6d 100644 --- a/apps/extension/src/ui/domains/Ethereum/getExtensionEthereumProvider.ts +++ b/apps/extension/src/ui/domains/Ethereum/getExtensionEthereumProvider.ts @@ -1,51 +1 @@ -import { - ETH_ERROR_EIP1474_INTERNAL_ERROR, - EthProviderRpcError, -} from "@core/injectEth/EthProviderRpcError" -import { log } from "@core/log" -import { EvmNetworkId } from "@talismn/chaindata-provider" -import { api } from "@ui/api" -import { ethers } from "ethers" -import { PublicClient, createPublicClient, custom } from "viem" - -type EthersRequest = (method: string, params?: unknown[]) => Promise -type ViemRequest = (arg: { method: string; params?: unknown[] }) => Promise - -const ethersRequest = - (chainId: EvmNetworkId): EthersRequest => - async (method: string, params?: unknown[]) => { - try { - return await api.ethRequest({ chainId, method, params }) - } catch (err) { - log.error("[provider.request] error on %s", method, { err }) - const { message, code, data } = err as EthProviderRpcError - throw new EthProviderRpcError(message, code ?? ETH_ERROR_EIP1474_INTERNAL_ERROR, data) - } - } - -const viemRequest = - (chainId: EvmNetworkId): ViemRequest => - async ({ method, params }) => { - try { - return await api.ethRequest({ chainId, method, params }) - } catch (err) { - log.error("[provider.request] error on %s", method, { err }) - throw err - // TODO check that we get proper error codes - // const { message, code, data } = err as EthProviderRpcError - // throw new EthProviderRpcError(message, code ?? ETH_ERROR_EIP1474_INTERNAL_ERROR, data) - } - } - -export const getExtensionEthereumProvider = (evmNetworkId: EvmNetworkId) => { - return new ethers.providers.Web3Provider(ethersRequest(evmNetworkId)) -} - -export const getExtensionPublicClient = (evmNetworkId: EvmNetworkId): PublicClient => { - return createPublicClient({ - // TODO check timers, decide if they should be here (remove them on backend) or clear them here and use defaults on backend - transport: custom({ - request: viemRequest(evmNetworkId), - }), - }) -} +export default 0 diff --git a/apps/extension/src/ui/domains/Ethereum/useEthBalance.ts b/apps/extension/src/ui/domains/Ethereum/useEthBalance.ts index 7881876b16..b4feeae3c0 100644 --- a/apps/extension/src/ui/domains/Ethereum/useEthBalance.ts +++ b/apps/extension/src/ui/domains/Ethereum/useEthBalance.ts @@ -1,21 +1,22 @@ +import { isEthereumAddress } from "@talismn/util" import { useQuery } from "@tanstack/react-query" -import { ethers } from "ethers" +import { PublicClient } from "viem" export const useEthBalance = ( - provider: ethers.providers.JsonRpcProvider | undefined, + publicClient: PublicClient | undefined, address: string | undefined ) => { const { data: balance, ...rest } = useQuery({ - queryKey: ["useEthBalance", provider?.network?.chainId, address], + queryKey: ["useEthBalance", publicClient?.chain?.id, address], queryFn: () => { - if (!provider || !address) return null - return provider.getBalance(address) + if (!publicClient || !isEthereumAddress(address)) return null + return publicClient.getBalance({ address }) }, refetchInterval: 12_000, refetchOnMount: false, refetchOnReconnect: false, refetchOnWindowFocus: false, - enabled: !!provider?.network && !!address, + enabled: !!publicClient?.chain?.id && isEthereumAddress(address), }) return { balance, ...rest } diff --git a/apps/extension/src/ui/domains/Ethereum/useEthReplaceTransaction.ts b/apps/extension/src/ui/domains/Ethereum/useEthReplaceTransaction.ts index 59bb4ab4da..bb9cfa05bc 100644 --- a/apps/extension/src/ui/domains/Ethereum/useEthReplaceTransaction.ts +++ b/apps/extension/src/ui/domains/Ethereum/useEthReplaceTransaction.ts @@ -1,32 +1,40 @@ -import { rebuildTransactionRequestNumbers } from "@core/domains/ethereum/helpers" -import { ethers } from "ethers" +import { parseTransactionRequest } from "@core/domains/ethereum/helpers" +import { EvmNetworkId } from "@talismn/chaindata-provider" import { useMemo } from "react" +import { TransactionRequest } from "viem" import { TxReplaceType } from "../Transactions" import { useEthTransaction } from "./useEthTransaction" export const useEthReplaceTransaction = ( - tx: ethers.providers.TransactionRequest, + txToReplace: TransactionRequest, + evmNetworkId: EvmNetworkId, type: TxReplaceType, lock?: boolean ) => { - const transaction: ethers.providers.TransactionRequest = useMemo( - () => - rebuildTransactionRequestNumbers({ - chainId: tx.chainId, - from: tx.from, - to: type === "cancel" ? tx.from : tx.to, - value: type === "cancel" ? "0" : tx.value, - data: type === "cancel" ? undefined : tx.data, - nonce: tx.nonce, + const replaceTx = useMemo(() => { + const parsed = parseTransactionRequest(txToReplace) - // pass previous tx gas data - type: tx.type, - gasPrice: tx.gasPrice, - maxPriorityFeePerGas: tx.maxPriorityFeePerGas, - }), - [tx, type] - ) + const newTx: TransactionRequest = + txToReplace.type === "eip1559" + ? { + type: "eip1559", + from: parsed.from, + maxPriorityFeePerGas: parsed.maxPriorityFeePerGas, + } + : { + type: "legacy", + from: parsed.from, + gasPrice: parsed.gasPrice, + } - return useEthTransaction(transaction, lock, true) + newTx.nonce = parsed.nonce + newTx.to = type === "cancel" ? parsed.from : parsed.to + newTx.value = type === "cancel" ? 0n : parsed.value + if (type === "speed-up") newTx.data = parsed.data + + return newTx + }, [txToReplace, type]) + + return useEthTransaction(replaceTx, evmNetworkId, lock, true) } diff --git a/apps/extension/src/ui/domains/Ethereum/useEthTransaction.ts b/apps/extension/src/ui/domains/Ethereum/useEthTransaction.ts index 3a9c8d311d..2831e5abae 100644 --- a/apps/extension/src/ui/domains/Ethereum/useEthTransaction.ts +++ b/apps/extension/src/ui/domains/Ethereum/useEthTransaction.ts @@ -4,6 +4,7 @@ import { getGasSettingsEip1559, getTotalFeesFromGasSettings, prepareTransaction, + serializeTransactionRequest, } from "@core/domains/ethereum/helpers" import { EthGasSettings, @@ -23,11 +24,11 @@ import { getEthTransactionInfo } from "@core/util/getEthTransactionInfo" import { FeeHistoryAnalysis, getFeeHistoryAnalysis } from "@core/util/getFeeHistoryAnalysis" import { useQuery } from "@tanstack/react-query" import { api } from "@ui/api" -import { useEthereumProvider, usePublicClient } from "@ui/domains/Ethereum/useEthereumProvider" -import { BigNumber, ethers } from "ethers" +import { usePublicClient } from "@ui/domains/Ethereum/useEthereumProvider" +import { useAlec } from "@ui/hooks/useAlec" import { useEffect, useMemo, useState } from "react" import { useTranslation } from "react-i18next" -import { PublicClient } from "viem" +import { PublicClient, TransactionRequest } from "viem" import { useIsValidEthTransaction } from "./useIsValidEthTransaction" @@ -51,21 +52,29 @@ const useNonce = ( } // TODO : could be skipped for networks that we know already support it, but need to keep checking for legacy network in case they upgrade -const useHasEip1559Support = (provider: ethers.providers.JsonRpcProvider | undefined) => { +const useHasEip1559Support = (publicClient: PublicClient | undefined) => { const { data, ...rest } = useQuery({ - queryKey: ["hasEip1559Support", provider?.network?.chainId], + queryKey: ["hasEip1559Support", publicClient?.chain?.id], queryFn: async () => { - if (!provider) return null + if (!publicClient) return null try { const [{ baseFeePerGas }] = await Promise.all([ // check that block has a baseFeePerGas - provider.send("eth_getBlockByNumber", ["latest", false]), + // publicClient.send("eth_getBlockByNumber", ["latest", false]), + publicClient.getBlock({ blockTag: "latest", includeTransactions: false }), + // check that method eth_feeHistory exists. This will throw with code -32601 if it doesn't. - provider.send("eth_feeHistory", [ethers.utils.hexValue(1), "latest", [10]]), + //publicClient.send("eth_feeHistory", [ethers.utils.hexValue(1), "latest", [10]]), + publicClient.getFeeHistory({ + blockCount: 1, + blockTag: "latest", + rewardPercentiles: [10], + }), ]) return baseFeePerGas !== undefined } catch (err) { + // TODO check that feeHistory returns -32601 when method doesn't exist const error = err as Error & { code?: number } if (error.code === ETH_ERROR_EIP1474_METHOD_NOT_FOUND) return false @@ -76,35 +85,29 @@ const useHasEip1559Support = (provider: ethers.providers.JsonRpcProvider | undef refetchOnMount: false, refetchOnReconnect: false, refetchOnWindowFocus: false, - enabled: !!provider, + enabled: !!publicClient, }) return { hasEip1559Support: data ?? undefined, ...rest } } const useBlockFeeData = ( - provider: ethers.providers.JsonRpcProvider | undefined, - tx: ethers.providers.TransactionRequest | undefined, + publicClient: PublicClient | undefined, + tx: TransactionRequest | undefined, withFeeOptions: boolean | undefined ) => { const { data, ...rest } = useQuery({ - queryKey: ["block", provider?.network?.chainId, tx, withFeeOptions], + queryKey: [ + "block", + publicClient?.chain?.id, + tx && serializeTransactionRequest(tx), + withFeeOptions, + ], queryFn: async () => { - if (!provider || !tx) return null - - // estimate gas without any gas setting, will be used as our gasLimit - // spread to keep only valid properties (exclude the one called "gas" and any undefined ones) - const { chainId, from, to, value = BigNumber.from("0"), data } = tx - const txForEstimate: ethers.providers.TransactionRequest = { - chainId, - from, - to, - value, - data, - } - if (tx.accessList !== undefined) txForEstimate.accessList = tx.accessList - if (tx.customData !== undefined) txForEstimate.customData = tx.customData - if (tx.ccipReadEnabled !== undefined) txForEstimate.ccipReadEnabled = tx.ccipReadEnabled + if (!publicClient?.chain?.id || !tx) return null + + // estimate gas without any gas or nonce setting to prevent rpc from validating these + const { from: account, to, value, data } = tx const [ gasPrice, @@ -112,22 +115,19 @@ const useBlockFeeData = ( feeHistoryAnalysis, estimatedGas, ] = await Promise.all([ - provider.getGasPrice(), - provider.getBlock("latest"), - withFeeOptions ? getFeeHistoryAnalysis(provider) : undefined, + publicClient.getGasPrice(), + publicClient.getBlock({ blockTag: "latest" }), + withFeeOptions ? getFeeHistoryAnalysis(publicClient) : undefined, // estimate gas may change over time for contract calls, so we need to refresh it every time we prepare the tx to prevent an invalid transaction - provider.estimateGas(txForEstimate), + publicClient.estimateGas({ account, to, value, data }), ]) - if ( - feeHistoryAnalysis && - !UNRELIABLE_GASPRICE_NETWORK_IDS.includes(provider.network.chainId) - ) { + if (feeHistoryAnalysis && !UNRELIABLE_GASPRICE_NETWORK_IDS.includes(publicClient.chain.id)) { // minimum maxPriorityPerGas value required to be considered valid into next block is equal to `gasPrice - baseFee` - let minimumMaxPriorityFeePerGas = gasPrice.sub(baseFeePerGas ?? 0) - if (minimumMaxPriorityFeePerGas.lt(0)) { + let minimumMaxPriorityFeePerGas = gasPrice - (baseFeePerGas ?? 0n) + if (minimumMaxPriorityFeePerGas < 0n) { // on a busy network, when there is a sudden lowering of amount of transactions, it can happen that baseFeePerGas is higher than gPrice - minimumMaxPriorityFeePerGas = BigNumber.from("0") + minimumMaxPriorityFeePerGas = 0n } // if feeHistory is invalid (network is inactive), use minimumMaxPriorityFeePerGas for all options. @@ -141,17 +141,14 @@ const useBlockFeeData = ( feeHistoryAnalysis.avgGasUsedRatio !== null && feeHistoryAnalysis.avgGasUsedRatio < 0.8 ) - feeHistoryAnalysis.maxPriorityPerGasOptions.low = minimumMaxPriorityFeePerGas.lt( - feeHistoryAnalysis.maxPriorityPerGasOptions.low - ) - ? minimumMaxPriorityFeePerGas - : feeHistoryAnalysis.maxPriorityPerGasOptions.low + feeHistoryAnalysis.maxPriorityPerGasOptions.low = + minimumMaxPriorityFeePerGas < feeHistoryAnalysis.maxPriorityPerGasOptions.low + ? minimumMaxPriorityFeePerGas + : feeHistoryAnalysis.maxPriorityPerGasOptions.low } const networkUsage = - !gasUsed || !blockGasLimit - ? undefined - : gasUsed.mul(100).div(blockGasLimit).toNumber() / 100 + !gasUsed || !blockGasLimit ? undefined : Number((gasUsed * 100n) / blockGasLimit) / 100 return { estimatedGas, @@ -163,7 +160,7 @@ const useBlockFeeData = ( blockNumber, } }, - enabled: !!tx && !!provider && withFeeOptions !== undefined, + enabled: !!tx && !!publicClient && withFeeOptions !== undefined, refetchInterval: 6_000, retry: false, }) @@ -186,11 +183,11 @@ const useBlockFeeData = ( const useTransactionInfo = ( publicClient: PublicClient | undefined, - tx: ethers.providers.TransactionRequest | undefined + tx: TransactionRequest | undefined ) => { const { data, ...rest } = useQuery({ // check tx as boolean as it's not pure - queryKey: ["transactionInfo", publicClient?.chain?.id, tx], + queryKey: ["transactionInfo", publicClient?.chain?.id, tx && serializeTransactionRequest(tx)], queryFn: async () => { if (!publicClient || !tx) return null return await getEthTransactionInfo(publicClient, tx) @@ -204,30 +201,36 @@ const useTransactionInfo = ( } const getEthGasSettingsFromTransaction = ( - tx: ethers.providers.TransactionRequest | undefined, + tx: TransactionRequest | undefined, hasEip1559Support: boolean | undefined, - estimatedGas: BigNumber | undefined, - blockGasLimit: BigNumber | undefined, + estimatedGas: bigint | undefined, + blockGasLimit: bigint | undefined, isContractCall: boolean | undefined = true // default to worse scenario ) => { - if (!tx || hasEip1559Support === undefined || !blockGasLimit || !estimatedGas) return undefined + if ( + !tx || + hasEip1559Support === undefined || + blockGasLimit === undefined || + estimatedGas === undefined + ) + return undefined const { gasPrice, maxFeePerGas, maxPriorityFeePerGas } = tx - const gasLimit = getGasLimit(blockGasLimit, estimatedGas, tx, isContractCall) + const gas = getGasLimit(blockGasLimit, estimatedGas, tx, isContractCall) - if (hasEip1559Support && gasLimit && maxFeePerGas && maxPriorityFeePerGas) { + if (hasEip1559Support && gas && maxFeePerGas && maxPriorityFeePerGas) { return { - type: 2, - gasLimit, + type: "eip1559", + gas, maxFeePerGas, maxPriorityFeePerGas, } as EthGasSettingsEip1559 } - if (!hasEip1559Support && gasLimit && gasPrice) { + if (!hasEip1559Support && gas && gasPrice) { return { - type: 0, - gasLimit, + type: "eip2930", + gas, gasPrice, } as EthGasSettingsLegacy } @@ -248,13 +251,13 @@ const useGasSettings = ({ isContractCall, }: { hasEip1559Support: boolean | undefined - baseFeePerGas: BigNumber | null | undefined - estimatedGas: BigNumber | null | undefined - gasPrice: BigNumber | null | undefined - blockGasLimit: BigNumber | null | undefined + baseFeePerGas: bigint | null | undefined + estimatedGas: bigint | null | undefined + gasPrice: bigint | null | undefined + blockGasLimit: bigint | null | undefined feeHistoryAnalysis: FeeHistoryAnalysis | null | undefined priority: EthPriorityOptionName | undefined - tx: ethers.providers.TransactionRequest | undefined + tx: TransactionRequest | undefined isReplacement: boolean | undefined isContractCall: boolean | undefined }) => { @@ -263,7 +266,7 @@ const useGasSettings = ({ const gasSettingsByPriority: GasSettingsByPriority | undefined = useMemo(() => { if (hasEip1559Support === undefined || !estimatedGas || !gasPrice || !blockGasLimit || !tx) return undefined - const gasLimit = getGasLimit(blockGasLimit, estimatedGas, tx, isContractCall) + const gas = getGasLimit(blockGasLimit, estimatedGas, tx, isContractCall) const suggestedSettings = getEthGasSettingsFromTransaction( tx, hasEip1559Support, @@ -277,27 +280,25 @@ const useGasSettings = ({ const mapMaxPriority = feeHistoryAnalysis.maxPriorityPerGasOptions - if (isReplacement) { + if (isReplacement && tx.maxPriorityFeePerGas !== undefined) { // for replacement transactions, ensure that maxPriorityFeePerGas is at least 10% higher than original tx - const minimumMaxPriorityFeePerGas = ethers.BigNumber.from(tx.maxPriorityFeePerGas) - .mul(110) - .div(100) - if (mapMaxPriority.low.lt(minimumMaxPriorityFeePerGas)) + const minimumMaxPriorityFeePerGas = (tx.maxPriorityFeePerGas * 110n) / 100n + if (mapMaxPriority.low < minimumMaxPriorityFeePerGas) mapMaxPriority.low = minimumMaxPriorityFeePerGas - if (mapMaxPriority.medium.lt(minimumMaxPriorityFeePerGas)) + if (mapMaxPriority.medium < minimumMaxPriorityFeePerGas) mapMaxPriority.medium = minimumMaxPriorityFeePerGas - if (mapMaxPriority.high.lt(minimumMaxPriorityFeePerGas)) + if (mapMaxPriority.high < minimumMaxPriorityFeePerGas) mapMaxPriority.high = minimumMaxPriorityFeePerGas } - const low = getGasSettingsEip1559(baseFeePerGas, mapMaxPriority.low, gasLimit) - const medium = getGasSettingsEip1559(baseFeePerGas, mapMaxPriority.medium, gasLimit) - const high = getGasSettingsEip1559(baseFeePerGas, mapMaxPriority.high, gasLimit) + const low = getGasSettingsEip1559(baseFeePerGas, mapMaxPriority.low, gas) + const medium = getGasSettingsEip1559(baseFeePerGas, mapMaxPriority.medium, gas) + const high = getGasSettingsEip1559(baseFeePerGas, mapMaxPriority.high, gas) const custom: EthGasSettingsEip1559 = - customSettings?.type === 2 + customSettings?.type === "eip1559" ? customSettings - : suggestedSettings?.type === 2 + : suggestedSettings?.type === "eip1559" ? suggestedSettings : { ...low, @@ -315,24 +316,23 @@ const useGasSettings = ({ } const recommendedSettings: EthGasSettingsLegacy = { - type: 0, - gasLimit, + type: "legacy", + gas, // in some cases (ex: claiming bridged tokens on Polygon zkEVM), // 0 is provided by the dapp and has to be used for the tx to succeed - gasPrice: tx.gasPrice && BigNumber.from(tx.gasPrice).isZero() ? BigNumber.from(0) : gasPrice, + gasPrice: tx.gasPrice === 0n ? 0n : gasPrice, } - if (isReplacement) { + if (isReplacement && tx.gasPrice !== undefined) { // for replacement transactions, ensure that maxPriorityFeePerGas is at least 10% higher than original tx - const minimumGasPrice = ethers.BigNumber.from(tx.gasPrice).mul(110).div(100) - if (ethers.BigNumber.from(gasPrice).lt(minimumGasPrice)) - recommendedSettings.gasPrice = minimumGasPrice + const minimumGasPrice = (tx.gasPrice * 110n) / 100n + if (gasPrice < minimumGasPrice) recommendedSettings.gasPrice = minimumGasPrice } const custom: EthGasSettingsLegacy = - customSettings?.type === 0 + customSettings?.type === "legacy" ? customSettings - : suggestedSettings?.type === 0 + : suggestedSettings?.type === "legacy" ? suggestedSettings : recommendedSettings @@ -370,20 +370,25 @@ const useGasSettings = ({ } export const useEthTransaction = ( - tx: ethers.providers.TransactionRequest | undefined, + tx: TransactionRequest | undefined, + evmNetworkId: EvmNetworkId | undefined, lockTransaction = false, isReplacement = false ) => { - const provider = useEthereumProvider(tx?.chainId?.toString()) - const publicClient = usePublicClient(tx?.chainId?.toString()) + useAlec("tx", tx) + const publicClient = usePublicClient(evmNetworkId) + useAlec("publicClient", publicClient) const { transactionInfo, error: errorTransactionInfo } = useTransactionInfo(publicClient, tx) - const { hasEip1559Support, error: errorEip1559Support } = useHasEip1559Support(provider) + const { hasEip1559Support, error: errorEip1559Support } = useHasEip1559Support(publicClient) + useAlec("hasEip1559Support", hasEip1559Support) + useAlec("errorEip1559Support", errorEip1559Support) const { nonce, error: nonceError } = useNonce( tx?.from as `0x${string}` | undefined, - tx?.chainId?.toString(), - isReplacement && tx?.nonce ? BigNumber.from(tx.nonce).toNumber() : undefined + evmNetworkId, + isReplacement && tx?.nonce ? tx.nonce : undefined ) - + useAlec("nonce", nonce) + useAlec("nonceError", nonceError) const { gasPrice, networkUsage, @@ -392,7 +397,14 @@ export const useEthTransaction = ( feeHistoryAnalysis, estimatedGas, error: blockFeeDataError, - } = useBlockFeeData(provider, tx, hasEip1559Support) + } = useBlockFeeData(publicClient, tx, hasEip1559Support) + useAlec("gasPrice", gasPrice) + useAlec("networkUsage", networkUsage) + useAlec("baseFeePerGas", baseFeePerGas) + useAlec("blockGasLimit", blockGasLimit) + useAlec("feeHistoryAnalysis", feeHistoryAnalysis) + useAlec("estimatedGas", estimatedGas) + useAlec("blockFeeDataError", blockFeeDataError) const [priority, setPriority] = useState() @@ -400,7 +412,7 @@ export const useEthTransaction = ( // ex: from send funds when switching from BSC (legacy) to mainnet (eip1559) useEffect(() => { setPriority(undefined) - }, [tx?.chainId]) + }, [evmNetworkId]) // set default priority based on EIP1559 support useEffect(() => { @@ -409,7 +421,7 @@ export const useEthTransaction = ( }, [hasEip1559Support, isReplacement, priority]) const { gasSettings, setCustomSettings, gasSettingsByPriority } = useGasSettings({ - tx, + tx: tx, priority, hasEip1559Support, baseFeePerGas, @@ -422,9 +434,9 @@ export const useEthTransaction = ( }) const liveUpdatingTransaction = useMemo(() => { - if (!provider || !tx || !gasSettings || nonce === undefined) return undefined + if (!publicClient || !tx || !gasSettings || nonce === undefined) return undefined return prepareTransaction(tx, gasSettings, nonce) - }, [gasSettings, provider, tx, nonce]) + }, [gasSettings, publicClient, tx, nonce]) // transaction may be locked once sent to hardware device for signing const [transaction, setTransaction] = useState(liveUpdatingTransaction) @@ -435,10 +447,11 @@ export const useEthTransaction = ( // TODO replace this wierd object name with something else... gasInfo ? const txDetails: EthTransactionDetails | undefined = useMemo(() => { - if (!gasPrice || !estimatedGas || !transaction || !gasSettings) return undefined + if (!evmNetworkId || !gasPrice || !estimatedGas || !transaction || !gasSettings) + return undefined - // if type 2 transaction, wait for baseFee to be available - if (gasSettings?.type === 2 && !baseFeePerGas) return undefined + // if eip1559 transaction, wait for baseFee to be available + if (gasSettings?.type === "eip1559" && !baseFeePerGas) return undefined const { estimatedFee, maxFee } = getTotalFeesFromGasSettings( gasSettings, @@ -447,6 +460,7 @@ export const useEthTransaction = ( ) return { + evmNetworkId, estimatedGas, gasPrice, baseFeePerGas, @@ -454,10 +468,22 @@ export const useEthTransaction = ( maxFee, baseFeeTrend: feeHistoryAnalysis?.baseFeeTrend, } - }, [baseFeePerGas, estimatedGas, feeHistoryAnalysis, gasPrice, gasSettings, transaction]) + }, [ + baseFeePerGas, + estimatedGas, + evmNetworkId, + feeHistoryAnalysis?.baseFeeTrend, + gasPrice, + gasSettings, + transaction, + ]) // use staleIsValid to prevent disabling approve button each time there is a new block (triggers gas check) - const { isValid, error: isValidError } = useIsValidEthTransaction(provider, transaction, priority) + const { isValid, error: isValidError } = useIsValidEthTransaction( + publicClient, + transaction, + priority + ) const { t } = useTranslation("request") const { error, errorDetails } = useMemo(() => { @@ -486,6 +512,12 @@ export const useEthTransaction = ( [tx, transactionInfo, txDetails, error] ) + useAlec("transactionInfo", transactionInfo) + useAlec("transaction", transaction) + useAlec("txDetails", txDetails) + useAlec("gasSettings", gasSettings) + useAlec("gasSettingsByPriority", gasSettingsByPriority) + return { transactionInfo, transaction, diff --git a/apps/extension/src/ui/domains/Ethereum/useEthereumProvider.ts b/apps/extension/src/ui/domains/Ethereum/useEthereumProvider.ts index 69ce35a4c0..10ae430e52 100644 --- a/apps/extension/src/ui/domains/Ethereum/useEthereumProvider.ts +++ b/apps/extension/src/ui/domains/Ethereum/useEthereumProvider.ts @@ -1,31 +1,65 @@ -import { EvmNetworkId } from "@core/domains/ethereum/types" -import { - getExtensionEthereumProvider, - getExtensionPublicClient, -} from "@ui/domains/Ethereum/getExtensionEthereumProvider" -import { ethers } from "ethers" +import { EvmNetwork, EvmNetworkId } from "@core/domains/ethereum/types" +import { log } from "@core/log" +import { EvmNativeToken } from "@talismn/balances-evm-native" +import { api } from "@ui/api" +import { useEvmNetwork } from "@ui/hooks/useEvmNetwork" +import useToken from "@ui/hooks/useToken" import { useMemo } from "react" -import { PublicClient } from "viem" +import { PublicClient, createPublicClient, custom } from "viem" -/** - * @deprecated use usePublicClient instead - */ -export const useEthereumProvider = ( - evmNetworkId?: EvmNetworkId -): ethers.providers.JsonRpcProvider | undefined => { - const provider = useMemo(() => { - if (!evmNetworkId) return undefined - return getExtensionEthereumProvider(evmNetworkId) - }, [evmNetworkId]) +type ViemRequest = (arg: { method: string; params?: unknown[] }) => Promise - return provider +const viemRequest = + (chainId: EvmNetworkId): ViemRequest => + async ({ method, params }) => { + try { + return await api.ethRequest({ chainId, method, params }) + } catch (err) { + log.error("[provider.request] error on %s", method, { err }) + throw err + // TODO check that we get proper error codes + // const { message, code, data } = err as EthProviderRpcError + // throw new EthProviderRpcError(message, code ?? ETH_ERROR_EIP1474_INTERNAL_ERROR, data) + } + } + +export const getExtensionPublicClient = ( + evmNetwork: EvmNetwork, + nativeToken: EvmNativeToken +): PublicClient => { + const name = evmNetwork.name ?? `EVM Chain ${evmNetwork.id}` + + return createPublicClient({ + chain: { + id: Number(evmNetwork.id), + name: name, + network: name, + nativeCurrency: { + symbol: nativeToken.symbol, + decimals: nativeToken.decimals, + name: nativeToken.symbol, + }, + rpcUrls: { + // rpcs are a typescript requirement, won't be used by the custom transport + public: { http: [] }, + default: { http: [] }, + }, + }, + // TODO check timers, decide if they should be here (remove them on backend) or clear them here and use defaults on backend + transport: custom({ + request: viemRequest(evmNetwork.id), + }), + }) } export const usePublicClient = (evmNetworkId?: EvmNetworkId): PublicClient | undefined => { + const evmNetwork = useEvmNetwork(evmNetworkId) + const nativeToken = useToken(evmNetwork?.nativeToken?.id) + const publicClient = useMemo(() => { - if (!evmNetworkId) return undefined - return getExtensionPublicClient(evmNetworkId) - }, [evmNetworkId]) + if (!evmNetwork || nativeToken?.type !== "evm-native") return undefined + return getExtensionPublicClient(evmNetwork, nativeToken) + }, [evmNetwork, nativeToken]) return publicClient } diff --git a/apps/extension/src/ui/domains/Ethereum/useIsValidEthTransaction.ts b/apps/extension/src/ui/domains/Ethereum/useIsValidEthTransaction.ts index 9db9cb4e0e..95bccd2591 100644 --- a/apps/extension/src/ui/domains/Ethereum/useIsValidEthTransaction.ts +++ b/apps/extension/src/ui/domains/Ethereum/useIsValidEthTransaction.ts @@ -1,52 +1,55 @@ -import { getMaxTransactionCost } from "@core/domains/ethereum/helpers" +import { getMaxTransactionCost, serializeTransactionRequest } from "@core/domains/ethereum/helpers" import { EthPriorityOptionName } from "@core/domains/signing/types" import { useQuery } from "@tanstack/react-query" import { useAccountByAddress } from "@ui/hooks/useAccountByAddress" -import { ethers } from "ethers" import { useTranslation } from "react-i18next" +import { PublicClient, TransactionRequest } from "viem" import { useEthBalance } from "./useEthBalance" export const useIsValidEthTransaction = ( - provider: ethers.providers.JsonRpcProvider | undefined, - transaction: ethers.providers.TransactionRequest | undefined, + publicClient: PublicClient | undefined, + tx: TransactionRequest | undefined, priority: EthPriorityOptionName | undefined ) => { const { t } = useTranslation("request") - const account = useAccountByAddress(transaction?.from) - const { balance } = useEthBalance(provider, transaction?.from) + const account = useAccountByAddress(tx?.from) + const { balance } = useEthBalance(publicClient, tx?.from) const { data, error, isLoading } = useQuery({ queryKey: [ - "useCheckTransaction", - provider?.network?.chainId, - transaction, + "useIsValidEthTransaction", + publicClient?.chain?.id, + tx && serializeTransactionRequest(tx), account?.address, priority, ], queryFn: async () => { - if (!provider || !transaction || !account || balance === undefined) return null + if (!publicClient || !tx || !account || balance === undefined) return null if (account.origin === "WATCHED") throw new Error(t("Cannot sign transactions with a watched account")) // balance checks - const value = ethers.BigNumber.from(transaction.value ?? 0) - const maxTransactionCost = getMaxTransactionCost(transaction) - if (!balance || value.gt(balance)) throw new Error(t("Insufficient balance")) - if (!balance || maxTransactionCost.gt(balance)) + const value = tx.value ?? 0n + const maxTransactionCost = getMaxTransactionCost(tx) + if (!balance || value > balance) throw new Error(t("Insufficient balance")) + if (!balance || maxTransactionCost > balance) throw new Error(t("Insufficient balance to pay for fee")) // dry runs the transaction, if it fails we can't know for sure what the issue really is // there should be helpful message in the error though. - const estimatedGas = await provider.estimateGas(transaction) - return estimatedGas?.gt(0) + const estimatedGas = await publicClient.estimateGas({ + account: tx.from, + ...tx, + }) + return estimatedGas > 0n }, refetchInterval: false, refetchOnWindowFocus: false, retry: 0, keepPreviousData: true, - enabled: !!provider && !!transaction && !!account && balance !== undefined, + enabled: !!publicClient && !!tx && !!account && balance !== undefined, }) return { isValid: !!data, error, isLoading } diff --git a/apps/extension/src/ui/domains/SendFunds/SendFundsProgress.tsx b/apps/extension/src/ui/domains/SendFunds/SendFundsProgress.tsx index 47989ca659..5bba4a219f 100644 --- a/apps/extension/src/ui/domains/SendFunds/SendFundsProgress.tsx +++ b/apps/extension/src/ui/domains/SendFunds/SendFundsProgress.tsx @@ -73,7 +73,7 @@ const TxReplaceActions: FC<{ tx: WalletTransaction }> = ({ tx }) => { ) } -const useStatusDetails = (tx: WalletTransaction) => { +const useStatusDetails = (tx?: WalletTransaction) => { const { t } = useTranslation("send-funds") const { title, subtitle, extra, animStatus } = useMemo<{ title: string @@ -81,6 +81,14 @@ const useStatusDetails = (tx: WalletTransaction) => { animStatus: ProcessAnimationStatus extra?: string }>(() => { + // missing tx can occur while loading + if (!tx) + return { + title: "", + subtitle: "", + animStatus: "processing", + } + const isReplacementCancel = tx.networkType === "evm" && tx.isReplacement && @@ -138,7 +146,7 @@ const useStatusDetails = (tx: WalletTransaction) => { } type SendFundsProgressBaseProps = { - tx: WalletTransaction + tx?: WalletTransaction className?: string blockNumber?: string onClose?: () => void @@ -184,7 +192,7 @@ const SendFundsProgressBase: FC = ({ extra )}
- {tx.status === "pending" && } + {tx?.status === "pending" && }
- diff --git a/packages/chain-connector-evm/src/getTransportForEvmNetwork.ts b/packages/chain-connector-evm/src/getTransportForEvmNetwork.ts index 178533db18..d686b7446f 100644 --- a/packages/chain-connector-evm/src/getTransportForEvmNetwork.ts +++ b/packages/chain-connector-evm/src/getTransportForEvmNetwork.ts @@ -28,9 +28,6 @@ export const getTransportForEvmNetwork = ( return custom({ request: (request) => { const { method, params } = request as { method: string; params: unknown[] } - // TODO check if params are good - // eslint-disable-next-line no-console - console.warn("acala request", { method, params }) return ethersProvider.send(method, params) }, }) From 03180f526b4a14fdb8c2fb5bf21e9669e0e10740 Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Wed, 1 Nov 2023 18:30:44 +0900 Subject: [PATCH 24/50] chore: typings cleanup --- .../src/core/domains/ethereum/handler.tabs.ts | 62 +++--- .../src/core/domains/ethereum/types.ts | 179 +++--------------- 2 files changed, 44 insertions(+), 197 deletions(-) diff --git a/apps/extension/src/core/domains/ethereum/handler.tabs.ts b/apps/extension/src/core/domains/ethereum/handler.tabs.ts index 689671f401..944425b2fa 100644 --- a/apps/extension/src/core/domains/ethereum/handler.tabs.ts +++ b/apps/extension/src/core/domains/ethereum/handler.tabs.ts @@ -63,8 +63,8 @@ import { requestAddNetwork, requestWatchAsset } from "./requests" import { EthProviderMessage, EthRequestArgsViem, - EthRequestArgumentsViem, - EthRequestResultViem, + EthRequestArguments, + EthRequestResult, EthRequestSignArguments, Web3WalletPermission, Web3WalletPermissionTarget, @@ -299,9 +299,9 @@ export class EthTabsHandler extends TabsHandler { private addEthereumChain = async ( url: string, - request: EthRequestArgumentsViem<"wallet_addEthereumChain">, + request: EthRequestArguments<"wallet_addEthereumChain">, port: Port - ): Promise> => { + ): Promise> => { const { params: [network], } = request @@ -360,8 +360,8 @@ export class EthTabsHandler extends TabsHandler { private switchEthereumChain = async ( url: string, - request: EthRequestArgumentsViem<"wallet_switchEthereumChain"> - ): Promise> => { + request: EthRequestArguments<"wallet_switchEthereumChain"> + ): Promise> => { const { params: [{ chainId: hexChainId }], } = request @@ -419,9 +419,11 @@ export class EthTabsHandler extends TabsHandler { }) } - private signMessage = async (url: string, request: EthRequestSignArguments, port: Port) => { - const { params, method } = request as EthRequestSignArguments - + private signMessage = async ( + url: string, + { params, method }: EthRequestSignArguments, + port: Port + ) => { // eth_signTypedData requires a non-empty array of parameters, else throw (uniswap will then call v4) if (method === "eth_signTypedData") { if (!Array.isArray(params[0])) @@ -475,9 +477,9 @@ export class EthTabsHandler extends TabsHandler { private addWatchAssetRequest = async ( url: string, - request: EthRequestArgumentsViem<"wallet_watchAsset">, + request: EthRequestArguments<"wallet_watchAsset">, port: Port - ): Promise> => { + ): Promise> => { if (!isValidWatchAssetRequestParam(request.params)) throw new EthProviderRpcError("Invalid parameter", ETH_ERROR_EIP1474_INVALID_PARAMS) @@ -566,7 +568,7 @@ export class EthTabsHandler extends TabsHandler { private async sendTransaction( url: string, - { params: [txRequest] }: EthRequestArgumentsViem<"eth_sendTransaction">, + { params: [txRequest] }: EthRequestArguments<"eth_sendTransaction">, port: Port ) { const site = await this.getSiteDetails(url, txRequest.from) @@ -640,9 +642,9 @@ export class EthTabsHandler extends TabsHandler { private async requestPermissions( url: string, - request: EthRequestArgumentsViem<"wallet_requestPermissions">, + request: EthRequestArguments<"wallet_requestPermissions">, port: Port - ): Promise> { + ): Promise> { if (request.params.length !== 1) throw new EthProviderRpcError( "This method expects an array with only 1 entry", @@ -776,40 +778,24 @@ export class EthTabsHandler extends TabsHandler { case "personal_ecRecover": { const { params: [message, signature], - } = request as EthRequestArgumentsViem<"personal_ecRecover"> + } = request return recoverMessageAddress({ message, signature }) - //return recoverPersonalSignature({ data, signature }) } case "eth_sendTransaction": - return this.sendTransaction( - url, - request as EthRequestArgumentsViem<"eth_sendTransaction">, - port - ) + return this.sendTransaction(url, request, port) case "wallet_watchAsset": //auth-less test dapp : rsksmart.github.io/metamask-rsk-custom-network/ - return this.addWatchAssetRequest( - url, - request as EthRequestArgumentsViem<"wallet_watchAsset">, - port - ) + return this.addWatchAssetRequest(url, request, port) case "wallet_addEthereumChain": //auth-less test dapp : rsksmart.github.io/metamask-rsk-custom-network/ - return this.addEthereumChain( - url, - request as EthRequestArgumentsViem<"wallet_addEthereumChain">, - port - ) + return this.addEthereumChain(url, request, port) case "wallet_switchEthereumChain": //auth-less test dapp : rsksmart.github.io/metamask-rsk-custom-network/ - return this.switchEthereumChain( - url, - request as EthRequestArgumentsViem<"wallet_switchEthereumChain"> - ) + return this.switchEthereumChain(url, request) // https://docs.metamask.io/guide/rpc-api.html#wallet-getpermissions case "wallet_getPermissions": @@ -817,11 +803,7 @@ export class EthTabsHandler extends TabsHandler { // https://docs.metamask.io/guide/rpc-api.html#wallet-requestpermissions case "wallet_requestPermissions": - return this.requestPermissions( - url, - request as EthRequestArgumentsViem<"wallet_requestPermissions">, - port - ) + return this.requestPermissions(url, request, port) default: return this.getFallbackRequest(url, request) diff --git a/apps/extension/src/core/domains/ethereum/types.ts b/apps/extension/src/core/domains/ethereum/types.ts index f2361e1b67..0d1c84652e 100644 --- a/apps/extension/src/core/domains/ethereum/types.ts +++ b/apps/extension/src/core/domains/ethereum/types.ts @@ -2,19 +2,8 @@ import type { ETH_SEND, ETH_SIGN, KnownSigningRequestIdOnly } from "@core/domain import type { CustomErc20Token } from "@core/domains/tokens/types" import { AnyEthRequest } from "@core/injectEth/types" import { BaseRequest, BaseRequestId, RequestIdOnly } from "@core/types/base" -import { BlockWithTransactions } from "@ethersproject/abstract-provider" -// import type { -// Block, -// BlockTag, -// TransactionReceipt, -// TransactionRequest, -// TransactionResponse, -// } from "@ethersproject/providers" -// Compliant with https://eips.ethereum.org/EIPS/eip-1193 -import type { InjectedAccount } from "@polkadot/extension-inject/types" import { HexString } from "@polkadot/util/types" import { EvmNetworkId } from "@talismn/chaindata-provider" -import { BigNumberish, ethers } from "ethers" import type { AddEthereumChainParameter, EIP1193Parameters, @@ -36,61 +25,6 @@ export type { EthereumRpc, } from "@talismn/chaindata-provider" -type Promisify = T | Promise - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type PromisifyArray> = { - /* The inbuilt ethers provider methods take arguments which can - * be a value or the promise of a value, this utility type converts a normal - * object type into one where all the values may be promises - */ - [K in keyof T]: Promisify -} - -export type EthRequestGetBalance = PromisifyArray<[string, ethers.providers.BlockTag]> - -export type EthRequestGetStorage = PromisifyArray<[string, BigNumberish, ethers.providers.BlockTag]> - -export type EthRequestGetTxCount = PromisifyArray<[string, ethers.providers.BlockTag]> - -export type EthRequestBlockTagOnly = PromisifyArray<[ethers.providers.BlockTag]> - -export type EthRequestSendRawTx = PromisifyArray<[string]> - -export type EthRequestCall = [ - ethers.providers.TransactionRequest, - Promise -] - -export type EthRequestEstimateGas = [ethers.providers.TransactionRequest, string] - -export type EthRequestGetBlock = PromisifyArray<[ethers.providers.BlockTag, boolean]> - -export type EthRequestTxHashOnly = PromisifyArray<[string]> - -export type EthRequestSign = [string, string] -export type EthRequestRecoverAddress = [string, `0x${string}`] - -export type EthRequestSendTx = [ethers.providers.TransactionRequest] - -export type EthRequestAddEthereumChain = [AddEthereumChainParameter] - -export type EthRequestSwitchEthereumChain = [{ chainId: string }] - -type RpcSchemaMap = { - [K in TRpcSchema[number]["Method"]]: [ - Extract["Parameters"], - Extract["ReturnType"] - ] -} - -// type RpcSchemaMapBetter = { -// [K in TRpcSchema[number]["Method"]]: { -// parameters: Extract["Parameters"] -// returnType: Extract["ReturnType"] -// } -// } - // define here the rpc methods that do not exist in viem or need to be overriden type TalismanRpcSchema = [ { @@ -114,7 +48,7 @@ type TalismanRpcSchema = [ ReturnType: `0x${string}` }, { - // TODO see if we can remove this + // TODO see if we can remove this one // override for now because of result type mismatch Method: "wallet_requestPermissions" Parameters: [permissions: { eth_accounts: Record }] @@ -124,87 +58,33 @@ type TalismanRpcSchema = [ export type FullRpcSchema = [...PublicRpcSchema, ...WalletRpcSchema, ...TalismanRpcSchema] -// export type EthRequestSignaturesPublic = RpcSchemaMap -// export type EthRequestSignaturesWallet = RpcSchemaMap -export type EthRequestSignaturesFull = RpcSchemaMap - -// TODO : replace EthRequestSignatures by EthRequestSignaturesViem -export interface EthRequestSignatures { - eth_requestAccounts: [null, InjectedAccount[]] - eth_gasPrice: [null, string] - eth_accounts: [null, string] - eth_blockNumber: [null, number] - eth_chainId: [null, string] - eth_coinbase: [null, string] - net_version: [null, string] - eth_getBalance: [EthRequestGetBalance, string] - eth_getStorageAt: [EthRequestGetStorage, string] - eth_getTransactionCount: [EthRequestGetTxCount, string] - eth_getBlockTransactionCountByHash: [EthRequestBlockTagOnly, string] - eth_getBlockTransactionCountByNumber: [EthRequestBlockTagOnly, string] - eth_getCode: [EthRequestBlockTagOnly, ethers.providers.Block] - eth_sendRawTransaction: [EthRequestSendRawTx, ethers.providers.TransactionResponse] - eth_call: [EthRequestCall, string] - estimateGas: [EthRequestEstimateGas, string] - eth_getBlockByHash: [EthRequestGetBlock, ethers.providers.Block | BlockWithTransactions] - eth_getBlockByNumber: [EthRequestGetBlock, ethers.providers.Block | BlockWithTransactions] - eth_getTransactionByHash: [EthRequestTxHashOnly, ethers.providers.TransactionResponse] - eth_getTransactionReceipt: [EthRequestTxHashOnly, ethers.providers.TransactionReceipt] - personal_sign: [EthRequestSign, string] - eth_signTypedData: [EthRequestSign, string] - eth_signTypedData_v1: [EthRequestSign, string] - eth_signTypedData_v3: [EthRequestSign, string] - eth_signTypedData_v4: [EthRequestSign, string] - eth_sendTransaction: [EthRequestSendTx, string] - personal_ecRecover: [EthRequestRecoverAddress, string] - - // EIP 747 https://github.com/ethereum/EIPs/blob/master/EIPS/eip-747.md - wallet_watchAsset: [WatchAssetBase, string] - - // pending EIP https://eips.ethereum.org/EIPS/eip-3085, defined by metamask to let dapp add chains. - // returns `null` if the request was successful, otherwise throws an error. - // metamask will automatically reject this when: - // - the rpc endpoint doesn't respond to rpc calls - // - the rpc endpoint returns a different chain id than the one specified - // - the chain id corresponds to any of the default metamask chains - wallet_addEthereumChain: [EthRequestAddEthereumChain, null] - - // pending EIP https://eips.ethereum.org/EIPS/eip-3326, defined by metamask to let dapp change chain. - // returns `null` if the request was successful, otherwise throws an error. - // if the `error.code` is `4902` then the dapp is more likely to call `wallet_addEthereumChain`. - // metamask will automatically reject this when: - // - the chain id is malformed - // - the chain with the specified id has not been added to metamask - wallet_switchEthereumChain: [EthRequestSwitchEthereumChain, null] - - // https://docs.metamask.io/guide/rpc-api.html#wallet-getpermissions - wallet_getPermissions: [null, Web3WalletPermission[]] - - // https://docs.metamask.io/guide/rpc-api.html#wallet-requestpermissions - wallet_requestPermissions: [[RequestedPermissions], Web3WalletPermission[]] +type EthRequestSignaturesMap = { + [K in TRpcSchema[number]["Method"]]: [ + Extract["Parameters"], + Extract["ReturnType"] + ] } -// export type EthRequestTypes = keyof EthRequestSignatures -// TODO yeet -export type EthResponseTypes = EthRequestSignatures[keyof EthRequestSignatures][1] -//export type EthResponseType = EthRequestSignatures[T][1] -//export type EthRequestParams = EthRequestSignatures[keyof EthRequestSignatures][0] -// export interface EthRequestArguments { -// readonly method: T -// readonly params: EthRequestSignatures[T][0] +// type EthRequestSignaturesMapBetter = { +// [K in TRpcSchema[number]["Method"]]: { +// parameters: Extract["Parameters"] +// returnType: Extract["ReturnType"] +// } // } -export type EthRequestArgumentsViem = { +export type EthRequestSignatures = EthRequestSignaturesMap + +export type EthRequestMethod = keyof EthRequestSignatures +export type EthRequestParams = EthRequestSignatures[T][0] +export type EthRequestResult = EthRequestSignatures[T][1] + +export type EthRequestArguments = { readonly method: T - readonly params: EthRequestSignaturesFull[T][0] + readonly params: EthRequestParams } export type EthRequestArgsViem = EIP1193Parameters - -export type EthRequestResultViem = - EthRequestSignaturesFull[T][1] - -export type EthRequestSignArguments = EthRequestArgumentsViem< +export type EthRequestSignArguments = EthRequestArguments< | "personal_sign" | "eth_signTypedData" | "eth_signTypedData_v1" @@ -216,21 +96,6 @@ export interface EthProviderMessage { readonly type: string readonly data: unknown } -// export type AddEthereumChainParameter = { -// /** A 0x-prefixed hexadecimal string */ -// chainId: string -// chainName: string -// nativeCurrency: { -// name: string -// /** 2-6 characters long */ -// symbol: string -// decimals: 18 -// } -// rpcUrls: string[] -// blockExplorerUrls?: string[] -// /** Currently ignored by metamask */ -// iconUrls?: string[] -// } export type EthTxSignAndSend = { evmNetworkId: EvmNetworkId @@ -299,11 +164,11 @@ export type RequestUpsertCustomEvmNetwork = { export interface EthMessages { // all ethereum calls - "pub(eth.request)": [AnyEthRequest, EthResponseTypes] + "pub(eth.request)": [AnyEthRequest, unknown] "pub(eth.subscribe)": [null, boolean, EthProviderMessage] "pub(eth.mimicMetaMask)": [null, boolean] // eth signing message signatures - "pri(eth.request)": [AnyEthRequestChainId, EthResponseTypes] + "pri(eth.request)": [AnyEthRequestChainId, unknown] "pri(eth.transactions.count)": [EthNonceRequest, number] "pri(eth.signing.signAndSend)": [EthTxSignAndSend, HexString] "pri(eth.signing.sendSigned)": [EthTxSendSigned, HexString] From 7d7f8cb4d0981d2b7e011bb0959ed00cf2cc8908 Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Thu, 2 Nov 2023 17:25:16 +0900 Subject: [PATCH 25/50] fix: bunch of fixes --- apps/extension/package.json | 4 +- .../src/core/domains/ethereum/errors.ts | 13 +- .../ethereum/transactionCountManager.ts | 5 + .../transactions/watchEthereumTransaction.ts | 27 +- .../core/notifications/createNotification.ts | 8 +- .../src/core/util/getFeeHistoryAnalysis.ts | 37 ++- .../popup/pages/Sign/ethereum/Transaction.tsx | 4 +- .../ui/domains/Ethereum/useEthTransaction.ts | 2 +- .../SendFunds/SendFundsHardwareEthereum.tsx | 3 +- .../ui/domains/Sign/SignHardwareEthereum.tsx | 2 + .../ui/domains/Sign/SignLedgerEthereum.tsx | 195 +++++++------ .../EthereumSignTransactionRequestContext.ts | 1 + .../Sign/ViewDetails/ViewDetailsEth.tsx | 5 + packages/balances-evm-erc20/package.json | 2 +- packages/balances-evm-native/package.json | 2 +- packages/chain-connector-evm/package.json | 2 +- packages/util/src/throwAfter.ts | 2 +- yarn.lock | 271 +++++++++++++----- 18 files changed, 393 insertions(+), 192 deletions(-) diff --git a/apps/extension/package.json b/apps/extension/package.json index 30e1c5ebd3..05c3557ed5 100644 --- a/apps/extension/package.json +++ b/apps/extension/package.json @@ -14,7 +14,7 @@ "@floating-ui/react-dom": "2.0.1", "@headlessui/react": "1.7.13", "@hookform/resolvers": "2.9.11", - "@ledgerhq/hw-app-eth": "6.33.3", + "@ledgerhq/hw-app-eth": "6.34.8", "@ledgerhq/hw-transport-webusb": "6.27.14", "@metamask/browser-passworder": "4.1.0", "@metamask/eth-sig-util": "5.1.0", @@ -138,7 +138,7 @@ "typescript": "^5.2.2", "url-join": "^5.0.0", "uuid": "^8.3.2", - "viem": "^1.16.6", + "viem": "^1.18.3", "webextension-polyfill": "0.8.0", "webpack": "^5.88.1", "webpack-cli": "^4.10.0", diff --git a/apps/extension/src/core/domains/ethereum/errors.ts b/apps/extension/src/core/domains/ethereum/errors.ts index a7f7e1a3c4..b21ba1adc5 100644 --- a/apps/extension/src/core/domains/ethereum/errors.ts +++ b/apps/extension/src/core/domains/ethereum/errors.ts @@ -98,8 +98,19 @@ export const getHumanReadableErrorMessage = (error: unknown) => { code, reason, error: serverError, + shortMessage, + details, + } = error as { + code?: string + reason?: string // eslint-disable-next-line @typescript-eslint/no-explicit-any - } = error as { code?: string; reason?: string; error?: any } + error?: any + shortMessage?: string + details?: string + } + + if (details) return details + if (shortMessage) return shortMessage if (serverError) { const message = serverError.error?.message ?? serverError.reason ?? serverError.message diff --git a/apps/extension/src/core/domains/ethereum/transactionCountManager.ts b/apps/extension/src/core/domains/ethereum/transactionCountManager.ts index 79b9416510..914f53a769 100644 --- a/apps/extension/src/core/domains/ethereum/transactionCountManager.ts +++ b/apps/extension/src/core/domains/ethereum/transactionCountManager.ts @@ -42,3 +42,8 @@ export const incrementTransactionCount = (address: string, evmNetworkId: EvmNetw dicTransactionCount.set(key, count + 1) } + +export const resetTransactionCount = (address: string, evmNetworkId: EvmNetworkId) => { + const key = getKey(address, evmNetworkId) + dicTransactionCount.delete(key) +} diff --git a/apps/extension/src/core/domains/transactions/watchEthereumTransaction.ts b/apps/extension/src/core/domains/transactions/watchEthereumTransaction.ts index 84cf8cf4a0..dfa90866ef 100644 --- a/apps/extension/src/core/domains/transactions/watchEthereumTransaction.ts +++ b/apps/extension/src/core/domains/transactions/watchEthereumTransaction.ts @@ -1,14 +1,17 @@ import { settingsStore } from "@core/domains/app" import { addEvmTransaction, updateTransactionStatus } from "@core/domains/transactions/helpers" +import { log } from "@core/log" import { createNotification } from "@core/notifications" import { chainConnectorEvm } from "@core/rpcs/chain-connector-evm" import { chaindataProvider } from "@core/rpcs/chaindata" import * as Sentry from "@sentry/browser" import { EvmNetworkId } from "@talismn/chaindata-provider" +import { sleep, throwAfter } from "@talismn/util" import { nanoid } from "nanoid" import urlJoin from "url-join" -import { TransactionRequest } from "viem" +import { Hex, TransactionReceipt, TransactionRequest } from "viem" +import { resetTransactionCount } from "../ethereum/transactionCountManager" import { WatchTransactionOptions } from "./types" export const watchEthereumTransaction = async ( @@ -38,9 +41,21 @@ export const watchEthereumTransaction = async ( try { await addEvmTransaction(evmNetworkId, hash, unsigned, { siteUrl, ...transferInfo }) - const receipt = await client.waitForTransactionReceipt({ - hash, - }) + // Observed on polygon network (tried multiple rpcs) that waitForTransactionReceipt throws TransactionNotFoundError & BlockNotFoundError randomly + // so we retry as long as we don't get a receipt, with a timeout on our side + const getTransactionReceipt = async (hash: Hex): Promise => { + try { + return await client.waitForTransactionReceipt({ hash }) + } catch (err) { + await sleep(4000) + return getTransactionReceipt(hash) + } + } + + const receipt = await Promise.race([ + getTransactionReceipt(hash), + throwAfter(5 * 60_000, "Transaction not found"), + ]) // to test failing transactions, swap on busy AMM pools with a 0.05% slippage limit updateTransactionStatus( @@ -57,8 +72,12 @@ export const watchEthereumTransaction = async ( txUrl ) } catch (err) { + log.error("watchEthereumTransaction error: ", { err }) updateTransactionStatus(hash, "error") + // observed on polygon, some submitted transactions are not found, in which case we must reset the nonce counter to avoid being stuck + resetTransactionCount(unsigned.from, evmNetworkId) + if (withNotifications) await createNotification("error", networkName, txUrl, err as Error) // eslint-disable-next-line no-console else console.error("Failed to watch transaction", { err }) diff --git a/apps/extension/src/core/notifications/createNotification.ts b/apps/extension/src/core/notifications/createNotification.ts index d2e2223a98..7054d4abc0 100644 --- a/apps/extension/src/core/notifications/createNotification.ts +++ b/apps/extension/src/core/notifications/createNotification.ts @@ -8,7 +8,7 @@ export type NotificationType = "submitted" | "success" | "error" const getNotificationOptions = ( type: NotificationType, networkName: string, - error?: Error & { reason?: string } + error?: Error & { shortMessage?: string; reason?: string } ): Browser.Notifications.CreateNotificationOptions => { switch (type) { case "submitted": @@ -29,7 +29,11 @@ const getNotificationOptions = ( return { type: "basic", title: "Transaction failed", - message: error?.reason ?? error?.message ?? `Failed transaction on ${networkName}.`, + message: + error?.shortMessage ?? + error?.reason ?? + error?.message ?? + `Failed transaction on ${networkName}.`, iconUrl: "/images/tx-nok.png", } } diff --git a/apps/extension/src/core/util/getFeeHistoryAnalysis.ts b/apps/extension/src/core/util/getFeeHistoryAnalysis.ts index 1e060ed4b5..1294b0c473 100644 --- a/apps/extension/src/core/util/getFeeHistoryAnalysis.ts +++ b/apps/extension/src/core/util/getFeeHistoryAnalysis.ts @@ -1,13 +1,15 @@ import { EthBaseFeeTrend } from "@core/domains/signing/types" +import { log } from "@core/log" import * as Sentry from "@sentry/browser" -import { PublicClient, parseGwei } from "viem" +import { PublicClient, formatGwei, parseGwei } from "viem" -const BLOCKS_HISTORY_LENGTH = 4 +const BLOCKS_HISTORY_LENGTH = 5 const REWARD_PERCENTILES = [10, 20, 30] +const LIVE_DEBUG = true type EthBasePriorityOptionsEip1559 = Record<"low" | "medium" | "high", bigint> -export const DEFAULT_ETH_PRIORITY_OPTIONS: EthBasePriorityOptionsEip1559 = { +const DEFAULT_ETH_PRIORITY_OPTIONS: EthBasePriorityOptionsEip1559 = { low: parseGwei("1.5"), medium: parseGwei("1.6"), high: parseGwei("1.7"), @@ -85,11 +87,11 @@ export const getFeeHistoryAnalysis = async ( ? "increasing" : "toTheMoon" - return { + const result: FeeHistoryAnalysis = { maxPriorityPerGasOptions: { low: medMaxPriorityFeePerGas[0], - medium: medMaxPriorityFeePerGas[1], - high: medMaxPriorityFeePerGas[2], + medium: (medMaxPriorityFeePerGas[1] * 102n) / 100n, + high: (medMaxPriorityFeePerGas[2] * 104n) / 100n, }, avgGasUsedRatio: avgGasUsedRatio, isValid: !feeHistory.gasUsedRatio.includes(0), // if a 0 is found, not all blocks contained a transaction @@ -98,6 +100,29 @@ export const getFeeHistoryAnalysis = async ( nextBaseFee, baseFeeTrend, } + + if (LIVE_DEBUG) { + log.log( + "rewards", + feeHistory.reward?.map((arr) => arr.map((reward) => `${formatGwei(reward)} GWEI`)) + ) + log.log("baseFee", `${formatGwei(result.nextBaseFee)} GWEI`) + log.log( + "medMaxPriorityFeePerGas", + medMaxPriorityFeePerGas.map((fee) => `${formatGwei(fee)} GWEI`) + ) + log.log( + "maxPriorityPerGasOptions", + [ + result.maxPriorityPerGasOptions.low, + result.maxPriorityPerGasOptions.medium, + result.maxPriorityPerGasOptions.high, + ].map((fee) => `${formatGwei(fee)} GWEI`) + ) + log.log("=========================================") + } + + return result } catch (err) { Sentry.captureException(err) throw new Error("Failed to load fee history", { cause: err as Error }) diff --git a/apps/extension/src/ui/apps/popup/pages/Sign/ethereum/Transaction.tsx b/apps/extension/src/ui/apps/popup/pages/Sign/ethereum/Transaction.tsx index fd77cef15f..e253f312fa 100644 --- a/apps/extension/src/ui/apps/popup/pages/Sign/ethereum/Transaction.tsx +++ b/apps/extension/src/ui/apps/popup/pages/Sign/ethereum/Transaction.tsx @@ -17,7 +17,6 @@ import { EthSignBody } from "@ui/domains/Sign/Ethereum/EthSignBody" import { SignAlertMessage } from "@ui/domains/Sign/SignAlertMessage" import { SignHardwareEthereum } from "@ui/domains/Sign/SignHardwareEthereum" import { useEthSignTransactionRequest } from "@ui/domains/Sign/SignRequestContext" -import { useAlec } from "@ui/hooks/useAlec" import { Suspense, useCallback, useEffect, useMemo } from "react" import { useTranslation } from "react-i18next" import { Button, Tooltip, TooltipContent, TooltipTrigger } from "talisman-ui" @@ -132,8 +131,6 @@ export const EthSignTransactionRequest = () => { [setPriority, setReady] ) - useAlec("EthSignTransactionRequest", { transaction, txDetails, network }) - return ( }> @@ -203,6 +200,7 @@ export const EthSignTransactionRequest = () => { ) : null} {account && request && account.isHardware ? ( { - const { from, evmTransaction, sendWithSignature, setIsLocked } = useSendFunds() + const { from, evmTransaction, sendWithSignature, setIsLocked, evmNetwork } = useSendFunds() const account = useAccountByAddress(from) as AccountJsonDcent const [error, setError] = useState() @@ -29,6 +29,7 @@ export const SendFundsHardwareEthereum = () => { return ( import("./SignLedgerEthereum")) export type SignHardwareEthereumProps = { + evmNetworkId?: EvmNetworkId account: AccountJsonAny method: EthSignMessageMethod | "eth_sendTransaction" // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/apps/extension/src/ui/domains/Sign/SignLedgerEthereum.tsx b/apps/extension/src/ui/domains/Sign/SignLedgerEthereum.tsx index b09747bbce..637ae68207 100644 --- a/apps/extension/src/ui/domains/Sign/SignLedgerEthereum.tsx +++ b/apps/extension/src/ui/domains/Sign/SignLedgerEthereum.tsx @@ -2,16 +2,24 @@ import { AccountJsonHardwareEthereum } from "@core/domains/accounts/types" import { EthSignMessageMethod } from "@core/domains/signing/types" import i18next from "@core/i18nConfig" import { log } from "@core/log" -import { bufferToHex, isHexString, stripHexPrefix } from "@ethereumjs/util" +import { bufferToHex, stripHexPrefix } from "@ethereumjs/util" import LedgerEthereumApp from "@ledgerhq/hw-app-eth" import { SignTypedDataVersion, TypedDataUtils } from "@metamask/eth-sig-util" import { classNames } from "@talismn/util" import { useLedgerEthereum } from "@ui/hooks/ledger/useLedgerEthereum" -import { ethers } from "ethers" import { FC, useCallback, useEffect, useMemo, useState } from "react" import { useTranslation } from "react-i18next" import { Drawer } from "talisman-ui" import { Button } from "talisman-ui" +import { + Signature, + TransactionRequest, + TransactionSerializable, + hexToBigInt, + isHex, + serializeTransaction, + signatureToHex, +} from "viem" import { LedgerConnectionStatus, @@ -20,106 +28,93 @@ import { import { LedgerSigningStatus } from "./LedgerSigningStatus" import { SignHardwareEthereumProps } from "./SignHardwareEthereum" +const toSignature = ({ v, r, s }: { v: string | number; r: string; s: string }): Signature => ({ + v: typeof v === "string" ? hexToBigInt(`0x${v}`) : BigInt(v), + r: `0x${r}`, + s: `0x${s}`, +}) + const signWithLedger = async ( ledger: LedgerEthereumApp, + chainId: number, method: EthSignMessageMethod | "eth_sendTransaction", // eslint-disable-next-line @typescript-eslint/no-explicit-any - payload: any, + payload: unknown, accountPath: string ): Promise<`0x${string}`> => { - // TODO Uncomment wen this method actually works - // if (["eth_signTypedData", "eth_signTypedData_v3", "eth_signTypedData_v4"].includes(method)) { - // const jsonMessage = typeof payload === "string" ? JSON.parse(payload) : payload - - // const sig = await ledger.signEIP712Message(accountPath, jsonMessage) - // sig.r = "0x" + sig.r - // sig.s = "0x" + sig.s - // return ethers.utils.joinSignature(sig) as `0x${string}` - // } - - if (method === "eth_signTypedData_v4") { - const jsonMessage = typeof payload === "string" ? JSON.parse(payload) : payload - - // at this time we cannot use ledger.signEIP712Message() (see comments above) without altering the payload (missing salt & wrong time for chainId) - // => let's hash using MM libraries, what's annoying is that the user will only see those hashes on his ledger - - const { domain, types, primaryType, message } = TypedDataUtils.sanitizeData(jsonMessage) - const domainSeparatorHex = TypedDataUtils.hashStruct( - "EIP712Domain", - domain, - types, - SignTypedDataVersion.V4 - ).toString("hex") - const hashStructMessageHex = TypedDataUtils.hashStruct( - primaryType as string, - message, - types, - SignTypedDataVersion.V4 - ).toString("hex") - - const sig = await ledger.signEIP712HashedMessage( - accountPath, - domainSeparatorHex, - hashStructMessageHex - ) - sig.r = "0x" + sig.r - sig.s = "0x" + sig.s - return ethers.utils.joinSignature(sig) as `0x${string}` - } - if (method === "personal_sign") { - // ensure that it is hex encoded - const messageHex = isHexString(payload) ? payload : bufferToHex(Buffer.from(payload, "utf8")) - - const sig = await ledger.signPersonalMessage(accountPath, stripHexPrefix(messageHex)) - sig.r = "0x" + sig.r - sig.s = "0x" + sig.s - return ethers.utils.joinSignature(sig) as `0x${string}` - } - if (method === "eth_sendTransaction") { - const { - accessList, - to, - nonce, - gasLimit, - gasPrice, - data, - value, - chainId, - type, - maxPriorityFeePerGas, - maxFeePerGas, - } = await ethers.utils.resolveProperties(payload as ethers.providers.TransactionRequest) - - const baseTx: ethers.utils.UnsignedTransaction = { - to, - gasLimit, - chainId, - type, + switch (method) { + case "eth_signTypedData_v3": + case "eth_signTypedData_v4": { + const jsonMessage = typeof payload === "string" ? JSON.parse(payload) : payload + + try { + // Nano S doesn't support signEIP712Message, fallback to signEIP712HashedMessage in case of error + // see https://github.com/LedgerHQ/ledger-live/tree/develop/libs/ledgerjs/packages/hw-app-eth#signeip712message + + // eslint-disable-next-line no-var + var sig = await ledger.signEIP712Message(accountPath, jsonMessage) + } catch { + // fallback for ledger Nano S + const { domain, types, primaryType, message } = TypedDataUtils.sanitizeData(jsonMessage) + const domainSeparatorHex = TypedDataUtils.hashStruct( + "EIP712Domain", + domain, + types, + SignTypedDataVersion.V4 + ).toString("hex") + const hashStructMessageHex = TypedDataUtils.hashStruct( + primaryType as string, + message, + types, + SignTypedDataVersion.V4 + ).toString("hex") + + sig = await ledger.signEIP712HashedMessage( + accountPath, + domainSeparatorHex, + hashStructMessageHex + ) + } + + return signatureToHex(toSignature(sig)) } - if (nonce !== undefined) baseTx.nonce = ethers.BigNumber.from(nonce).toNumber() - if (maxPriorityFeePerGas) baseTx.maxPriorityFeePerGas = maxPriorityFeePerGas - if (maxFeePerGas) baseTx.maxFeePerGas = maxFeePerGas - if (gasPrice) baseTx.gasPrice = gasPrice - if (data) baseTx.data = data - if (value) baseTx.value = value - if (accessList) baseTx.accessList = accessList - - const unsignedTx = stripHexPrefix(ethers.utils.serializeTransaction(baseTx)) - const sig = await ledger.signTransaction(accountPath, unsignedTx, null) // resolver) - - return ethers.utils.serializeTransaction(baseTx, { - v: ethers.BigNumber.from("0x" + sig.v).toNumber(), - r: "0x" + sig.r, - s: "0x" + sig.s, - }) as `0x${string}` - } + case "personal_sign": { + // ensure that it is hex encoded + const messageHex = isHex(payload) + ? payload + : bufferToHex(Buffer.from(payload as string, "utf8")) + + const sig = await ledger.signPersonalMessage(accountPath, stripHexPrefix(messageHex)) + + return signatureToHex(toSignature(sig)) + } - // sign typed data v0, v1, v3... - throw new Error(i18next.t("This type of message cannot be signed with ledger.")) + case "eth_sendTransaction": { + const txRequest = payload as TransactionRequest + const baseTx: TransactionSerializable = { + ...txRequest, + // viem's legacy serialization changes the chainId once deserialized, tx can't be submitted + // can be tested on BSC + type: txRequest.type === "legacy" ? "eip2930" : "eip1559", + chainId, + } + + const serialized = serializeTransaction(baseTx) + + const sig = await ledger.signTransaction(accountPath, stripHexPrefix(serialized), null) + + return serializeTransaction(baseTx, toSignature(sig)) + } + + default: { + throw new Error(i18next.t("This type of message cannot be signed with ledger.")) + } + } } const SignLedgerEthereum: FC = ({ + evmNetworkId, account, className = "", method, @@ -135,6 +130,11 @@ const SignLedgerEthereum: FC = ({ const [error, setError] = useState(null) const { ledger, refresh, status, message, isReady, requiresManualRetry } = useLedgerEthereum() + const inputsReady = useMemo( + () => !!payload && (method !== "eth_sendTransaction" || !!evmNetworkId), + [evmNetworkId, method, payload] + ) + // reset useEffect(() => { setIsSigned(false) @@ -156,14 +156,13 @@ const SignLedgerEthereum: FC = ({ }, [refresh, setError]) const signLedger = useCallback(async () => { - if (!ledger || !onSigned) { - return - } + if (!ledger || !onSigned || !inputsReady) return setError(null) try { const signature = await signWithLedger( ledger, + Number(evmNetworkId), method, payload, (account as AccountJsonHardwareEthereum).path @@ -186,7 +185,7 @@ const SignLedgerEthereum: FC = ({ ) else setError(error.reason ?? error.message) } - }, [ledger, onSigned, method, payload, account, t]) + }, [ledger, onSigned, inputsReady, evmNetworkId, method, payload, account, t]) const handleSendClick = useCallback(() => { setIsSigning(true) @@ -201,7 +200,13 @@ const SignLedgerEthereum: FC = ({ {!error && ( <> {isReady ? ( - ) : ( diff --git a/apps/extension/src/ui/domains/Sign/SignRequestContext/EthereumSignTransactionRequestContext.ts b/apps/extension/src/ui/domains/Sign/SignRequestContext/EthereumSignTransactionRequestContext.ts index 977ad2b23b..4c1e0683e2 100644 --- a/apps/extension/src/ui/domains/Sign/SignRequestContext/EthereumSignTransactionRequestContext.ts +++ b/apps/extension/src/ui/domains/Sign/SignRequestContext/EthereumSignTransactionRequestContext.ts @@ -70,6 +70,7 @@ const useEthSignTransactionRequestProvider = ({ id }: KnownSigningRequestIdOnly< } catch (err) { log.error("failed to approve hardware", { err }) baseRequest.setStatus.error((err as Error).message) + setIsPayloadLocked(false) } }, [baseRequest, transaction] diff --git a/apps/extension/src/ui/domains/Sign/ViewDetails/ViewDetailsEth.tsx b/apps/extension/src/ui/domains/Sign/ViewDetails/ViewDetailsEth.tsx index 4c5717290a..ba1c7bf2f4 100644 --- a/apps/extension/src/ui/domains/Sign/ViewDetails/ViewDetailsEth.tsx +++ b/apps/extension/src/ui/domains/Sign/ViewDetails/ViewDetailsEth.tsx @@ -159,8 +159,13 @@ const ViewDetailsContent: FC = ({ onClose }) => { typeof networkUsage === "number" ? `${Math.round(networkUsage * 100)}%` : t("N/A") } /> + {transaction?.type === "eip1559" && ( <> + } + /> } diff --git a/packages/balances-evm-erc20/package.json b/packages/balances-evm-erc20/package.json index 8fb55b804b..7bf751f184 100644 --- a/packages/balances-evm-erc20/package.json +++ b/packages/balances-evm-erc20/package.json @@ -31,7 +31,7 @@ "@talismn/util": "workspace:*", "anylogger": "^1.0.11", "lodash": "4.17.21", - "viem": "^1.16.6" + "viem": "^1.18.3" }, "devDependencies": { "@polkadot/util": "^11.1.1", diff --git a/packages/balances-evm-native/package.json b/packages/balances-evm-native/package.json index ecc32bcfcc..073a9d68fe 100644 --- a/packages/balances-evm-native/package.json +++ b/packages/balances-evm-native/package.json @@ -31,7 +31,7 @@ "@talismn/util": "workspace:*", "anylogger": "^1.0.11", "lodash": "4.17.21", - "viem": "^1.16.6" + "viem": "^1.18.3" }, "devDependencies": { "@talismn/eslint-config": "workspace:*", diff --git a/packages/chain-connector-evm/package.json b/packages/chain-connector-evm/package.json index 664bbe07d3..a0492bb477 100644 --- a/packages/chain-connector-evm/package.json +++ b/packages/chain-connector-evm/package.json @@ -33,7 +33,7 @@ "anylogger": "^1.0.11", "ethers": "5.7.2", "lodash": "4.17.21", - "viem": "^1.16.6" + "viem": "^1.18.3" }, "devDependencies": { "@talismn/eslint-config": "workspace:*", diff --git a/packages/util/src/throwAfter.ts b/packages/util/src/throwAfter.ts index 321d130bf8..8e97741f9c 100644 --- a/packages/util/src/throwAfter.ts +++ b/packages/util/src/throwAfter.ts @@ -1,2 +1,2 @@ export const throwAfter = (ms: number, reason: string) => - new Promise((_, reject) => setTimeout(() => reject(reason), ms)) + new Promise((_, reject) => setTimeout(() => reject(reason), ms)) diff --git a/yarn.lock b/yarn.lock index ffe273f7a6..498b2438e7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4676,12 +4676,12 @@ __metadata: languageName: node linkType: hard -"@ledgerhq/cryptoassets@npm:^9.5.0": - version: 9.5.0 - resolution: "@ledgerhq/cryptoassets@npm:9.5.0" +"@ledgerhq/cryptoassets@npm:^11.0.1": + version: 11.0.1 + resolution: "@ledgerhq/cryptoassets@npm:11.0.1" dependencies: invariant: 2 - checksum: 15c226edf8e90e7c858b6faa4e322f4d547c185c34f9f4b623b3a7399d8f596892867bb7ed4c546aa2e3f1c8627ea425f0476914d49f14690f7a07671c32c62e + checksum: 0dcb5ae8bf2958d746ebadba6309ee17c3a0225df01852ef2e9f3f24e590ac80ff5ef7aad59fc1f58386347cd4d5a0f3a9bd0445086665b2f8568eb9b0606c85 languageName: node linkType: hard @@ -4709,19 +4709,31 @@ __metadata: languageName: node linkType: hard -"@ledgerhq/domain-service@npm:^1.1.1": - version: 1.1.1 - resolution: "@ledgerhq/domain-service@npm:1.1.1" +"@ledgerhq/devices@npm:^8.0.7": + version: 8.0.7 + resolution: "@ledgerhq/devices@npm:8.0.7" dependencies: - "@ledgerhq/cryptoassets": ^9.5.0 - "@ledgerhq/errors": ^6.12.5 + "@ledgerhq/errors": ^6.14.0 + "@ledgerhq/logs": ^6.10.1 + rxjs: 6 + semver: ^7.3.5 + checksum: 9dff60fc6b443d38a508e3f83b0ee3989fd58061c59345dd6ded524b5fd01b9a2840eb9bde7979829ae6d50d8e0c0e552e21284a1f10d8636a11ed737ca464a2 + languageName: node + linkType: hard + +"@ledgerhq/domain-service@npm:^1.1.13": + version: 1.1.13 + resolution: "@ledgerhq/domain-service@npm:1.1.13" + dependencies: + "@ledgerhq/cryptoassets": ^11.0.1 + "@ledgerhq/errors": ^6.14.0 "@ledgerhq/logs": ^6.10.1 - "@ledgerhq/types-live": ^6.34.0 + "@ledgerhq/types-live": ^6.41.1 axios: ^1.3.4 - eip55: ^2.1.0 + eip55: ^2.1.1 react: ^17.0.2 react-dom: ^17.0.2 - checksum: 08f0b3f1d1cfa4e25fd5451791f867577caba3113d949d0f2309e39369520b8e89cfc66df1e52a6a6cdbf5575454013d24ce086499c09e4a614b3aa9aa065120 + checksum: f4234dd178837d72d8dfd25b487bb1c4625ca04cd41f9a60ab19d918a58343d37ba4695d11a1ba93f5b8656616b1f83bd139197f482ad00b2e7edec08d70ea14 languageName: node linkType: hard @@ -4739,32 +4751,53 @@ __metadata: languageName: node linkType: hard -"@ledgerhq/hw-app-eth@npm:6.33.3": - version: 6.33.3 - resolution: "@ledgerhq/hw-app-eth@npm:6.33.3" +"@ledgerhq/errors@npm:^6.14.0": + version: 6.14.0 + resolution: "@ledgerhq/errors@npm:6.14.0" + checksum: 2c4e7bf126952a781d9226752b9401e842a5a773861128d182e9f40f1f46bf6cd8667c2ca64a1c52254d151bc6bec6d85ea62ba8b3dd35e46bbe47a90c291830 + languageName: node + linkType: hard + +"@ledgerhq/evm-tools@npm:^1.0.9": + version: 1.0.9 + resolution: "@ledgerhq/evm-tools@npm:1.0.9" + dependencies: + "@ledgerhq/cryptoassets": ^11.0.1 + "@ledgerhq/live-env": ^0.6.0 + "@ledgerhq/live-network": ^1.1.7 + crypto-js: 4.1.1 + ethers: 5.7.2 + checksum: faa1027ee9ee6f132ee10c66a0c49a300ab688a0a2a8069c153a75047955c3779c62e331d237028716bb60e052d038e0395bf87d6570ce4eb66cd8d3b8d05e9f + languageName: node + linkType: hard + +"@ledgerhq/hw-app-eth@npm:6.34.8": + version: 6.34.8 + resolution: "@ledgerhq/hw-app-eth@npm:6.34.8" dependencies: "@ethersproject/abi": ^5.5.0 "@ethersproject/rlp": ^5.5.0 - "@ledgerhq/cryptoassets": ^9.5.0 - "@ledgerhq/domain-service": ^1.1.1 - "@ledgerhq/errors": ^6.12.5 - "@ledgerhq/hw-transport": ^6.28.3 - "@ledgerhq/hw-transport-mocker": ^6.27.14 + "@ledgerhq/cryptoassets": ^11.0.1 + "@ledgerhq/domain-service": ^1.1.13 + "@ledgerhq/errors": ^6.14.0 + "@ledgerhq/evm-tools": ^1.0.9 + "@ledgerhq/hw-transport": ^6.28.8 + "@ledgerhq/hw-transport-mocker": ^6.27.19 "@ledgerhq/logs": ^6.10.1 + "@ledgerhq/types-live": ^6.41.1 axios: ^1.3.4 - bignumber.js: ^9.1.0 - crypto-js: ^4.1.1 - checksum: 65ccc79acd2400b01cf42b9757102537ce9dd3ad3a1c1a863c6b1f43790b2f6134b3616cd31123ff82216c1ab47b913506ce7984178d0e85b72dbcb90fc95ded + bignumber.js: ^9.1.2 + checksum: d58d0c73d7d8a69ce8938ff8fb114bbbcea1b5edb2ac0db38ad209bf83c2eeeba291861f5e3e2406567be1618732df11699063f6412c3d499881fbfbab2dc905 languageName: node linkType: hard -"@ledgerhq/hw-transport-mocker@npm:^6.27.14": - version: 6.27.14 - resolution: "@ledgerhq/hw-transport-mocker@npm:6.27.14" +"@ledgerhq/hw-transport-mocker@npm:^6.27.19": + version: 6.27.19 + resolution: "@ledgerhq/hw-transport-mocker@npm:6.27.19" dependencies: - "@ledgerhq/hw-transport": ^6.28.3 + "@ledgerhq/hw-transport": ^6.28.8 "@ledgerhq/logs": ^6.10.1 - checksum: 328be53eeb443e72da9b5a3d08e86681c24553a32bfa8c2c5076398893e4edea0aac53e8f729fc82bf6948401e9847594fb92bd7a9a916c83a8e281025e03875 + checksum: f235f3263fa0366a923334d931d8fe24779cbbda33beb7f799356f11e18fb7cce29df1228964ed18bccdf511f9d0ac2077147aca8caf820c0bb1fa0a7887fb01 languageName: node linkType: hard @@ -4855,6 +4888,52 @@ __metadata: languageName: node linkType: hard +"@ledgerhq/hw-transport@npm:^6.28.8": + version: 6.28.8 + resolution: "@ledgerhq/hw-transport@npm:6.28.8" + dependencies: + "@ledgerhq/devices": ^8.0.7 + "@ledgerhq/errors": ^6.14.0 + events: ^3.3.0 + checksum: 636a1d8229e10673745a322ed4714f85ef395c8bdf39ed14bef859fbc8132ee271a2cf164042a06d7a4d24ee0bbb77c6c29e15c950b3f58af53ad161f9854c51 + languageName: node + linkType: hard + +"@ledgerhq/live-env@npm:^0.6.0": + version: 0.6.0 + resolution: "@ledgerhq/live-env@npm:0.6.0" + dependencies: + rxjs: ^6.6.7 + utility-types: ^3.10.0 + checksum: 1833d07a42e9bd22aa889ae507544bcaefd1a8423d73feaa252ffb0039b9fbf7ca788547923b517e5b843c1aa21665f4f517aaa84ab062e7f267594494bb6a8f + languageName: node + linkType: hard + +"@ledgerhq/live-network@npm:^1.1.7": + version: 1.1.7 + resolution: "@ledgerhq/live-network@npm:1.1.7" + dependencies: + "@ledgerhq/errors": ^6.14.0 + "@ledgerhq/live-env": ^0.6.0 + "@ledgerhq/live-promise": ^0.0.1 + "@ledgerhq/logs": ^6.10.1 + "@types/node": ^20.2.5 + axios: 0.26.1 + invariant: ^2.2.2 + lru-cache: ^7.14.1 + checksum: f82c80e284cf47e4e19c3ed84cad6745ba89fd11d83610764a4d7668c99e262f00ca21e0fa8e0122dd5c6fdb78db8d85eb3a7e1a3bf2d79eec3238a926b3c286 + languageName: node + linkType: hard + +"@ledgerhq/live-promise@npm:^0.0.1": + version: 0.0.1 + resolution: "@ledgerhq/live-promise@npm:0.0.1" + dependencies: + "@ledgerhq/logs": ^6.10.1 + checksum: 7bfbab505d45646b57e2b35468a6226bcdbbaca37a89685d92f9b38726d1da236fa9db24863060ded9160caacb52b67c127bd5ddcc05c629e13f8f90a9e09473 + languageName: node + linkType: hard + "@ledgerhq/logs@npm:^6.10.1": version: 6.10.1 resolution: "@ledgerhq/logs@npm:6.10.1" @@ -4862,13 +4941,13 @@ __metadata: languageName: node linkType: hard -"@ledgerhq/types-live@npm:^6.34.0": - version: 6.34.0 - resolution: "@ledgerhq/types-live@npm:6.34.0" +"@ledgerhq/types-live@npm:^6.41.1": + version: 6.41.1 + resolution: "@ledgerhq/types-live@npm:6.41.1" dependencies: - bignumber.js: ^9.1.0 + bignumber.js: ^9.1.2 rxjs: 6 - checksum: 42829bee0f1343b60197c60bc5ba00a104bdefa2c2f17d7bcb52e37359a6c78b272a9fb98eec63376a34eb7867f5b6cabc43d1bfc10dd6c721678c2def8213db + checksum: 2b9089ca62bca7bc975ff1c2affc9e592be4146aba8edc858c58f20f867bfc28eee1de4a571c44574a321bfafa064d2b9357d1b053071d799de5e53ba5234453 languageName: node linkType: hard @@ -7586,7 +7665,7 @@ __metadata: lodash: 4.17.21 ts-jest: ^28.0.2 typescript: ^5.2.2 - viem: ^1.16.6 + viem: ^1.18.3 peerDependencies: "@polkadot/util": 11.x languageName: unknown @@ -7609,7 +7688,7 @@ __metadata: lodash: 4.17.21 ts-jest: ^28.0.2 typescript: ^5.2.2 - viem: ^1.16.6 + viem: ^1.18.3 languageName: unknown linkType: soft @@ -7854,7 +7933,7 @@ __metadata: lodash: 4.17.21 ts-jest: ^28.0.2 typescript: ^5.2.2 - viem: ^1.16.6 + viem: ^1.18.3 languageName: unknown linkType: soft @@ -8744,6 +8823,15 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^20.2.5": + version: 20.8.10 + resolution: "@types/node@npm:20.8.10" + dependencies: + undici-types: ~5.26.4 + checksum: 7c61190e43e8074a1b571e52ff14c880bc67a0447f2fe5ed0e1a023eb8a23d5f815658edb98890f7578afe0f090433c4a635c7c87311762544e20dd78723e515 + languageName: node + linkType: hard + "@types/normalize-package-data@npm:^2.4.0": version: 2.4.1 resolution: "@types/normalize-package-data@npm:2.4.1" @@ -10826,6 +10914,15 @@ __metadata: languageName: node linkType: hard +"axios@npm:0.26.1": + version: 0.26.1 + resolution: "axios@npm:0.26.1" + dependencies: + follow-redirects: ^1.14.8 + checksum: d9eb58ff4bc0b36a04783fc9ff760e9245c829a5a1052ee7ca6013410d427036b1d10d04e7380c02f3508c5eaf3485b1ae67bd2adbfec3683704745c8d7a6e1a + languageName: node + linkType: hard + "axios@npm:^0.21.0": version: 0.21.4 resolution: "axios@npm:0.21.4" @@ -11221,13 +11318,20 @@ __metadata: languageName: node linkType: hard -"bignumber.js@npm:^9.1.0, bignumber.js@npm:^9.1.1": +"bignumber.js@npm:^9.1.1": version: 9.1.1 resolution: "bignumber.js@npm:9.1.1" checksum: ad243b7e2f9120b112d670bb3d674128f0bd2ca1745b0a6c9df0433bd2c0252c43e6315d944c2ac07b4c639e7496b425e46842773cf89c6a2dcd4f31e5c4b11e languageName: node linkType: hard +"bignumber.js@npm:^9.1.2": + version: 9.1.2 + resolution: "bignumber.js@npm:9.1.2" + checksum: 582c03af77ec9cb0ebd682a373ee6c66475db94a4325f92299621d544aa4bd45cb45fd60001610e94aef8ae98a0905fa538241d9638d4422d57abbeeac6fadaf + languageName: node + linkType: hard + "binary-extensions@npm:^2.0.0": version: 2.2.0 resolution: "binary-extensions@npm:2.2.0" @@ -11242,7 +11346,7 @@ __metadata: languageName: node linkType: hard -"bindings@npm:^1.2.1, bindings@npm:^1.3.0, bindings@npm:^1.5.0": +"bindings@npm:^1.3.0, bindings@npm:^1.5.0": version: 1.5.0 resolution: "bindings@npm:1.5.0" dependencies: @@ -12810,7 +12914,7 @@ __metadata: languageName: node linkType: hard -"crypto-js@npm:^4.1.1": +"crypto-js@npm:4.1.1": version: 4.1.1 resolution: "crypto-js@npm:4.1.1" checksum: b3747c12ee3a7632fab3b3e171ea50f78b182545f0714f6d3e7e2858385f0f4101a15f2517e033802ce9d12ba50a391575ff4638c9de3dd9b2c4bc47768d5425 @@ -13667,12 +13771,12 @@ __metadata: languageName: node linkType: hard -"eip55@npm:^2.1.0": - version: 2.1.0 - resolution: "eip55@npm:2.1.0" +"eip55@npm:^2.1.1": + version: 2.1.1 + resolution: "eip55@npm:2.1.1" dependencies: - keccak: ^1.3.0 - checksum: 80999b66f407dc16aa7d06e1f1ddaa45e31db9e528cd8e5fb15364b2ef5062d0e8c817e46ac5d9b4a164e316b8258de7271b97c36b0719e3048600a4cca51b58 + keccak: ^3.0.3 + checksum: 512d319e4f91ab0c33b514f371206956521dcdcdd23e8eb4d6f9c21e3be9f72287c0b82feb854d3a1eec91805804d13c31e7a1a7dafd37f69eb9994a9c6c8f32 languageName: node linkType: hard @@ -14975,7 +15079,7 @@ __metadata: "@floating-ui/react-dom": 2.0.1 "@headlessui/react": 1.7.13 "@hookform/resolvers": 2.9.11 - "@ledgerhq/hw-app-eth": 6.33.3 + "@ledgerhq/hw-app-eth": 6.34.8 "@ledgerhq/hw-transport-webusb": 6.27.14 "@metamask/browser-passworder": 4.1.0 "@metamask/eth-sig-util": 5.1.0 @@ -15135,7 +15239,7 @@ __metadata: url: ^0.11.0 url-join: ^5.0.0 uuid: ^8.3.2 - viem: ^1.16.6 + viem: ^1.18.3 webextension-polyfill: 0.8.0 webpack: ^5.88.1 webpack-bundle-analyzer: ^4.9.0 @@ -15530,6 +15634,16 @@ __metadata: languageName: node linkType: hard +"follow-redirects@npm:^1.14.8": + version: 1.15.3 + resolution: "follow-redirects@npm:1.15.3" + peerDependenciesMeta: + debug: + optional: true + checksum: 584da22ec5420c837bd096559ebfb8fe69d82512d5585004e36a3b4a6ef6d5905780e0c74508c7b72f907d1fa2b7bd339e613859e9c304d0dc96af2027fd0231 + languageName: node + linkType: hard + "for-each@npm:^0.3.3": version: 0.3.3 resolution: "for-each@npm:0.3.3" @@ -17198,7 +17312,7 @@ __metadata: languageName: node linkType: hard -"invariant@npm:2, invariant@npm:^2.2.4": +"invariant@npm:2, invariant@npm:^2.2.2, invariant@npm:^2.2.4": version: 2.2.4 resolution: "invariant@npm:2.2.4" dependencies: @@ -19242,28 +19356,27 @@ __metadata: languageName: node linkType: hard -"keccak@npm:^1.3.0": - version: 1.4.0 - resolution: "keccak@npm:1.4.0" +"keccak@npm:^3.0.0, keccak@npm:^3.0.1, keccak@npm:^3.0.2": + version: 3.0.3 + resolution: "keccak@npm:3.0.3" dependencies: - bindings: ^1.2.1 - inherits: ^2.0.3 - nan: ^2.2.1 + node-addon-api: ^2.0.0 node-gyp: latest - safe-buffer: ^5.1.0 - checksum: 236ba4183d64e1118566c4f123d812cc8fa5fb0fa477b6743bc398aced42595816f46a322bf0240a6a7589eff932aa1540066a30db2367e4049436d9fa30f537 + node-gyp-build: ^4.2.0 + readable-stream: ^3.6.0 + checksum: f08f04f5cc87013a3fc9e87262f761daff38945c86dd09c01a7f7930a15ae3e14f93b310ef821dcc83675a7b814eb1c983222399a2f263ad980251201d1b9a99 languageName: node linkType: hard -"keccak@npm:^3.0.0, keccak@npm:^3.0.1, keccak@npm:^3.0.2": - version: 3.0.3 - resolution: "keccak@npm:3.0.3" +"keccak@npm:^3.0.3": + version: 3.0.4 + resolution: "keccak@npm:3.0.4" dependencies: node-addon-api: ^2.0.0 node-gyp: latest node-gyp-build: ^4.2.0 readable-stream: ^3.6.0 - checksum: f08f04f5cc87013a3fc9e87262f761daff38945c86dd09c01a7f7930a15ae3e14f93b310ef821dcc83675a7b814eb1c983222399a2f263ad980251201d1b9a99 + checksum: 2bf27b97b2f24225b1b44027de62be547f5c7326d87d249605665abd0c8c599d774671c35504c62c9b922cae02758504c6f76a73a84234d23af8a2211afaaa11 languageName: node linkType: hard @@ -19790,6 +19903,13 @@ __metadata: languageName: node linkType: hard +"lru-cache@npm:^7.14.1": + version: 7.18.3 + resolution: "lru-cache@npm:7.18.3" + checksum: e550d772384709deea3f141af34b6d4fa392e2e418c1498c078de0ee63670f1f46f5eee746e8ef7e69e1c895af0d4224e62ee33e66a543a14763b0f2e74c1356 + languageName: node + linkType: hard + "lru-cache@npm:^7.7.1": version: 7.12.0 resolution: "lru-cache@npm:7.12.0" @@ -20502,15 +20622,6 @@ __metadata: languageName: node linkType: hard -"nan@npm:^2.2.1": - version: 2.17.0 - resolution: "nan@npm:2.17.0" - dependencies: - node-gyp: latest - checksum: ec609aeaf7e68b76592a3ba96b372aa7f5df5b056c1e37410b0f1deefbab5a57a922061e2c5b369bae9c7c6b5e6eecf4ad2dac8833a1a7d3a751e0a7c7f849ed - languageName: node - linkType: hard - "nano-css@npm:^5.3.1": version: 5.3.5 resolution: "nano-css@npm:5.3.5" @@ -23410,7 +23521,7 @@ __metadata: languageName: node linkType: hard -"rxjs@npm:6, rxjs@npm:^6.6.3": +"rxjs@npm:6, rxjs@npm:^6.6.3, rxjs@npm:^6.6.7": version: 6.6.7 resolution: "rxjs@npm:6.6.7" dependencies: @@ -25930,6 +26041,13 @@ __metadata: languageName: node linkType: hard +"undici-types@npm:~5.26.4": + version: 5.26.5 + resolution: "undici-types@npm:5.26.5" + checksum: 3192ef6f3fd5df652f2dc1cd782b49d6ff14dc98e5dced492aa8a8c65425227da5da6aafe22523c67f035a272c599bb89cfe803c1db6311e44bed3042fc25487 + languageName: node + linkType: hard + "undici@npm:^5.14.0": version: 5.22.0 resolution: "undici@npm:5.22.0" @@ -26194,6 +26312,13 @@ __metadata: languageName: node linkType: hard +"utility-types@npm:^3.10.0": + version: 3.10.0 + resolution: "utility-types@npm:3.10.0" + checksum: 8f274415c6196ab62883b8bd98c9d2f8829b58016e4269aaa1ebd84184ac5dda7dc2ca45800c0d5e0e0650966ba063bf9a412aaeaea6850ca4440a391283d5c8 + languageName: node + linkType: hard + "uuid@npm:^3.3.2": version: 3.4.0 resolution: "uuid@npm:3.4.0" @@ -26314,9 +26439,9 @@ __metadata: languageName: node linkType: hard -"viem@npm:^1.16.6": - version: 1.16.6 - resolution: "viem@npm:1.16.6" +"viem@npm:^1.18.3": + version: 1.18.3 + resolution: "viem@npm:1.18.3" dependencies: "@adraffy/ens-normalize": 1.9.4 "@noble/curves": 1.2.0 @@ -26331,7 +26456,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 2f116cad184cfc7a9584073451549edfb23c3847b1784f092b80a279b848fe011a054bc4141c923b5bcce1d8493db98284db65416ce72e8ba522225d02786a9a + checksum: 263eb99ee46c586a743a37e15e3079546c5ee681d541b5d42db41eeae27638dbeabc6542ac620bdb5e3eaecae1ec3f2ad04cf4693f3f3105bed64a495ff925bd languageName: node linkType: hard From 5600c87f7c0220bde29ba153c9869f9bee85c732 Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Thu, 2 Nov 2023 17:46:46 +0900 Subject: [PATCH 26/50] chore: remove viem batch temporary fix --- .../src/getTransportForEvmNetwork.ts | 5 +- .../chain-connector-evm/src/tmp/README.md | 4 - .../src/tmp/createTalismanBatchScheduler.ts | 104 ---------------- .../src/tmp/talismanHttp.ts | 115 ------------------ 4 files changed, 2 insertions(+), 226 deletions(-) delete mode 100644 packages/chain-connector-evm/src/tmp/README.md delete mode 100644 packages/chain-connector-evm/src/tmp/createTalismanBatchScheduler.ts delete mode 100644 packages/chain-connector-evm/src/tmp/talismanHttp.ts diff --git a/packages/chain-connector-evm/src/getTransportForEvmNetwork.ts b/packages/chain-connector-evm/src/getTransportForEvmNetwork.ts index d686b7446f..475f42f1ce 100644 --- a/packages/chain-connector-evm/src/getTransportForEvmNetwork.ts +++ b/packages/chain-connector-evm/src/getTransportForEvmNetwork.ts @@ -1,7 +1,6 @@ import { EvmNetwork } from "@talismn/chaindata-provider" -import { custom, fallback } from "viem" +import { custom, fallback, http } from "viem" -import { talismanHttp } from "./tmp/talismanHttp" import { AcalaRpcProvider, addOnfinalityApiKey, isAcalaNetwork } from "./util" const HTTP_BATCH_WAIT = 25 @@ -35,7 +34,7 @@ export const getTransportForEvmNetwork = ( return fallback( evmNetwork.rpcs.map((rpc) => - talismanHttp(addOnfinalityApiKey(rpc.url, options.onFinalityApiKey), { + http(addOnfinalityApiKey(rpc.url, options.onFinalityApiKey), { batch: { wait: HTTP_BATCH_WAIT, batchSize: HTTP_BATCH_SIZE }, retryCount: 0, }) diff --git a/packages/chain-connector-evm/src/tmp/README.md b/packages/chain-connector-evm/src/tmp/README.md deleted file mode 100644 index 3e04a0daaa..0000000000 --- a/packages/chain-connector-evm/src/tmp/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# talismanHttp - -Temporary fix to viem's batch scheduler which doesn't sort items in batch responses, causing a random bugs in the tx signing form. -Once fixed on viem side, delete this folder and use `http()` instead of `talismanHttp()` in `getTransportFormEvmNetwork.ts` diff --git a/packages/chain-connector-evm/src/tmp/createTalismanBatchScheduler.ts b/packages/chain-connector-evm/src/tmp/createTalismanBatchScheduler.ts deleted file mode 100644 index 4a43fde5d8..0000000000 --- a/packages/chain-connector-evm/src/tmp/createTalismanBatchScheduler.ts +++ /dev/null @@ -1,104 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ - -import { ErrorType } from "viem/_types/errors/utils" - -type Resolved = [ - result: TReturnType[number], - results: TReturnType -] - -type PendingPromise = { - resolve?: (data: Resolved) => void - reject?: (reason?: unknown) => void -} - -type SchedulerItem = { args: unknown; pendingPromise: PendingPromise } - -type SortBatchResults = (a: TResult, b: TResult) => number - -export type CreateBatchSchedulerArguments< - TParameters = unknown, - TReturnType extends readonly unknown[] = readonly unknown[] -> = { - fn: (args: TParameters[]) => Promise - id: number | string - shouldSplitBatch?: (args: TParameters[]) => boolean - wait?: number - sort?: SortBatchResults -} - -export type CreateBatchSchedulerReturnType< - TParameters = unknown, - TReturnType extends readonly unknown[] = readonly unknown[] -> = { - flush: () => void - schedule: TParameters extends undefined - ? (args?: TParameters) => Promise> - : (args: TParameters) => Promise> -} - -export type CreateBatchSchedulerErrorType = ErrorType - -const schedulerCache = /*#__PURE__*/ new Map() - -export function createTalismanBatchScheduler({ - fn, - id, - shouldSplitBatch, - wait = 0, - sort, -}: CreateBatchSchedulerArguments): CreateBatchSchedulerReturnType< - TParameters, - TReturnType -> { - const exec = async () => { - const scheduler = getScheduler() - flush() - - const args = scheduler.map(({ args }) => args) - - if (args.length === 0) return - - fn(args as TParameters[]) - .then((data) => { - if (sort && Array.isArray(data)) data.sort(sort) - scheduler.forEach(({ pendingPromise }, i) => pendingPromise.resolve?.([data[i], data])) - }) - .catch((err) => { - scheduler.forEach(({ pendingPromise }) => pendingPromise.reject?.(err)) - }) - } - - const flush = () => schedulerCache.delete(id) - - const getBatchedArgs = () => getScheduler().map(({ args }) => args) as TParameters[] - - const getScheduler = () => schedulerCache.get(id) || [] - - const setScheduler = (item: SchedulerItem) => schedulerCache.set(id, [...getScheduler(), item]) - - return { - flush, - async schedule(args: TParameters) { - const pendingPromise: PendingPromise = {} - const promise = new Promise>((resolve, reject) => { - pendingPromise.resolve = resolve - pendingPromise.reject = reject - }) - - const split = shouldSplitBatch?.([...getBatchedArgs(), args]) - - if (split) exec() - - const hasActiveScheduler = getScheduler().length > 0 - if (hasActiveScheduler) { - setScheduler({ args, pendingPromise }) - return promise - } - - setScheduler({ args, pendingPromise }) - setTimeout(exec, wait) - return promise - }, - } as unknown as CreateBatchSchedulerReturnType -} diff --git a/packages/chain-connector-evm/src/tmp/talismanHttp.ts b/packages/chain-connector-evm/src/tmp/talismanHttp.ts deleted file mode 100644 index 26fa7fc985..0000000000 --- a/packages/chain-connector-evm/src/tmp/talismanHttp.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { - CreateTransportErrorType, - RpcRequestError, - Transport, - TransportConfig, - UrlRequiredError, - UrlRequiredErrorType, - createTransport, -} from "viem" -import { ErrorType } from "viem/_types/errors/utils" -import { HttpOptions, RpcRequest, rpc } from "viem/utils" - -import { createTalismanBatchScheduler } from "./createTalismanBatchScheduler" - -export type BatchOptions = { - /** The maximum number of JSON-RPC requests to send in a batch. @default 1_000 */ - batchSize?: number - /** The maximum number of milliseconds to wait before sending a batch. @default 0 */ - wait?: number -} - -export type HttpTransportConfig = { - /** - * Whether to enable Batch JSON-RPC. - * @link https://www.jsonrpc.org/specification#batch - */ - batch?: boolean | BatchOptions - /** - * Request configuration to pass to `fetch`. - * @link https://developer.mozilla.org/en-US/docs/Web/API/fetch - */ - fetchOptions?: HttpOptions["fetchOptions"] - /** The key of the HTTP transport. */ - key?: TransportConfig["key"] - /** The name of the HTTP transport. */ - name?: TransportConfig["name"] - /** The max number of times to retry. */ - retryCount?: TransportConfig["retryCount"] - /** The base delay (in ms) between retries. */ - retryDelay?: TransportConfig["retryDelay"] - /** The timeout (in ms) for the HTTP request. Default: 10_000 */ - timeout?: TransportConfig["timeout"] -} - -export type HttpTransport = Transport< - "http", - { - fetchOptions?: HttpTransportConfig["fetchOptions"] - url?: string - } -> - -export type HttpTransportErrorType = CreateTransportErrorType | UrlRequiredErrorType | ErrorType - -/** - * @description Creates a HTTP transport that connects to a JSON-RPC API. - */ -export function talismanHttp( - /** URL of the JSON-RPC API. Defaults to the chain's public RPC URL. */ - url?: string, - config: HttpTransportConfig = {} -): HttpTransport { - const { batch, fetchOptions, key = "http", name = "HTTP JSON-RPC", retryDelay } = config - return ({ chain, retryCount: retryCount_, timeout: timeout_ }) => { - const { batchSize = 1000, wait = 0 } = typeof batch === "object" ? batch : {} - const retryCount = config.retryCount ?? retryCount_ - const timeout = timeout_ ?? config.timeout ?? 10_000 - const url_ = url || chain?.rpcUrls.default.http[0] - if (!url_) throw new UrlRequiredError() - return createTransport( - { - key, - name, - async request({ method, params }) { - const body = { method, params } - - const { schedule } = createTalismanBatchScheduler({ - id: `${url}`, - wait, - shouldSplitBatch(requests) { - return requests.length > batchSize - }, - fn: (body: RpcRequest[]) => - rpc.http(url_, { - body, - fetchOptions, - timeout, - }), - sort: (a, b) => a.id - b.id, - }) - - const fn = async (body: RpcRequest) => - batch ? schedule(body) : [await rpc.http(url_, { body, fetchOptions, timeout })] - - const [{ error, result }] = await fn(body) - if (error) - throw new RpcRequestError({ - body, - error, - url: url_, - }) - return result - }, - retryCount, - retryDelay, - timeout, - type: "http", - }, - { - fetchOptions, - url, - } - ) - } -} From 58d157bf3e76501744826f63c2c42a0cf2d51caa Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Thu, 2 Nov 2023 19:36:29 +0900 Subject: [PATCH 27/50] wip: replace bignumber usage --- .../src/@talisman/util/formatEthValue.ts | 8 +- .../src/core/domains/ethereum/errors.ts | 126 +++++----------- .../core/domains/ethereum/viemMigration.ts | 134 ------------------ .../src/core/util/getErc20ContractData.ts | 13 -- .../src/core/util/isContractAddress.ts | 15 -- .../AccountAddSecretMnemonicForm.tsx | 9 +- .../CopyAddress/useCopyAddressWizard.ts | 4 +- .../CustomGasSettingsFormEip1559.tsx | 39 ++--- .../CustomGasSettingsFormLegacy.tsx | 23 +-- .../ui/domains/Ethereum/useEthTransaction.ts | 4 +- 10 files changed, 69 insertions(+), 306 deletions(-) delete mode 100644 apps/extension/src/core/domains/ethereum/viemMigration.ts diff --git a/apps/extension/src/@talisman/util/formatEthValue.ts b/apps/extension/src/@talisman/util/formatEthValue.ts index 158d1f94a0..94e4daeb24 100644 --- a/apps/extension/src/@talisman/util/formatEthValue.ts +++ b/apps/extension/src/@talisman/util/formatEthValue.ts @@ -1,9 +1,7 @@ -import { formatDecimals } from "@talismn/util" -import { BigNumber, BigNumberish } from "ethers" -import { formatUnits } from "ethers/lib/utils" +import { formatDecimals, planckToTokens } from "@talismn/util" -export const formatEtherValue = (value: BigNumberish, decimals: number, symbol?: string) => { - return `${formatDecimals(formatUnits(BigNumber.from(value), decimals))}${ +export const formatEthValue = (value: bigint, decimals: number, symbol?: string) => { + return `${formatDecimals(planckToTokens(value.toString(), decimals))}${ symbol ? ` ${symbol}` : "" }` } diff --git a/apps/extension/src/core/domains/ethereum/errors.ts b/apps/extension/src/core/domains/ethereum/errors.ts index b21ba1adc5..36c2e8b3fd 100644 --- a/apps/extension/src/core/domains/ethereum/errors.ts +++ b/apps/extension/src/core/domains/ethereum/errors.ts @@ -1,115 +1,61 @@ -import { ethers } from "ethers" - -export const getEthersErrorLabelFromCode = (code?: string | number) => { - if (typeof code === "string") { - switch (code) { - // operational errors - case ethers.errors.BUFFER_OVERRUN: - return "Buffer overrun" - case ethers.errors.NUMERIC_FAULT: - return "Numeric fault" - - // argument errors - case ethers.errors.UNEXPECTED_ARGUMENT: - return "Too many arguments" - case ethers.errors.MISSING_ARGUMENT: - return "Missing argument" - case ethers.errors.INVALID_ARGUMENT: - return "Invalid argument" - case ethers.errors.MISSING_NEW: - return "Missing constructor" - - // interactions errors - case ethers.errors.ACTION_REJECTED: - return "Action rejected" - - // blockchain errors - case ethers.errors.CALL_EXCEPTION: - return "Contract method failed to execute" - case ethers.errors.INSUFFICIENT_FUNDS: - return "Insufficient balance" - case ethers.errors.NONCE_EXPIRED: - return "Nonce expired" - case ethers.errors.UNPREDICTABLE_GAS_LIMIT: - // TODO could be gas limit to low, parameters making the operation impossible, balance to low to pay for gas, or anything else that makes the operation impossible to succeed. - // TODO need a better copy to explain this, but gas limit issue has to be stated as it can be solved by the user. - return "Transaction may fail because of insufficient balance, incorrect parameters or may require higher gas limit" - case ethers.errors.TRANSACTION_REPLACED: - return "Transaction was replaced with another one with higher gas price" - case ethers.errors.REPLACEMENT_UNDERPRICED: - return "Replacement fee is too low, try again with higher gas price" - - // generic errors - case ethers.errors.NETWORK_ERROR: - return "Network error" - case ethers.errors.UNSUPPORTED_OPERATION: - return "Unsupported operation" - case ethers.errors.NOT_IMPLEMENTED: - return "Not implemented" - case ethers.errors.TIMEOUT: - return "Timeout exceeded" - case ethers.errors.SERVER_ERROR: - return "Server error" - case ethers.errors.UNKNOWN_ERROR: - default: - return "Unknown error" - } - } +import { log } from "@core/log" +export const getEthersErrorLabelFromCode = (code: number) => { // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1474.md - if (typeof code === "number") { - switch (code) { - case -32700: - return "Parse error. Invalid JSON was received by the server" - case -32600: - return "Invalid request, it could not be understood by the server" - case -32601: - return "Method does not exist" - case -32602: - return "Invalid method parameters" - case -32603: - return "Internal JSON-RPC error" - case -32000: - return "Missing or invalid parameters" - case -32001: - return "Requested resource not found" - case -32002: - return "Requested resource is not available" - case -32003: - return "Transaction rejected" - case -32004: - return "Method is not implemented" - case -32005: - return "Request exceeds defined limit" - case -32006: - return "Transaction not yet known" - default: - return "Unknown error" + switch (code) { + case -32700: + return "Parse error. Invalid JSON was received by the server" + case -32600: + return "Invalid request, it could not be understood by the server" + case -32601: + return "Method does not exist" + case -32602: + return "Invalid method parameters" + case -32603: + return "Internal JSON-RPC error" + case -32000: + return "Missing or invalid parameters" + case -32001: + return "Requested resource not found" + case -32002: + return "Requested resource is not available" + case -32003: + return "Transaction rejected" + case -32004: + return "Method is not implemented" + case -32005: + return "Request exceeds defined limit" + case -32006: + return "Transaction not yet known" + default: { + log.warn("Unknown error code", { code }) + return "Unknown error" } } - - return undefined } // turns errors into short and human readable message. // main use case is teling the user why a transaction failed without going into details and clutter the UI export const getHumanReadableErrorMessage = (error: unknown) => { + if (!error) return undefined + const { - code, reason, error: serverError, shortMessage, details, + code, } = error as { - code?: string reason?: string // eslint-disable-next-line @typescript-eslint/no-explicit-any error?: any shortMessage?: string details?: string + code?: number } if (details) return details + if (shortMessage) return shortMessage if (serverError) { @@ -125,5 +71,5 @@ export const getHumanReadableErrorMessage = (error: unknown) => { if (reason) return reason - return getEthersErrorLabelFromCode(code) + if (code) return getEthersErrorLabelFromCode(code) } diff --git a/apps/extension/src/core/domains/ethereum/viemMigration.ts b/apps/extension/src/core/domains/ethereum/viemMigration.ts deleted file mode 100644 index d5e98b5e0a..0000000000 --- a/apps/extension/src/core/domains/ethereum/viemMigration.ts +++ /dev/null @@ -1,134 +0,0 @@ -// TODO delete this file after ethers => viem migration - -// import { log } from "@core/log" -// import { BigNumber, ethers } from "ethers" -// import { accessListify } from "ethers/lib/utils" -// import { -// AccessList, -// RpcTransactionRequest, -// TransactionRequest, -// hexToBigInt, -// hexToNumber, -// isHex, -// } from "viem" -// // import { RecursiveArray } from "viem/_types/utils/encoding/toRlp" -// // import { parseAccessList } from "viem/_types/utils/transaction/parseTransaction" - -// // import { EthGasSettings } from "./types" - -// // type ViemGasSettings = -// // | { -// // type: "legacy" -// // gas?: bigint -// // gasPrice?: bigint -// // } -// // | { -// // type: "eip1559" -// // gas?: bigint -// // maxFeePerGas?: bigint -// // maxPriorityFeePerGas?: bigint -// // } - -// // export const ethGasSettingsToViemGasSettings = (settings: EthGasSettings): ViemGasSettings => { -// // return settings.type === 2 -// // ? ({ -// // type: "eip1559", -// // gas: settings.gasLimit ? BigNumber.from(settings.gasLimit).toBigInt() : undefined, -// // maxFeePerGas: settings.maxFeePerGas -// // ? BigNumber.from(settings.maxFeePerGas).toBigInt() -// // : undefined, -// // maxPriorityFeePerGas: settings.maxPriorityFeePerGas -// // ? BigNumber.from(settings.maxPriorityFeePerGas).toBigInt() -// // : undefined, -// // } as const) -// // : ({ -// // type: "legacy", -// // gas: settings.gasLimit ? BigNumber.from(settings.gasLimit).toBigInt() : undefined, -// // gasPrice: settings.gasPrice ? BigNumber.from(settings.gasPrice).toBigInt() : undefined, -// // } as const) -// // } - -// export const getViemGasSettings = (tx: ethers.providers.TransactionRequest) => { -// return tx.type === 2 -// ? ({ -// type: "eip1559", -// gas: tx.gasLimit ? BigNumber.from(tx.gasLimit).toBigInt() : undefined, -// maxFeePerGas: tx.maxFeePerGas ? BigNumber.from(tx.maxFeePerGas).toBigInt() : undefined, -// maxPriorityFeePerGas: tx.maxPriorityFeePerGas -// ? BigNumber.from(tx.maxPriorityFeePerGas).toBigInt() -// : undefined, -// accessList: tx.accessList ? (accessListify(tx.accessList) as AccessList) : undefined, -// } as const) -// : ({ -// type: "legacy" as const, -// gas: tx.gasLimit ? BigNumber.from(tx.gasLimit).toBigInt() : undefined, -// gasPrice: tx.gasPrice ? BigNumber.from(tx.gasPrice).toBigInt() : undefined, -// } as const) -// } - -// export const getViemSendTransactionParams = (ethersTx: ethers.providers.TransactionRequest) => { -// const viemTx = { -// to: ethersTx.to ? (ethersTx.to as `0x${string}`) : undefined, -// data: ethersTx.data ? (ethersTx.data as `0x${string}`) : undefined, -// value: ethersTx.value !== undefined ? BigNumber.from(ethersTx.value).toBigInt() : undefined, -// nonce: ethersTx.nonce !== undefined ? BigNumber.from(ethersTx.nonce).toNumber() : undefined, -// ...getViemGasSettings(ethersTx), -// } - -// log.log("getViemSendTransactionParams", { ethersTx, viemTx }) -// return viemTx -// } - -// export const parseTransactionRequest = (rtx: RpcTransactionRequest): TransactionRequest => { -// switch (rtx.type) { -// case "0x0": { -// const tx: TransactionRequest = { -// type: "legacy", -// from: rtx.from, -// } -// if (isHex(rtx.to)) tx.to = rtx.to -// if (isHex(rtx.data)) tx.data = rtx.data -// if (isHex(rtx.value)) tx.value = hexToBigInt(rtx.value) -// if (isHex(rtx.nonce)) tx.nonce = hexToNumber(rtx.nonce) -// if (isHex(rtx.gas)) tx.gas = hexToBigInt(rtx.gas) -// if (isHex(rtx.gasPrice)) tx.gasPrice = hexToBigInt(rtx.gasPrice) -// return tx -// } -// case "0x1": { -// const tx: TransactionRequest = { -// type: "eip2930", -// from: rtx.from, -// } -// if (isHex(rtx.to)) tx.to = rtx.to -// if (isHex(rtx.data)) tx.data = rtx.data -// if (isHex(rtx.value)) tx.value = hexToBigInt(rtx.value) -// if (isHex(rtx.nonce)) tx.nonce = hexToNumber(rtx.nonce) -// if (isHex(rtx.gas)) tx.gas = hexToBigInt(rtx.gas) -// if (isHex(rtx.gasPrice)) tx.gasPrice = hexToBigInt(rtx.gasPrice) -// if (rtx.accessList) tx.accessList = rtx.accessList -// return tx -// } -// case "0x2": { -// const tx: TransactionRequest = { -// type: "eip1559", -// from: rtx.from, -// } -// if (isHex(rtx.to)) tx.to = rtx.to -// if (isHex(rtx.data)) tx.data = rtx.data -// if (isHex(rtx.value)) tx.value = hexToBigInt(rtx.value) -// if (isHex(rtx.nonce)) tx.nonce = hexToNumber(rtx.nonce) -// if (isHex(rtx.gas)) tx.gas = hexToBigInt(rtx.gas) -// if (isHex(rtx.maxFeePerGas)) tx.maxFeePerGas = hexToBigInt(rtx.maxFeePerGas) -// if (isHex(rtx.maxPriorityFeePerGas)) -// tx.maxPriorityFeePerGas = hexToBigInt(rtx.maxPriorityFeePerGas) -// if (rtx.accessList) tx.accessList = rtx.accessList -// return tx -// } -// } - -// if (rtx.gasPrice && rtx.accessList) return parseTransactionRequest({ type: "0x1", ...rtx }) -// if (rtx.gasPrice) return parseTransactionRequest({ type: "0x0", ...rtx } as RpcTransactionRequest) -// return parseTransactionRequest({ type: "0x2", ...rtx } as RpcTransactionRequest) -// } - -export default 0 diff --git a/apps/extension/src/core/util/getErc20ContractData.ts b/apps/extension/src/core/util/getErc20ContractData.ts index 49cefae228..548be2ba1d 100644 --- a/apps/extension/src/core/util/getErc20ContractData.ts +++ b/apps/extension/src/core/util/getErc20ContractData.ts @@ -1,4 +1,3 @@ -import { ethers } from "ethers" import { Client, getContract, parseAbi } from "viem" const ABI_ERC20 = [ @@ -13,18 +12,6 @@ export type Erc20ContractData = { decimals: number } -/** - * @deprecated use viem - */ -export const getErc20ContractDataOld = async ( - provider: ethers.providers.JsonRpcProvider, - contractAddress: string -): Promise => { - const erc20 = new ethers.Contract(contractAddress, ABI_ERC20, provider) - const [symbol, decimals] = await Promise.all([erc20.symbol(), erc20.decimals()]) - return { symbol, decimals } -} - export const getErc20ContractData = async ( client: Client, contractAddress: `0x${string}` diff --git a/apps/extension/src/core/util/isContractAddress.ts b/apps/extension/src/core/util/isContractAddress.ts index 5b49f03e9b..282e56731a 100644 --- a/apps/extension/src/core/util/isContractAddress.ts +++ b/apps/extension/src/core/util/isContractAddress.ts @@ -1,21 +1,6 @@ import { EvmAddress } from "@core/domains/ethereum/types" -import { ethers } from "ethers" import { PublicClient } from "viem" -/** @deprecated */ -export const isContractAddressOld = async ( - provider: ethers.providers.Provider, - address: string -) => { - try { - const code = await provider.getCode(address) - return code !== "0x" - } catch (error) { - // not a contract - return false - } -} - export const isContractAddress = async (client: PublicClient, address: EvmAddress) => { try { const code = await client.getBytecode({ address }) diff --git a/apps/extension/src/ui/domains/Account/AccountAdd/AccountAddSecret/AccountAddSecretMnemonicForm.tsx b/apps/extension/src/ui/domains/Account/AccountAdd/AccountAddSecret/AccountAddSecretMnemonicForm.tsx index 935290cacf..df959107f2 100644 --- a/apps/extension/src/ui/domains/Account/AccountAdd/AccountAddSecret/AccountAddSecretMnemonicForm.tsx +++ b/apps/extension/src/ui/domains/Account/AccountAdd/AccountAddSecret/AccountAddSecretMnemonicForm.tsx @@ -10,7 +10,6 @@ import { api } from "@ui/api" import { AccountIcon } from "@ui/domains/Account/AccountIcon" import { AccountTypeSelector } from "@ui/domains/Account/AccountTypeSelector" import useAccounts from "@ui/hooks/useAccounts" -import { Wallet } from "ethers" import { useCallback, useEffect, useMemo, useState } from "react" import { useForm } from "react-hook-form" import { useTranslation } from "react-i18next" @@ -24,6 +23,8 @@ import { TooltipContent, TooltipTrigger, } from "talisman-ui" +import { isHex } from "viem" +import { privateKeyToAccount } from "viem/accounts" import * as yup from "yup" import { AccountAddDerivationMode, useAccountAddSecret } from "./context" @@ -39,10 +40,10 @@ const cleanupMnemonic = (input = "") => const isValidEthPrivateKey = (privateKey?: string) => { if (!privateKey) return false - try { - new Wallet(privateKey) - return true + const hexPrivateKey = privateKey?.startsWith("0x") ? privateKey : `0x${privateKey}` + if (!isHex(hexPrivateKey)) return false + return !!privateKeyToAccount(hexPrivateKey) } catch (err) { return false } diff --git a/apps/extension/src/ui/domains/CopyAddress/useCopyAddressWizard.ts b/apps/extension/src/ui/domains/CopyAddress/useCopyAddressWizard.ts index c6115d0c5e..f668532677 100644 --- a/apps/extension/src/ui/domains/CopyAddress/useCopyAddressWizard.ts +++ b/apps/extension/src/ui/domains/CopyAddress/useCopyAddressWizard.ts @@ -16,8 +16,8 @@ import useToken from "@ui/hooks/useToken" import useTokens from "@ui/hooks/useTokens" import { copyAddress } from "@ui/util/copyAddress" import { isEvmToken } from "@ui/util/isEvmToken" -import { ethers } from "ethers" import { useCallback, useEffect, useMemo, useState } from "react" +import { getAddress } from "viem" import { CopyAddressWizardInputs } from "./types" import { useCopyAddressModal } from "./useCopyAddressModal" @@ -91,7 +91,7 @@ const getNextRoute = (inputs: CopyAddressWizardInputs): CopyAddressWizardPage => const getFormattedAddress = (address?: Address, chain?: Chain) => { if (address) { try { - if (isEthereumAddress(address)) return ethers.utils.getAddress(address) // enforces format for checksum + if (isEthereumAddress(address)) return getAddress(address) // enforces format for checksum return convertAddress(address, chain?.prefix ?? null) } catch (err) { diff --git a/apps/extension/src/ui/domains/Ethereum/GasSettings/CustomGasSettingsFormEip1559.tsx b/apps/extension/src/ui/domains/Ethereum/GasSettings/CustomGasSettingsFormEip1559.tsx index 1f4789c8cf..4ddea36932 100644 --- a/apps/extension/src/ui/domains/Ethereum/GasSettings/CustomGasSettingsFormEip1559.tsx +++ b/apps/extension/src/ui/domains/Ethereum/GasSettings/CustomGasSettingsFormEip1559.tsx @@ -10,14 +10,13 @@ import { ArrowRightIcon, InfoIcon, LoaderIcon } from "@talismn/icons" import { formatDecimals } from "@talismn/util" import { TokensAndFiat } from "@ui/domains/Asset/TokensAndFiat" import { useAnalytics } from "@ui/hooks/useAnalytics" -import { BigNumber, ethers } from "ethers" import { FC, FormEventHandler, useCallback, useEffect, useMemo, useRef, useState } from "react" import { useForm } from "react-hook-form" import { useTranslation } from "react-i18next" import { useDebounce } from "react-use" import { IconButton } from "talisman-ui" import { Button, FormFieldContainer, FormFieldInputText } from "talisman-ui" -import { TransactionRequest, formatGwei } from "viem" +import { TransactionRequest, formatGwei, parseGwei } from "viem" import * as yup from "yup" import { NetworkUsage } from "../NetworkUsage" @@ -158,22 +157,17 @@ export const CustomGasSettingsFormEip1559: FC () => ({ maxBaseFee: Number( formatDecimals( - ethers.utils.formatUnits( - BigNumber.from(customSettings.maxFeePerGas).sub(customSettings.maxPriorityFeePerGas), - "gwei" - ), + formatGwei(customSettings.maxFeePerGas - customSettings.maxPriorityFeePerGas), undefined, { notation: "standard" } ) ), maxPriorityFee: Number( - formatDecimals( - ethers.utils.formatUnits(customSettings.maxPriorityFeePerGas, "gwei"), - undefined, - { notation: "standard" } - ) + formatDecimals(formatGwei(customSettings.maxPriorityFeePerGas), undefined, { + notation: "standard", + }) ), - gasLimit: BigNumber.from(customSettings.gas).toNumber(), + gasLimit: Number(customSettings.gas), }), [customSettings] ) @@ -208,9 +202,7 @@ export const CustomGasSettingsFormEip1559: FC const totalMaxFee = useMemo(() => { try { - return BigNumber.from(ethers.utils.parseUnits(String(maxBaseFee), "gwei")) - .add(ethers.utils.parseUnits(String(maxPriorityFee), "gwei")) - .mul(gasLimit) + return (parseGwei(String(maxBaseFee)) + parseGwei(String(maxPriorityFee))) * BigInt(gasLimit) } catch (err) { return null } @@ -226,31 +218,28 @@ export const CustomGasSettingsFormEip1559: FC else if ( maxBaseFee && txDetails.baseFeePerGas && - BigNumber.from(ethers.utils.parseUnits(String(maxBaseFee), "gwei")).lt( - getMaxFeePerGas(txDetails.baseFeePerGas, 0n, 20, false) - ) + parseGwei(String(maxBaseFee)) < getMaxFeePerGas(txDetails.baseFeePerGas, 0n, 20, false) ) warningFee = t("Max Base Fee seems too low for current network conditions") // if higher than highest possible fee after 20 blocks else if ( txDetails.baseFeePerGas && maxBaseFee && - BigNumber.from(ethers.utils.parseUnits(String(maxBaseFee), "gwei")).gt( - getMaxFeePerGas(txDetails.baseFeePerGas, 0n, 20) - ) + parseGwei(String(maxBaseFee)) > getMaxFeePerGas(txDetails.baseFeePerGas, 0n, 20) ) warningFee = t("Max Base Fee seems higher than required") else if ( maxPriorityFee && - BigNumber.from(ethers.utils.parseUnits(String(maxPriorityFee), "gwei")).gt( - BigNumber.from(2).mul(highSettings?.maxPriorityFeePerGas) - ) + parseGwei(String(maxPriorityFee)) > 2n * highSettings.maxPriorityFeePerGas + // BigNumber.from(ethers.utils.parseUnits(String(maxPriorityFee), "gwei")).gt( + // BigNumber.from(2).mul(highSettings?.maxPriorityFeePerGas) + // ) ) warningFee = t("Max Priority Fee seems higher than required") if (errors.gasLimit?.type === "min") errorGasLimit = t("Gas Limit minimum value is 21000") else if (errors.gasLimit) errorGasLimit = t("Gas Limit is invalid") - else if (BigNumber.from(txDetails.estimatedGas).gt(gasLimit)) + else if (txDetails.estimatedGas > gasLimit) errorGasLimit = t("Gas Limit too low, transaction likely to fail") return { diff --git a/apps/extension/src/ui/domains/Ethereum/GasSettings/CustomGasSettingsFormLegacy.tsx b/apps/extension/src/ui/domains/Ethereum/GasSettings/CustomGasSettingsFormLegacy.tsx index ecba6e19e6..dc942b216f 100644 --- a/apps/extension/src/ui/domains/Ethereum/GasSettings/CustomGasSettingsFormLegacy.tsx +++ b/apps/extension/src/ui/domains/Ethereum/GasSettings/CustomGasSettingsFormLegacy.tsx @@ -8,14 +8,13 @@ import { ArrowRightIcon, InfoIcon, LoaderIcon } from "@talismn/icons" import { formatDecimals } from "@talismn/util" import { TokensAndFiat } from "@ui/domains/Asset/TokensAndFiat" import { useAnalytics } from "@ui/hooks/useAnalytics" -import { BigNumber, ethers } from "ethers" import { FC, FormEventHandler, useCallback, useEffect, useMemo, useRef, useState } from "react" import { useForm } from "react-hook-form" import { useTranslation } from "react-i18next" import { useDebounce } from "react-use" import { IconButton } from "talisman-ui" import { Button, FormFieldContainer, FormFieldInputText } from "talisman-ui" -import { TransactionRequest, formatGwei } from "viem" +import { TransactionRequest, formatGwei, parseGwei } from "viem" import * as yup from "yup" import { usePublicClient } from "../useEthereumProvider" @@ -141,11 +140,11 @@ export const CustomGasSettingsFormLegacy: FC = const defaultValues: FormData = useMemo( () => ({ gasPrice: Number( - formatDecimals(ethers.utils.formatUnits(customSettings.gasPrice, "gwei"), undefined, { + formatDecimals(formatGwei(customSettings.gasPrice), undefined, { notation: "standard", }) ), - gasLimit: BigNumber.from(customSettings.gas).toNumber(), + gasLimit: Number(customSettings.gas), }), [customSettings.gas, customSettings.gasPrice] ) @@ -176,7 +175,7 @@ export const CustomGasSettingsFormLegacy: FC = const totalMaxFee = useMemo(() => { try { - return BigNumber.from(ethers.utils.parseUnits(String(gasPrice), "gwei")).mul(gasLimit) + return parseGwei(String(gasPrice)) * BigInt(gasLimit) } catch (err) { return null } @@ -187,22 +186,14 @@ export const CustomGasSettingsFormLegacy: FC = let errorGasLimit = "" if (errors.gasPrice) warningFee = t("Gas price is invalid") - else if ( - gasPrice && - BigNumber.from(ethers.utils.parseUnits(String(gasPrice), "gwei")).lt(txDetails.gasPrice) - ) + else if (gasPrice && parseGwei(String(gasPrice)) < txDetails.gasPrice) warningFee = t("Gas price seems too low for current network conditions") - else if ( - gasPrice && - BigNumber.from(ethers.utils.parseUnits(String(gasPrice), "gwei")).gt( - BigNumber.from(txDetails.gasPrice).mul(2) - ) - ) + else if (gasPrice && parseGwei(String(gasPrice)) > txDetails.gasPrice * 2n) warningFee = t("Gas price seems higher than required") if (errors.gasLimit?.type === "min") errorGasLimit = t("Gas Limit minimum value is 21000") else if (errors.gasLimit) errorGasLimit = t("Gas Limit is invalid") - else if (BigNumber.from(txDetails.estimatedGas).gt(gasLimit)) + else if (txDetails.estimatedGas > BigInt(gasLimit)) errorGasLimit = t("Gas Limit too low, transaction likely to fail") return { diff --git a/apps/extension/src/ui/domains/Ethereum/useEthTransaction.ts b/apps/extension/src/ui/domains/Ethereum/useEthTransaction.ts index dc2b91f1a0..5f6c468bf7 100644 --- a/apps/extension/src/ui/domains/Ethereum/useEthTransaction.ts +++ b/apps/extension/src/ui/domains/Ethereum/useEthTransaction.ts @@ -1,4 +1,4 @@ -import { getEthersErrorLabelFromCode } from "@core/domains/ethereum/errors" +import { getHumanReadableErrorMessage } from "@core/domains/ethereum/errors" import { getGasLimit, getGasSettingsEip1559, @@ -481,7 +481,7 @@ export const useEthTransaction = ( errorTransactionInfo ?? isValidError) as Error & { code?: string; error?: Error } - const userFriendlyError = getEthersErrorLabelFromCode(anyError?.code) + const userFriendlyError = getHumanReadableErrorMessage(anyError) // if ethers.js error, display underlying error that shows the RPC's error message const errorToDisplay = anyError?.error ?? anyError From 7767d2b8f037a9aeeac4312a282d860c114392a6 Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Thu, 2 Nov 2023 20:08:56 +0900 Subject: [PATCH 28/50] chore: translate bignumber to bigint --- .../domains/SendFunds/SendFundsFeeTooltip.tsx | 3 +- .../domains/SendFunds/SendFundsProgress.tsx | 3 +- .../src/ui/domains/SendFunds/useSendFunds.ts | 12 ++--- .../ManageNetworks/NetworkForm/helpers.ts | 11 ++--- .../src/ui/domains/Sign/Ethereum/types.ts | 11 ----- .../Sign/ViewDetails/ViewDetailsEth.tsx | 17 ++----- .../PendingTransactionsDrawer.tsx | 6 +-- .../domains/Transactions/TxReplaceDrawer.tsx | 16 ++----- packages/chain-connector-evm/src/util.ts | 44 +++++++++---------- 9 files changed, 39 insertions(+), 84 deletions(-) delete mode 100644 apps/extension/src/ui/domains/Sign/Ethereum/types.ts diff --git a/apps/extension/src/ui/domains/SendFunds/SendFundsFeeTooltip.tsx b/apps/extension/src/ui/domains/SendFunds/SendFundsFeeTooltip.tsx index 7c0d5409a6..a2c430f2d4 100644 --- a/apps/extension/src/ui/domains/SendFunds/SendFundsFeeTooltip.tsx +++ b/apps/extension/src/ui/domains/SendFunds/SendFundsFeeTooltip.tsx @@ -1,6 +1,5 @@ import { WithTooltip } from "@talisman/components/Tooltip" import { InfoIcon } from "@talismn/icons" -import { ethers } from "ethers" import { useTranslation } from "react-i18next" import { TokensAndFiat } from "../Asset/TokensAndFiat" @@ -25,7 +24,7 @@ export const SendFundsFeeTooltip = () => {
{t("Max. fee:")}
diff --git a/apps/extension/src/ui/domains/SendFunds/SendFundsProgress.tsx b/apps/extension/src/ui/domains/SendFunds/SendFundsProgress.tsx index 5bba4a219f..675dd0d72b 100644 --- a/apps/extension/src/ui/domains/SendFunds/SendFundsProgress.tsx +++ b/apps/extension/src/ui/domains/SendFunds/SendFundsProgress.tsx @@ -10,7 +10,6 @@ import { useSendFundsWizard } from "@ui/apps/popup/pages/SendFunds/context" import useChainByGenesisHash from "@ui/hooks/useChainByGenesisHash" import { useEvmNetwork } from "@ui/hooks/useEvmNetwork" import useTransactionByHash from "@ui/hooks/useTransactionByHash" -import { ethers } from "ethers" import { FC, useCallback, useMemo, useState } from "react" import { Trans, useTranslation } from "react-i18next" import { Button, PillButton, ProcessAnimation, ProcessAnimationStatus } from "talisman-ui" @@ -93,7 +92,7 @@ const useStatusDetails = (tx?: WalletTransaction) => { tx.networkType === "evm" && tx.isReplacement && tx.unsigned.value && - ethers.BigNumber.from(tx.unsigned.value).isZero() + BigInt(tx.unsigned.value) === 0n switch (tx.status) { case "unknown": diff --git a/apps/extension/src/ui/domains/SendFunds/useSendFunds.ts b/apps/extension/src/ui/domains/SendFunds/useSendFunds.ts index 7ab51e9c0f..7d9c6b0863 100644 --- a/apps/extension/src/ui/domains/SendFunds/useSendFunds.ts +++ b/apps/extension/src/ui/domains/SendFunds/useSendFunds.ts @@ -30,7 +30,6 @@ import useTokens from "@ui/hooks/useTokens" import { isEvmToken } from "@ui/util/isEvmToken" import { isSubToken } from "@ui/util/isSubToken" import { isTransferableToken } from "@ui/util/isTransferableToken" -import { BigNumber } from "ethers" import { useCallback, useEffect, useMemo, useRef, useState } from "react" import { useTranslation } from "react-i18next" import { useLocation } from "react-router-dom" @@ -256,8 +255,7 @@ const useSendFundsProvider = () => { } case "evm-native": { if (!evmTransaction?.txDetails?.maxFee) return null - const val = - balance.transferable.planck - BigNumber.from(evmTransaction.txDetails.maxFee).toBigInt() + const val = balance.transferable.planck - evmTransaction.txDetails.maxFee return evmTransaction?.txDetails?.maxFee ? new BalanceFormatter(val > 0n ? val : 0n, token.decimals, tokenRates) : null @@ -287,15 +285,11 @@ const useSendFundsProvider = () => { if (evmTransaction?.txDetails?.estimatedFee) { return [ new BalanceFormatter( - BigNumber.from(evmTransaction.txDetails.estimatedFee).toBigInt(), - feeToken?.decimals, - feeTokenRates - ), - new BalanceFormatter( - BigNumber.from(evmTransaction.txDetails.maxFee).toBigInt(), + evmTransaction.txDetails.estimatedFee, feeToken?.decimals, feeTokenRates ), + new BalanceFormatter(evmTransaction.txDetails.maxFee, feeToken?.decimals, feeTokenRates), ] } if (subTransaction?.partialFee) { diff --git a/apps/extension/src/ui/domains/Settings/ManageNetworks/NetworkForm/helpers.ts b/apps/extension/src/ui/domains/Settings/ManageNetworks/NetworkForm/helpers.ts index 41c256b48f..cfe504c4cb 100644 --- a/apps/extension/src/ui/domains/Settings/ManageNetworks/NetworkForm/helpers.ts +++ b/apps/extension/src/ui/domains/Settings/ManageNetworks/NetworkForm/helpers.ts @@ -1,7 +1,7 @@ import { WsProvider } from "@polkadot/api" import { EvmNetworkId } from "@talismn/chaindata-provider" import { sleep } from "@talismn/util" -import { ethers } from "ethers" +import { createClient, hexToNumber, http } from "viem" // because of validation the same query is done 3 times minimum per url, make all await same promise const rpcChainIdCache = new Map>() @@ -13,10 +13,11 @@ export const getEvmRpcChainId = (rpcUrl: string): Promise => { const cached = rpcChainIdCache.get(rpcUrl) if (cached) return cached - const provider = new ethers.providers.StaticJsonRpcProvider(rpcUrl) - const request = provider - .send("eth_chainId", []) - .then((hexChainId) => String(parseInt(hexChainId, 16))) + const client = createClient({ transport: http(rpcUrl) }) + + const request = client + .request({ method: "eth_chainId" }) + .then((hexChainId) => String(hexToNumber(hexChainId))) .catch(() => null) rpcChainIdCache.set(rpcUrl, request) diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/types.ts b/apps/extension/src/ui/domains/Sign/Ethereum/types.ts deleted file mode 100644 index a9a078f2a4..0000000000 --- a/apps/extension/src/ui/domains/Sign/Ethereum/types.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { AccountJsonAny } from "@core/domains/accounts/types" -import { CustomEvmNetwork, EvmNetwork } from "@core/domains/ethereum/types" -import { TransactionInfo } from "@core/util/getEthTransactionInfo" -import { ethers } from "ethers" - -export type EthTxBodyProps = { - network: EvmNetwork | CustomEvmNetwork - account: AccountJsonAny - request: ethers.providers.TransactionRequest - transactionInfo: TransactionInfo -} diff --git a/apps/extension/src/ui/domains/Sign/ViewDetails/ViewDetailsEth.tsx b/apps/extension/src/ui/domains/Sign/ViewDetails/ViewDetailsEth.tsx index ba1c7bf2f4..a037805b89 100644 --- a/apps/extension/src/ui/domains/Sign/ViewDetails/ViewDetailsEth.tsx +++ b/apps/extension/src/ui/domains/Sign/ViewDetails/ViewDetailsEth.tsx @@ -10,7 +10,6 @@ import { NetworkUsage } from "@ui/domains/Ethereum/NetworkUsage" import { useAnalytics } from "@ui/hooks/useAnalytics" import useToken from "@ui/hooks/useToken" import { useTokenRates } from "@ui/hooks/useTokenRates" -import { BigNumber, BigNumberish } from "ethers" import { formatEther } from "ethers/lib/utils" import { FC, PropsWithChildren, ReactNode, useCallback, useEffect, useMemo } from "react" import { useTranslation } from "react-i18next" @@ -71,7 +70,7 @@ const ViewDetailsContent: FC = ({ onClose }) => { const nativeToken = useToken(network?.nativeToken?.id) const formatEthValue = useCallback( - (value?: BigNumberish) => { + (value: bigint = 0n) => { return value ? `${formatEther(value)} ${nativeToken?.symbol ?? ""}` : null }, [nativeToken?.symbol] @@ -87,16 +86,8 @@ const ViewDetailsContent: FC = ({ onClose }) => { () => txDetails && nativeToken ? [ - new BalanceFormatter( - BigNumber.from(txDetails?.estimatedFee).toString(), - nativeToken?.decimals, - nativeTokenRates - ), - new BalanceFormatter( - BigNumber.from(txDetails?.maxFee).toString(), - nativeToken?.decimals, - nativeTokenRates - ), + new BalanceFormatter(txDetails.estimatedFee, nativeToken?.decimals, nativeTokenRates), + new BalanceFormatter(txDetails.maxFee, nativeToken?.decimals, nativeTokenRates), ] : [null, null], [nativeToken, nativeTokenRates, txDetails] @@ -145,7 +136,7 @@ const ViewDetailsContent: FC = ({ onClose }) => { blockExplorerUrl={network?.explorerUrl} /> - {formatEthValue(request.value)} + {formatEthValue(transaction?.value)} diff --git a/apps/extension/src/ui/domains/Transactions/PendingTransactionsDrawer.tsx b/apps/extension/src/ui/domains/Transactions/PendingTransactionsDrawer.tsx index 0950b6d3ce..fab056444d 100644 --- a/apps/extension/src/ui/domains/Transactions/PendingTransactionsDrawer.tsx +++ b/apps/extension/src/ui/domains/Transactions/PendingTransactionsDrawer.tsx @@ -24,7 +24,6 @@ import { useTokenRates } from "@ui/hooks/useTokenRates" import { getTransactionHistoryUrl } from "@ui/util/getTransactionHistoryUrl" import formatDistanceToNowStrict from "date-fns/formatDistanceToNowStrict" import { useLiveQuery } from "dexie-react-hooks" -import { BigNumber } from "ethers" import sortBy from "lodash/sortBy" import { FC, PropsWithChildren, forwardRef, useCallback, useEffect, useMemo, useState } from "react" import { Trans, useTranslation } from "react-i18next" @@ -348,10 +347,7 @@ const TransactionRowEvm: FC = ({ const [isCtxMenuOpen, setIsCtxMenuOpen] = useState(false) const amount = useMemo( - () => - token && value - ? new BalanceFormatter(BigNumber.from(value).toBigInt(), token.decimals, tokenRates) - : null, + () => (token && value ? new BalanceFormatter(value, token.decimals, tokenRates) : null), [token, tokenRates, value] ) diff --git a/apps/extension/src/ui/domains/Transactions/TxReplaceDrawer.tsx b/apps/extension/src/ui/domains/Transactions/TxReplaceDrawer.tsx index b3d57aab77..16b4a0a434 100644 --- a/apps/extension/src/ui/domains/Transactions/TxReplaceDrawer.tsx +++ b/apps/extension/src/ui/domains/Transactions/TxReplaceDrawer.tsx @@ -12,8 +12,6 @@ import { useAccountByAddress } from "@ui/hooks/useAccountByAddress" import { useAnalyticsPageView } from "@ui/hooks/useAnalyticsPageView" import { useBalance } from "@ui/hooks/useBalance" import { useEvmNetwork } from "@ui/hooks/useEvmNetwork" -import { BigNumber } from "ethers" -import { ethers } from "ethers" import { FC, useCallback, useMemo, useState } from "react" import { useTranslation } from "react-i18next" import { Button, Drawer, useOpenCloseWithData } from "talisman-ui" @@ -57,21 +55,13 @@ export const EvmEstimatedFeeTooltip: FC<{
{t("Estimated fee:")}
- +
{!!txDetails?.maxFee && ( <>
{t("Max. fee:")}
- +
)} @@ -267,7 +257,7 @@ const EvmDrawerContent: FC<{
{txDetails?.estimatedFee ? ( ) : null} diff --git a/packages/chain-connector-evm/src/util.ts b/packages/chain-connector-evm/src/util.ts index 132158ed19..8fcc4c1750 100644 --- a/packages/chain-connector-evm/src/util.ts +++ b/packages/chain-connector-evm/src/util.ts @@ -1,10 +1,6 @@ import { AcalaJsonRpcProvider } from "@acala-network/eth-providers" -import { throwAfter } from "@talismn/util" -import { ethers } from "ethers" -import { ACALA_NETWORK_IDS, RPC_HEALTHCHECK_TIMEOUT } from "./constants" -// import { EvmJsonRpcBatchProvider } from "./EvmJsonRpcBatchProvider" -import log from "./log" +import { ACALA_NETWORK_IDS } from "./constants" /** * Helper function to add our onfinality api key to a public onfinality RPC url. @@ -26,27 +22,27 @@ export const addOnfinalityApiKey = (rpcUrl: string, onfinalityApiKey?: string) = // TODO yeet everything below -export const isHealthyRpc = async (url: string, chainId: number) => { - try { - // StaticJsonRpcProvider is better suited for this as it will not do health check requests on it's own - const provider = new ethers.providers.StaticJsonRpcProvider(url, { - chainId, - name: `EVM Network ${chainId}`, - }) +// export const isHealthyRpc = async (url: string, chainId: number) => { +// try { +// // StaticJsonRpcProvider is better suited for this as it will not do health check requests on it's own +// const provider = new ethers.providers.StaticJsonRpcProvider(url, { +// chainId, +// name: `EVM Network ${chainId}`, +// }) - // check that RPC responds in time - const rpcChainId = await Promise.race([ - provider.send("eth_chainId", []), - throwAfter(RPC_HEALTHCHECK_TIMEOUT, "timeout"), - ]) +// // check that RPC responds in time +// const rpcChainId = await Promise.race([ +// provider.send("eth_chainId", []), +// throwAfter(RPC_HEALTHCHECK_TIMEOUT, "timeout"), +// ]) - // with expected chain id - return parseInt(rpcChainId, 16) === chainId - } catch (err) { - log.error("Unhealthy EVM RPC %s", url, { err }) - return false - } -} +// // with expected chain id +// return parseInt(rpcChainId, 16) === chainId +// } catch (err) { +// log.error("Unhealthy EVM RPC %s", url, { err }) +// return false +// } +// } export const isAcalaNetwork = (chainId: number) => ACALA_NETWORK_IDS.includes(chainId) From 3a2a605cc3ab153d57e6bb89d9d6d7a010d59792 Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Fri, 3 Nov 2023 09:03:08 +0900 Subject: [PATCH 29/50] wip: tx decoding --- .../src/core/util/getEthTransactionInfo.ts | 243 ++++++++++++++++-- .../Sign/ViewDetails/ViewDetailsEth.tsx | 3 +- 2 files changed, 225 insertions(+), 21 deletions(-) diff --git a/apps/extension/src/core/util/getEthTransactionInfo.ts b/apps/extension/src/core/util/getEthTransactionInfo.ts index 72f8d9f1a0..a98a6a9899 100644 --- a/apps/extension/src/core/util/getEthTransactionInfo.ts +++ b/apps/extension/src/core/util/getEthTransactionInfo.ts @@ -1,22 +1,101 @@ import { EvmAddress } from "@core/domains/ethereum/types" +import { log } from "@core/log" import * as Sentry from "@sentry/browser" import { getContractCallArg } from "@ui/domains/Sign/Ethereum/getContractCallArg" import { BigNumber, ethers } from "ethers" -import { PublicClient, TransactionRequestBase, getAddress, getContract, parseAbi } from "viem" +import { + PublicClient, + TransactionRequestBase, + decodeFunctionData, + getAbiItem, + getAddress, + getContract, + parseAbi, +} from "viem" import { abiErc1155, abiErc20, abiErc721, abiMoonStaking } from "./abi" import { abiMoonConvictionVoting } from "./abi/abiMoonConvictionVoting" import { abiMoonXTokens } from "./abi/abiMoonXTokens" import { isContractAddress } from "./isContractAddress" -export type ContractType = - | "ERC20" - | "ERC721" - | "ERC1155" - | "MoonXTokens" - | "MoonStaking" - | "MoonConvictionVoting" - | "unknown" +const KNOWN_ABI = { + ERC20: parseAbi(abiErc20), + ERC721: parseAbi(abiErc721), + ERC1155: parseAbi(abiErc1155), + MoonStaking: abiMoonStaking, + MoonConvictionVoting: abiMoonConvictionVoting, + MoonXTokens: abiMoonXTokens, + unknown: null, +} as const + +export type ContractType = keyof typeof KNOWN_ABI +//type KnownAbi = T extends "unknown" ? never : (typeof KNOWN_ABI)[T] + +// export type ContractType = +// | "ERC20" +// | "ERC721" +// | "ERC1155" +// | "MoonXTokens" +// | "MoonStaking" +// | "MoonConvictionVoting" +// | "unknown" + +// type MoonbeamPrecompileDef = Record< +// EvmAddress, +// | { +// contractType: TContractType +// abi: KnownAbi +// } +// | undefined +// > + +// const MOON_CHAIN_PRECOMPILE_ADDRESSES_2 = { +// "0x0000000000000000000000000000000000000800": { +// contractType: "MoonStaking", +// abi: abiMoonStaking, +// }, +// "0x0000000000000000000000000000000000000812": { +// contractType: "MoonConvictionVoting", +// abi: abiMoonConvictionVoting, +// }, +// "0x0000000000000000000000000000000000000804": { +// contractType: "MoonXTokens", +// abi: abiMoonXTokens, +// }, +// } as const + +const MOON_CHAIN_PRECOMPILES = [ + { + address: "0x0000000000000000000000000000000000000800", + contractType: "MoonStaking", + abi: abiMoonStaking, + }, + { + address: "0x0000000000000000000000000000000000000812", + contractType: "MoonConvictionVoting", + abi: abiMoonConvictionVoting, + }, + { + address: "0x0000000000000000000000000000000000000804", + contractType: "MoonXTokens", + abi: abiMoonXTokens, + }, +] as const + +const STANDARD_CONTRACTS = [ + { + contractType: "ERC20", + abi: parseAbi(abiErc20), + }, + { + contractType: "ERC721", + abi: parseAbi(abiErc721), + }, + { + contractType: "ERC1155", + abi: parseAbi(abiErc1155), + }, +] as const const MOON_CHAIN_PRECOMPILE_ADDRESSES: Record< EvmAddress, @@ -36,6 +115,21 @@ const MOON_CHAIN_PRECOMPILE_ADDRESSES: Record< }, } +// type ContractAbis = { +// erc20: ParseAbi +// erc721: ParseAbi +// erc1155: ParseAbi +// MoonXTokens: typeof abiMoonXTokens +// MoonStaking: typeof abiMoonStaking +// abiMoonConvictionVoting: typeof abiMoonConvictionVoting +// } + +// type ContractDef = { +// contractType: TContractType +// abi: ContractAbis[TContractType] +// } + +// TODO yeet // note : order may be important here as some contracts may inherit from others // eslint-disable-next-line @typescript-eslint/no-explicit-any const knownContracts: { contractType: ContractType; abi: any }[] = [ @@ -53,21 +147,13 @@ const knownContracts: { contractType: ContractType; abi: any }[] = [ }, ] -const KNOWN_ABI = { - ERC20: parseAbi(abiErc20), - ERC721: parseAbi(abiErc721), - ERC1155: parseAbi(abiErc1155), - MoonStaking: abiMoonStaking, - MoonConvictionVoting: abiMoonConvictionVoting, - MoonXTokens: abiMoonXTokens, -} - export type TransactionInfo = { targetAddress?: EvmAddress isContractCall: boolean value?: bigint contractType?: ContractType contractCall?: ethers.utils.TransactionDescription + // contractCall2?: DecodeFunctionDataReturnType>> | never asset?: { name: string symbol: string @@ -77,12 +163,130 @@ export type TransactionInfo = { tokenURI?: string } } + +// type UnknownTransactionInfo = { +// contractType: TContractType +// contractCall: TContractType extends "unknown" +// ? null +// : DecodeFunctionDataReturnType> +// targetAddress?: EvmAddress +// isContractCall: boolean +// value?: bigint + +// // contractCall2?: DecodeFunctionDataReturnType>> | never +// asset?: { +// name: string +// symbol: string +// decimals: number +// image?: string +// tokenId?: bigint +// tokenURI?: string +// } +// } + export type KnownTransactionInfo = Required +const getViemTransactionInfo = async (publicClient: PublicClient, tx: TransactionRequestBase) => { + // : Promise | undefined> + // transactions that provision a contract have an empty 'to' field + const { to: targetAddress, value, data } = tx + // const targetAddress = tx.to ? getAddress(tx.to) : undefined + // const value = tx.value ? BigNumber.from(tx.value).toBigInt() : undefined + + const isContractCall = targetAddress + ? await isContractAddress(publicClient, targetAddress) + : false + + if (isContractCall && data && targetAddress) { + // moon chains precompiles + if (publicClient.chain?.id && [1284, 1285, 1287].includes(publicClient.chain.id)) { + for (const { address, contractType, abi } of MOON_CHAIN_PRECOMPILES) { + if (address === targetAddress) { + //const { contractType, abi } = precompile + const contractCall = decodeFunctionData({ abi, data }) + return { contractType, contractCall, targetAddress, isContractCall: true, value } + } + } + } + + // common contracts + for (const { contractType, abi } of STANDARD_CONTRACTS) { + if (contractType === "ERC20") { + const contractCall = decodeFunctionData({ abi, data }) + + const contract = getContract({ + address: targetAddress, + abi: KNOWN_ABI.ERC20, + publicClient, + }) + + const [name, symbol, decimals] = await Promise.all([ + contract.read.name(), + contract.read.symbol(), + contract.read.decimals(), + ]) + + return { + contractType, + contractCall, + targetAddress, + isContractCall: true, + value, + asset: { name, symbol, decimals }, + } + } + if (contractType === "ERC721") { + const contractCall = decodeFunctionData({ abi, data }) + const abiItem = getAbiItem({ + abi, + args: contractCall.args, + name: contractCall.functionName, + }) + const tokenIdIndex = abiItem.inputs.findIndex((input) => input.name === "tokenId") + const tokenId = + tokenIdIndex > -1 ? (contractCall.args?.[tokenIdIndex] as bigint) : undefined + + const contract = getContract({ + address: targetAddress, + abi: KNOWN_ABI.ERC721, + publicClient, + }) + + // some calls may fail as not all NFTs implement the metadata functions + const [name, symbol, tokenURI] = await Promise.allSettled([ + contract.read.name(), + contract.read.symbol(), + tokenId ? contract.read.tokenURI([tokenId]) : undefined, + ]) + + const asset = [name.status, symbol.status, tokenURI].includes("fulfilled") + ? { + name: name.status === "fulfilled" ? name.value : undefined, + symbol: symbol.status === "fulfilled" ? symbol.value : undefined, + tokenId, + tokenURI: tokenURI.status === "fulfilled" ? tokenURI.value : undefined, + decimals: 1, + } + : undefined + + return { contractType, contractCall, targetAddress, isContractCall: true, value, asset } + } + } + } + + return { contractType: "unknown", targetAddress, isContractCall, value } +} + export const getEthTransactionInfo = async ( publicClient: PublicClient, tx: TransactionRequestBase -): Promise => { +): Promise => { + try { + const test = await getViemTransactionInfo(publicClient, tx) + log.log("test", test) + } catch (err) { + log.log("failed test", { err }) + } // transactions that provision a contract have an empty 'to' field const targetAddress = tx.to ? getAddress(tx.to) : undefined @@ -94,6 +298,7 @@ export const getEthTransactionInfo = async ( targetAddress, isContractCall, contractType: isContractCall ? "unknown" : undefined, + //contractType: isContractCall ? "unknown" as const : undefined, value: tx.value ? BigNumber.from(tx.value).toBigInt() : undefined, } diff --git a/apps/extension/src/ui/domains/Sign/ViewDetails/ViewDetailsEth.tsx b/apps/extension/src/ui/domains/Sign/ViewDetails/ViewDetailsEth.tsx index a037805b89..0fd7f0fbde 100644 --- a/apps/extension/src/ui/domains/Sign/ViewDetails/ViewDetailsEth.tsx +++ b/apps/extension/src/ui/domains/Sign/ViewDetails/ViewDetailsEth.tsx @@ -10,11 +10,10 @@ import { NetworkUsage } from "@ui/domains/Ethereum/NetworkUsage" import { useAnalytics } from "@ui/hooks/useAnalytics" import useToken from "@ui/hooks/useToken" import { useTokenRates } from "@ui/hooks/useTokenRates" -import { formatEther } from "ethers/lib/utils" import { FC, PropsWithChildren, ReactNode, useCallback, useEffect, useMemo } from "react" import { useTranslation } from "react-i18next" import { Button, Drawer, PillButton } from "talisman-ui" -import { formatGwei } from "viem" +import { formatEther, formatGwei } from "viem" import { Message } from "../Message" import { useEthSignTransactionRequest } from "../SignRequestContext" From e8640f60895e7a59fd3f974f451e3c053ea2c629 Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Fri, 3 Nov 2023 11:16:12 +0900 Subject: [PATCH 30/50] wip: tx decoding --- .../src/core/util/getEthTransactionInfo.ts | 43 +++++++++++++---- .../src/core/util/getFeeHistoryAnalysis.ts | 2 +- .../popup/pages/Sign/ethereum/Transaction.tsx | 4 +- .../ui/domains/Ethereum/useEthTransaction.ts | 29 +++++++++++- .../ui/domains/Sign/Ethereum/EthSignBody.tsx | 16 +++---- .../Ethereum/EthSignBodyErc20Transfer.tsx | 46 ++++++++++--------- .../Sign/Ethereum/getContractCallArg.ts | 20 ++++++++ .../useEthSignKnownTransactionRequest.ts | 5 +- .../EthereumSignTransactionRequestContext.ts | 2 + 9 files changed, 120 insertions(+), 47 deletions(-) diff --git a/apps/extension/src/core/util/getEthTransactionInfo.ts b/apps/extension/src/core/util/getEthTransactionInfo.ts index a98a6a9899..80c2cbd980 100644 --- a/apps/extension/src/core/util/getEthTransactionInfo.ts +++ b/apps/extension/src/core/util/getEthTransactionInfo.ts @@ -186,12 +186,12 @@ export type TransactionInfo = { export type KnownTransactionInfo = Required -const getViemTransactionInfo = async (publicClient: PublicClient, tx: TransactionRequestBase) => { - // : Promise | undefined> +export const decodeEvmTransaction = async ( + publicClient: PublicClient, + tx: TransactionRequestBase +) => { // transactions that provision a contract have an empty 'to' field const { to: targetAddress, value, data } = tx - // const targetAddress = tx.to ? getAddress(tx.to) : undefined - // const value = tx.value ? BigNumber.from(tx.value).toBigInt() : undefined const isContractCall = targetAddress ? await isContractAddress(publicClient, targetAddress) @@ -204,7 +204,14 @@ const getViemTransactionInfo = async (publicClient: PublicClient, tx: Transactio if (address === targetAddress) { //const { contractType, abi } = precompile const contractCall = decodeFunctionData({ abi, data }) - return { contractType, contractCall, targetAddress, isContractCall: true, value } + return { + contractType: contractType as TContractType, + contractCall, + targetAddress, + isContractCall: true, + value, + abi, + } } } } @@ -227,8 +234,9 @@ const getViemTransactionInfo = async (publicClient: PublicClient, tx: Transactio ]) return { - contractType, + contractType: contractType as TContractType, contractCall, + abi, targetAddress, isContractCall: true, value, @@ -269,20 +277,35 @@ const getViemTransactionInfo = async (publicClient: PublicClient, tx: Transactio } : undefined - return { contractType, contractCall, targetAddress, isContractCall: true, value, asset } + return { + contractType: contractType as TContractType, + contractCall, + abi, + targetAddress, + isContractCall: true, + value, + asset, + } } } } - return { contractType: "unknown", targetAddress, isContractCall, value } + return { contractType: "unknown" as TContractType, targetAddress, isContractCall, value } } -export const getEthTransactionInfo = async ( +// export type DecodedEvmTransaction = Awaited< +// ReturnType> +// > + +export type DecodedEvmTransaction = Awaited> + +/** @deprecated */ +export const getEthTransactionInfoOld = async ( publicClient: PublicClient, tx: TransactionRequestBase ): Promise => { try { - const test = await getViemTransactionInfo(publicClient, tx) + const test = await decodeEvmTransaction(publicClient, tx) log.log("test", test) } catch (err) { log.log("failed test", { err }) diff --git a/apps/extension/src/core/util/getFeeHistoryAnalysis.ts b/apps/extension/src/core/util/getFeeHistoryAnalysis.ts index 1294b0c473..1160a03e95 100644 --- a/apps/extension/src/core/util/getFeeHistoryAnalysis.ts +++ b/apps/extension/src/core/util/getFeeHistoryAnalysis.ts @@ -5,7 +5,7 @@ import { PublicClient, formatGwei, parseGwei } from "viem" const BLOCKS_HISTORY_LENGTH = 5 const REWARD_PERCENTILES = [10, 20, 30] -const LIVE_DEBUG = true +const LIVE_DEBUG = false type EthBasePriorityOptionsEip1559 = Record<"low" | "medium" | "high", bigint> diff --git a/apps/extension/src/ui/apps/popup/pages/Sign/ethereum/Transaction.tsx b/apps/extension/src/ui/apps/popup/pages/Sign/ethereum/Transaction.tsx index e253f312fa..fcffb010b9 100644 --- a/apps/extension/src/ui/apps/popup/pages/Sign/ethereum/Transaction.tsx +++ b/apps/extension/src/ui/apps/popup/pages/Sign/ethereum/Transaction.tsx @@ -102,7 +102,7 @@ export const EthSignTransactionRequest = () => { approveHardware, isPayloadLocked, setIsPayloadLocked, - transactionInfo, + decodedTx, gasSettingsByPriority, setCustomSettings, setReady, @@ -138,7 +138,7 @@ export const EthSignTransactionRequest = () => {
- +
{!isLoading && ( diff --git a/apps/extension/src/ui/domains/Ethereum/useEthTransaction.ts b/apps/extension/src/ui/domains/Ethereum/useEthTransaction.ts index 5f6c468bf7..bff988e25b 100644 --- a/apps/extension/src/ui/domains/Ethereum/useEthTransaction.ts +++ b/apps/extension/src/ui/domains/Ethereum/useEthTransaction.ts @@ -20,7 +20,7 @@ import { GasSettingsByPriority, } from "@core/domains/signing/types" import { ETH_ERROR_EIP1474_METHOD_NOT_FOUND } from "@core/injectEth/EthProviderRpcError" -import { getEthTransactionInfo } from "@core/util/getEthTransactionInfo" +import { decodeEvmTransaction, getEthTransactionInfoOld } from "@core/util/getEthTransactionInfo" import { FeeHistoryAnalysis, getFeeHistoryAnalysis } from "@core/util/getFeeHistoryAnalysis" import { isBigInt } from "@talismn/util" import { useQuery } from "@tanstack/react-query" @@ -170,6 +170,29 @@ const useBlockFeeData = ( } } +const useDecodeEvmTransaction = ( + publicClient: PublicClient | undefined, + tx: TransactionRequest | undefined +) => { + const { data, ...rest } = useQuery({ + // check tx as boolean as it's not pure + queryKey: [ + "useDecodeEvmTransaction", + publicClient?.chain?.id, + tx && serializeTransactionRequest(tx), + ], + queryFn: async () => { + if (!publicClient || !tx) return null + return await decodeEvmTransaction(publicClient, tx) + }, + refetchInterval: false, + refetchOnWindowFocus: false, // prevents error to be cleared when window gets focus + enabled: !!publicClient && !!tx, + }) + + return { decodedTx: data, ...rest } +} + const useTransactionInfo = ( publicClient: PublicClient | undefined, tx: TransactionRequest | undefined @@ -183,7 +206,7 @@ const useTransactionInfo = ( ], queryFn: async () => { if (!publicClient || !tx) return null - return await getEthTransactionInfo(publicClient, tx) + return await getEthTransactionInfoOld(publicClient, tx) }, refetchInterval: false, refetchOnWindowFocus: false, // prevents error to be cleared when window gets focus @@ -372,6 +395,7 @@ export const useEthTransaction = ( ) => { const publicClient = usePublicClient(evmNetworkId) const { transactionInfo, error: errorTransactionInfo } = useTransactionInfo(publicClient, tx) + const { decodedTx } = useDecodeEvmTransaction(publicClient, tx) const { hasEip1559Support, error: errorEip1559Support } = useHasEip1559Support(publicClient) const { nonce, error: nonceError } = useNonce( tx?.from as `0x${string}` | undefined, @@ -501,6 +525,7 @@ export const useEthTransaction = ( ) return { + decodedTx, transactionInfo, transaction, txDetails, diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBody.tsx b/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBody.tsx index 8108a3d183..5d303517e6 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBody.tsx +++ b/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBody.tsx @@ -1,4 +1,4 @@ -import { TransactionInfo } from "@core/util/getEthTransactionInfo" +import { DecodedEvmTransaction } from "@core/util/getEthTransactionInfo" import { ErrorBoundary, FallbackRender } from "@sentry/react" import { FC } from "react" @@ -22,14 +22,14 @@ import { EthSignMoonStakingUnstake } from "./staking/EthSignMoonStakingUnstake" import { EthSignMoonXTokensTransfer } from "./xTokens/EthSignMoonXTokensTransfer" type EthSignBodyProps = { - transactionInfo?: TransactionInfo + decodedTx?: DecodedEvmTransaction | null isReady: boolean } -const getComponentFromKnownContractCall = (transactionInfo: TransactionInfo) => { - const { contractType, contractCall } = transactionInfo +const getComponentFromKnownContractCall = (decodedTx: DecodedEvmTransaction) => { + const { contractType, contractCall } = decodedTx - switch (`${contractType}.${contractCall?.name}`) { + switch (`${contractType}.${contractCall?.functionName}`) { case "ERC20.transfer": case "ERC20.transferFrom": return EthSignBodyErc20Transfer @@ -72,10 +72,10 @@ const getComponentFromKnownContractCall = (transactionInfo: TransactionInfo) => const Fallback: FallbackRender = () => -export const EthSignBody: FC = ({ transactionInfo, isReady }) => { - if (!isReady || !transactionInfo) return +export const EthSignBody: FC = ({ decodedTx, isReady }) => { + if (!isReady || !decodedTx) return - const Component = getComponentFromKnownContractCall(transactionInfo) + const Component = getComponentFromKnownContractCall(decodedTx) if (Component) return ( diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc20Transfer.tsx b/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc20Transfer.tsx index 3beb20e6c8..470e35e993 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc20Transfer.tsx +++ b/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc20Transfer.tsx @@ -5,31 +5,30 @@ import { useSelectedCurrency } from "@ui/hooks/useCurrency" import useToken from "@ui/hooks/useToken" import { useTokenRates } from "@ui/hooks/useTokenRates" import useTokens from "@ui/hooks/useTokens" -import { BigNumber } from "ethers" import { FC, useMemo } from "react" import { useTranslation } from "react-i18next" import { SignContainer } from "../SignContainer" import { SignViewBodyShimmer } from "../Views/SignViewBodyShimmer" -import { getContractCallArg } from "./getContractCallArg" +import { getContractCallArg2 } from "./getContractCallArg" import { SignParamAccountButton } from "./shared" import { SignParamTokensButton } from "./shared/SignParamTokensButton" import { useEthSignKnownTransactionRequest } from "./shared/useEthSignKnownTransactionRequest" export const EthSignBodyErc20Transfer: FC = () => { const { t } = useTranslation("request") - const { account, network, transactionInfo } = useEthSignKnownTransactionRequest() + const { account, network, decodedTx } = useEthSignKnownTransactionRequest() const nativeToken = useToken(network?.nativeToken?.id) const currency = useSelectedCurrency() const { from, value, to } = useMemo(() => { return { - from: getContractCallArg(transactionInfo.contractCall, "from"), - to: getContractCallArg(transactionInfo.contractCall, "to"), - value: getContractCallArg(transactionInfo.contractCall, "amount"), + from: getContractCallArg2(decodedTx, "from"), + to: getContractCallArg2(decodedTx, "to"), + value: getContractCallArg2(decodedTx, "amount"), } - }, [transactionInfo?.contractCall]) + }, [decodedTx]) const isOnBehalf = useMemo( () => account && from && account.address.toLowerCase() !== from.toLowerCase(), @@ -43,41 +42,44 @@ export const EthSignBodyErc20Transfer: FC = () => { (t) => t.type === "evm-erc20" && t.evmNetwork?.id === network.id && - t.contractAddress === transactionInfo.targetAddress + t.contractAddress === decodedTx.targetAddress ) as CustomErc20Token) : undefined - }, [network, tokens, transactionInfo.targetAddress]) + }, [network, tokens, decodedTx.targetAddress]) const tokenRates = useTokenRates(token?.id) const { amount, symbol } = useMemo(() => { - const symbol = token?.symbol ?? (transactionInfo.asset.symbol as string) + const symbol = token?.symbol ?? (decodedTx.asset?.symbol as string) const amount = value - ? new BalanceFormatter(value.toString(), transactionInfo.asset.decimals, tokenRates) + ? new BalanceFormatter(value.toString(), decodedTx.asset?.decimals, tokenRates) : undefined return { amount, symbol } - }, [ - tokenRates, - token?.symbol, - transactionInfo.asset.decimals, - transactionInfo.asset.symbol, - value, - ]) + }, [tokenRates, token?.symbol, decodedTx.asset?.decimals, decodedTx.asset?.symbol, value]) - if (!amount || !nativeToken || !account || !network || !to) return + if ( + !decodedTx.targetAddress || + !decodedTx.asset?.decimals || + !amount || + !nativeToken || + !account || + !network || + !to + ) + return return (
{t("You are transferring")}
( contractCall: ethers.utils.TransactionDescription, @@ -7,3 +9,21 @@ export const getContractCallArg = ( const paramIndex = contractCall.functionFragment.inputs.findIndex((arg) => arg.name === argName) return contractCall.args[paramIndex] as T } + +export const getContractCallArg2 = ( + decodedTx: DecodedEvmTransaction, + argName: string +): TResult | undefined => { + if (decodedTx.contractCall && decodedTx.abi) { + const methodDef = getAbiItem({ + abi: decodedTx.abi, + args: decodedTx.contractCall.args, + name: decodedTx.contractCall.functionName, + }) + + const argIndex = methodDef.inputs.findIndex((input) => input.name === argName) + return decodedTx.contractCall.args?.[argIndex] as TResult + } + + return undefined +} diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/shared/useEthSignKnownTransactionRequest.ts b/apps/extension/src/ui/domains/Sign/Ethereum/shared/useEthSignKnownTransactionRequest.ts index eae6ba07be..ce652b4a0e 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/shared/useEthSignKnownTransactionRequest.ts +++ b/apps/extension/src/ui/domains/Sign/Ethereum/shared/useEthSignKnownTransactionRequest.ts @@ -1,12 +1,13 @@ -import { KnownTransactionInfo } from "@core/util/getEthTransactionInfo" +import { DecodedEvmTransaction, KnownTransactionInfo } from "@core/util/getEthTransactionInfo" import { useEthSignTransactionRequest } from "@ui/domains/Sign/SignRequestContext" // only call this hook from known contracts (ERC20, ERC721...) display components export const useEthSignKnownTransactionRequest = () => { - const { transactionInfo, ...rest } = useEthSignTransactionRequest() + const { transactionInfo, decodedTx, ...rest } = useEthSignTransactionRequest() return { transactionInfo: transactionInfo as KnownTransactionInfo, + decodedTx: decodedTx as DecodedEvmTransaction, ...rest, } } diff --git a/apps/extension/src/ui/domains/Sign/SignRequestContext/EthereumSignTransactionRequestContext.ts b/apps/extension/src/ui/domains/Sign/SignRequestContext/EthereumSignTransactionRequestContext.ts index 4c1e0683e2..8b32aecaa4 100644 --- a/apps/extension/src/ui/domains/Sign/SignRequestContext/EthereumSignTransactionRequestContext.ts +++ b/apps/extension/src/ui/domains/Sign/SignRequestContext/EthereumSignTransactionRequestContext.ts @@ -32,6 +32,7 @@ const useEthSignTransactionRequestProvider = ({ id }: KnownSigningRequestIdOnly< const [isPayloadLocked, setIsPayloadLocked] = useState(false) const { + decodedTx, transaction, transactionInfo, txDetails, @@ -86,6 +87,7 @@ const useEthSignTransactionRequestProvider = ({ id }: KnownSigningRequestIdOnly< errorDetails, network, networkUsage, + decodedTx, transaction, transactionInfo, approve, From a682bc34e8208496d6de03ffcf4026921769bd5c Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Fri, 3 Nov 2023 11:22:33 +0900 Subject: [PATCH 31/50] wip: getContractCallArg --- .../src/core/util/getEthTransactionInfo.ts | 4 +-- .../Sign/Ethereum/EthSignBodyErc20Approve.tsx | 6 ++--- .../Ethereum/EthSignBodyErc20Transfer.tsx | 9 ++++--- .../Ethereum/EthSignBodyErc721Approve.tsx | 8 +++--- .../Ethereum/EthSignBodyErc721ApproveAll.tsx | 6 ++--- .../Ethereum/EthSignBodyErc721Transfer.tsx | 8 +++--- .../EthSignMoonVotingDelegate.tsx | 10 ++++---- .../EthSignMoonVotingUndelegate.tsx | 4 +-- .../EthSignMoonVotingVote.tsx | 8 +++--- .../Sign/Ethereum/getContractCallArg.ts | 25 +++++++++---------- .../EthSignMoonStakingSetAutoCompound.tsx | 4 +-- .../staking/EthSignMoonStakingStake.tsx | 6 ++--- .../staking/EthSignMoonStakingStakeLess.tsx | 4 +-- .../staking/EthSignMoonStakingStakeMore.tsx | 4 +-- .../xTokens/EthSignMoonXTokensTransfer.tsx | 8 +++--- 15 files changed, 57 insertions(+), 57 deletions(-) diff --git a/apps/extension/src/core/util/getEthTransactionInfo.ts b/apps/extension/src/core/util/getEthTransactionInfo.ts index 80c2cbd980..ed727a8772 100644 --- a/apps/extension/src/core/util/getEthTransactionInfo.ts +++ b/apps/extension/src/core/util/getEthTransactionInfo.ts @@ -1,7 +1,7 @@ import { EvmAddress } from "@core/domains/ethereum/types" import { log } from "@core/log" import * as Sentry from "@sentry/browser" -import { getContractCallArg } from "@ui/domains/Sign/Ethereum/getContractCallArg" +import { getContractCallArgOld } from "@ui/domains/Sign/Ethereum/getContractCallArg" import { BigNumber, ethers } from "ethers" import { PublicClient, @@ -382,7 +382,7 @@ export const getEthTransactionInfoOld = async ( decimals, } } else if (contractType === "ERC721") { - const tokenId = getContractCallArg(contractCall, "tokenId")?.toBigInt() + const tokenId = getContractCallArgOld(contractCall, "tokenId")?.toBigInt() if (tokenId) { try { const contract = getContract({ diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc20Approve.tsx b/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc20Approve.tsx index 2a1d0e67bc..d072f1236d 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc20Approve.tsx +++ b/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc20Approve.tsx @@ -10,7 +10,7 @@ import { Trans, useTranslation } from "react-i18next" import { SignAlertMessage } from "../SignAlertMessage" import { SignContainer } from "../SignContainer" import { SignViewBodyShimmer } from "../Views/SignViewBodyShimmer" -import { getContractCallArg } from "./getContractCallArg" +import { getContractCallArgOld } from "./getContractCallArg" import { SignParamAccountButton, SignParamNetworkAddressButton, @@ -48,11 +48,11 @@ export const EthSignBodyErc20Approve: FC = () => { const nativeToken = useToken(network?.nativeToken?.id) const { spender, allowance, isInfinite } = useMemo(() => { - const rawAllowance = getContractCallArg(transactionInfo.contractCall, "amount") + const rawAllowance = getContractCallArgOld(transactionInfo.contractCall, "amount") const isInfinite = rawAllowance?.toHexString() === ALLOWANCE_UNLIMITED return { - spender: getContractCallArg(transactionInfo.contractCall, "spender"), + spender: getContractCallArgOld(transactionInfo.contractCall, "spender"), allowance: rawAllowance && !isInfinite ? new BalanceFormatter( diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc20Transfer.tsx b/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc20Transfer.tsx index 470e35e993..362431de00 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc20Transfer.tsx +++ b/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc20Transfer.tsx @@ -1,4 +1,5 @@ import { BalanceFormatter } from "@core/domains/balances" +import { EvmAddress } from "@core/domains/ethereum/types" import { CustomErc20Token } from "@core/domains/tokens/types" import { TokenLogo } from "@ui/domains/Asset/TokenLogo" import { useSelectedCurrency } from "@ui/hooks/useCurrency" @@ -10,7 +11,7 @@ import { useTranslation } from "react-i18next" import { SignContainer } from "../SignContainer" import { SignViewBodyShimmer } from "../Views/SignViewBodyShimmer" -import { getContractCallArg2 } from "./getContractCallArg" +import { getContractCallArg } from "./getContractCallArg" import { SignParamAccountButton } from "./shared" import { SignParamTokensButton } from "./shared/SignParamTokensButton" import { useEthSignKnownTransactionRequest } from "./shared/useEthSignKnownTransactionRequest" @@ -24,9 +25,9 @@ export const EthSignBodyErc20Transfer: FC = () => { const { from, value, to } = useMemo(() => { return { - from: getContractCallArg2(decodedTx, "from"), - to: getContractCallArg2(decodedTx, "to"), - value: getContractCallArg2(decodedTx, "amount"), + from: getContractCallArg(decodedTx, "from"), + to: getContractCallArg(decodedTx, "to"), + value: getContractCallArg(decodedTx, "amount"), } }, [decodedTx]) diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc721Approve.tsx b/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc721Approve.tsx index 543565e58c..e1d9c83dbe 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc721Approve.tsx +++ b/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc721Approve.tsx @@ -8,7 +8,7 @@ import { UnsafeImage } from "talisman-ui" import { SignContainer } from "../SignContainer" import { SignViewBodyShimmer } from "../Views/SignViewBodyShimmer" -import { getContractCallArg } from "./getContractCallArg" +import { getContractCallArgOld } from "./getContractCallArg" import { SignParamAccountButton, SignParamNetworkAddressButton } from "./shared" import { useEthSignKnownTransactionRequest } from "./shared/useEthSignKnownTransactionRequest" @@ -24,11 +24,11 @@ export const EthSignBodyErc721Approve: FC = () => { }) const { operator, approve, tokenId } = useMemo(() => { - const operator = getContractCallArg(transactionInfo.contractCall, "operator") + const operator = getContractCallArgOld(transactionInfo.contractCall, "operator") return { - operator: getContractCallArg(transactionInfo.contractCall, "operator"), + operator: getContractCallArgOld(transactionInfo.contractCall, "operator"), approve: operator !== ZERO_ADDRESS, - tokenId: BigNumber.from(getContractCallArg(transactionInfo.contractCall, "tokenId")), + tokenId: BigNumber.from(getContractCallArgOld(transactionInfo.contractCall, "tokenId")), } }, [transactionInfo.contractCall]) diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc721ApproveAll.tsx b/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc721ApproveAll.tsx index 714cb9fe2c..116fc2ca6f 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc721ApproveAll.tsx +++ b/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc721ApproveAll.tsx @@ -4,7 +4,7 @@ import { useTranslation } from "react-i18next" import { SignAlertMessage } from "../SignAlertMessage" import { SignContainer } from "../SignContainer" import { SignViewBodyShimmer } from "../Views/SignViewBodyShimmer" -import { getContractCallArg } from "./getContractCallArg" +import { getContractCallArgOld } from "./getContractCallArg" import { SignParamAccountButton, SignParamNetworkAddressButton } from "./shared" import { useEthSignKnownTransactionRequest } from "./shared/useEthSignKnownTransactionRequest" @@ -14,8 +14,8 @@ export const EthSignBodyErc721ApproveAll: FC = () => { const { operator, approve } = useMemo(() => { return { - operator: getContractCallArg(transactionInfo.contractCall, "operator"), - approve: getContractCallArg(transactionInfo.contractCall, "approved"), + operator: getContractCallArgOld(transactionInfo.contractCall, "operator"), + approve: getContractCallArgOld(transactionInfo.contractCall, "approved"), } }, [transactionInfo.contractCall]) diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc721Transfer.tsx b/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc721Transfer.tsx index 3d5494c82f..d9c7bc8378 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc721Transfer.tsx +++ b/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc721Transfer.tsx @@ -7,7 +7,7 @@ import { UnsafeImage } from "talisman-ui" import { SignContainer } from "../SignContainer" import { SignViewBodyShimmer } from "../Views/SignViewBodyShimmer" -import { getContractCallArg } from "./getContractCallArg" +import { getContractCallArgOld } from "./getContractCallArg" import { SignParamAccountButton, SignParamNetworkAddressButton } from "./shared" import { useEthSignKnownTransactionRequest } from "./shared/useEthSignKnownTransactionRequest" @@ -22,10 +22,10 @@ export const EthSignBodyErc721Transfer: FC = () => { const { from, to, tokenId } = useMemo(() => { return { - from: getContractCallArg(transactionInfo.contractCall, "from"), - to: getContractCallArg(transactionInfo.contractCall, "to"), + from: getContractCallArgOld(transactionInfo.contractCall, "from"), + to: getContractCallArgOld(transactionInfo.contractCall, "to"), tokenId: BigNumber.from( - getContractCallArg(transactionInfo.contractCall, "tokenId") + getContractCallArgOld(transactionInfo.contractCall, "tokenId") ), } }, [transactionInfo.contractCall]) diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/convictionVoting/EthSignMoonVotingDelegate.tsx b/apps/extension/src/ui/domains/Sign/Ethereum/convictionVoting/EthSignMoonVotingDelegate.tsx index 302a0e8252..4493b2c301 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/convictionVoting/EthSignMoonVotingDelegate.tsx +++ b/apps/extension/src/ui/domains/Sign/Ethereum/convictionVoting/EthSignMoonVotingDelegate.tsx @@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next" import { SignContainer } from "../../SignContainer" import { SignViewVotingDelegate } from "../../Views/convictionVoting/SignViewVotingDelegate" import { SignViewIconHeader } from "../../Views/SignViewIconHeader" -import { getContractCallArg } from "../getContractCallArg" +import { getContractCallArgOld } from "../getContractCallArg" import { useEthSignKnownTransactionRequest } from "../shared/useEthSignKnownTransactionRequest" export const EthSignMoonVotingDelegate: FC = () => { @@ -13,13 +13,13 @@ export const EthSignMoonVotingDelegate: FC = () => { const { network, transactionInfo } = useEthSignKnownTransactionRequest() const { amount, representative, conviction, trackId } = useMemo(() => { - const representative = getContractCallArg( + const representative = getContractCallArgOld( transactionInfo.contractCall, "representative" ) - const amount = getContractCallArg(transactionInfo.contractCall, "amount") - const conviction = getContractCallArg(transactionInfo.contractCall, "conviction") - const trackId = getContractCallArg(transactionInfo.contractCall, "trackId") + const amount = getContractCallArgOld(transactionInfo.contractCall, "amount") + const conviction = getContractCallArgOld(transactionInfo.contractCall, "conviction") + const trackId = getContractCallArgOld(transactionInfo.contractCall, "trackId") return { representative, diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/convictionVoting/EthSignMoonVotingUndelegate.tsx b/apps/extension/src/ui/domains/Sign/Ethereum/convictionVoting/EthSignMoonVotingUndelegate.tsx index b4c8fa5710..03a00674ac 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/convictionVoting/EthSignMoonVotingUndelegate.tsx +++ b/apps/extension/src/ui/domains/Sign/Ethereum/convictionVoting/EthSignMoonVotingUndelegate.tsx @@ -4,7 +4,7 @@ import { useTranslation } from "react-i18next" import { SignContainer } from "../../SignContainer" import { SignViewVotingUndelegate } from "../../Views/convictionVoting/SignViewVotingUndelegate" import { SignViewIconHeader } from "../../Views/SignViewIconHeader" -import { getContractCallArg } from "../getContractCallArg" +import { getContractCallArgOld } from "../getContractCallArg" import { useEthSignKnownTransactionRequest } from "../shared/useEthSignKnownTransactionRequest" export const EthSignMoonVotingUndelegate: FC = () => { @@ -12,7 +12,7 @@ export const EthSignMoonVotingUndelegate: FC = () => { const { network, transactionInfo } = useEthSignKnownTransactionRequest() const trackId = useMemo( - () => getContractCallArg(transactionInfo.contractCall, "trackId"), + () => getContractCallArgOld(transactionInfo.contractCall, "trackId"), [transactionInfo.contractCall] ) diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/convictionVoting/EthSignMoonVotingVote.tsx b/apps/extension/src/ui/domains/Sign/Ethereum/convictionVoting/EthSignMoonVotingVote.tsx index ad20d00a9f..508b7a18f9 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/convictionVoting/EthSignMoonVotingVote.tsx +++ b/apps/extension/src/ui/domains/Sign/Ethereum/convictionVoting/EthSignMoonVotingVote.tsx @@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next" import { SignContainer } from "../../SignContainer" import { SignViewVotingVote } from "../../Views/convictionVoting/SignViewVotingVote" import { SignViewIconHeader } from "../../Views/SignViewIconHeader" -import { getContractCallArg } from "../getContractCallArg" +import { getContractCallArgOld } from "../getContractCallArg" import { useEthSignKnownTransactionRequest } from "../shared/useEthSignKnownTransactionRequest" export const EthSignMoonVotingVote: FC = () => { @@ -30,9 +30,9 @@ export const EthSignMoonVotingVote: FC = () => { }, [t, transactionInfo.contractCall.name]) const { voteAmount, pollIndex, conviction } = useMemo(() => { - const pollIndex = getContractCallArg(transactionInfo.contractCall, "pollIndex") - const voteAmount = getContractCallArg(transactionInfo.contractCall, "voteAmount") - const conviction = getContractCallArg(transactionInfo.contractCall, "conviction") + const pollIndex = getContractCallArgOld(transactionInfo.contractCall, "pollIndex") + const voteAmount = getContractCallArgOld(transactionInfo.contractCall, "voteAmount") + const conviction = getContractCallArgOld(transactionInfo.contractCall, "conviction") return { pollIndex, diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/getContractCallArg.ts b/apps/extension/src/ui/domains/Sign/Ethereum/getContractCallArg.ts index aa013c7017..01fe135529 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/getContractCallArg.ts +++ b/apps/extension/src/ui/domains/Sign/Ethereum/getContractCallArg.ts @@ -2,7 +2,8 @@ import { DecodedEvmTransaction } from "@core/util/getEthTransactionInfo" import { ethers } from "ethers" import { getAbiItem } from "viem" -export const getContractCallArg = ( +/** @deprecated */ +export const getContractCallArgOld = ( contractCall: ethers.utils.TransactionDescription, argName: string ): T | undefined => { @@ -10,20 +11,18 @@ export const getContractCallArg = ( return contractCall.args[paramIndex] as T } -export const getContractCallArg2 = ( +export const getContractCallArg = ( decodedTx: DecodedEvmTransaction, argName: string -): TResult | undefined => { - if (decodedTx.contractCall && decodedTx.abi) { - const methodDef = getAbiItem({ - abi: decodedTx.abi, - args: decodedTx.contractCall.args, - name: decodedTx.contractCall.functionName, - }) +): TResult => { + if (!decodedTx.contractCall || !decodedTx.abi) throw new Error("Missing contract call or abi") - const argIndex = methodDef.inputs.findIndex((input) => input.name === argName) - return decodedTx.contractCall.args?.[argIndex] as TResult - } + const methodDef = getAbiItem({ + abi: decodedTx.abi, + args: decodedTx.contractCall.args, + name: decodedTx.contractCall.functionName, + }) - return undefined + const argIndex = methodDef.inputs.findIndex((input) => input.name === argName) + return decodedTx.contractCall.args?.[argIndex] as TResult } diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/staking/EthSignMoonStakingSetAutoCompound.tsx b/apps/extension/src/ui/domains/Sign/Ethereum/staking/EthSignMoonStakingSetAutoCompound.tsx index 11e2175cc2..183fab1c64 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/staking/EthSignMoonStakingSetAutoCompound.tsx +++ b/apps/extension/src/ui/domains/Sign/Ethereum/staking/EthSignMoonStakingSetAutoCompound.tsx @@ -4,7 +4,7 @@ import { useTranslation } from "react-i18next" import { SignContainer } from "../../SignContainer" import { SignViewIconHeader } from "../../Views/SignViewIconHeader" import { SignViewStakingSetAutoCompound } from "../../Views/staking/SignViewStakingSetAutoCompound" -import { getContractCallArg } from "../getContractCallArg" +import { getContractCallArgOld } from "../getContractCallArg" import { useEthSignKnownTransactionRequest } from "../shared/useEthSignKnownTransactionRequest" export const EthSignMoonStakingSetAutoCompound: FC = () => { @@ -12,7 +12,7 @@ export const EthSignMoonStakingSetAutoCompound: FC = () => { const { network, transactionInfo } = useEthSignKnownTransactionRequest() const { autoCompound } = useMemo(() => { - const autoCompound = getContractCallArg(transactionInfo.contractCall, "value") + const autoCompound = getContractCallArgOld(transactionInfo.contractCall, "value") return { autoCompound, diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/staking/EthSignMoonStakingStake.tsx b/apps/extension/src/ui/domains/Sign/Ethereum/staking/EthSignMoonStakingStake.tsx index b5c9230247..f6307dc9fd 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/staking/EthSignMoonStakingStake.tsx +++ b/apps/extension/src/ui/domains/Sign/Ethereum/staking/EthSignMoonStakingStake.tsx @@ -6,7 +6,7 @@ import { useTranslation } from "react-i18next" import { SignContainer } from "../../SignContainer" import { SignViewIconHeader } from "../../Views/SignViewIconHeader" import { SignViewStakingStake } from "../../Views/staking/SignViewStakingStake" -import { getContractCallArg } from "../getContractCallArg" +import { getContractCallArgOld } from "../getContractCallArg" import { useEthSignKnownTransactionRequest } from "../shared/useEthSignKnownTransactionRequest" export const EthSignMoonStakingStake: FC = () => { @@ -15,8 +15,8 @@ export const EthSignMoonStakingStake: FC = () => { const token = useToken(network?.nativeToken?.id) const { planck, autoCompound } = useMemo(() => { - const amount = getContractCallArg(transactionInfo.contractCall, "amount") - const autoCompound = getContractCallArg(transactionInfo.contractCall, "autoCompound") + const amount = getContractCallArgOld(transactionInfo.contractCall, "amount") + const autoCompound = getContractCallArgOld(transactionInfo.contractCall, "autoCompound") return { planck: amount?.toBigInt(), diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/staking/EthSignMoonStakingStakeLess.tsx b/apps/extension/src/ui/domains/Sign/Ethereum/staking/EthSignMoonStakingStakeLess.tsx index 3e6eaf1092..03c4f947ca 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/staking/EthSignMoonStakingStakeLess.tsx +++ b/apps/extension/src/ui/domains/Sign/Ethereum/staking/EthSignMoonStakingStakeLess.tsx @@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next" import { SignContainer } from "../../SignContainer" import { SignViewIconHeader } from "../../Views/SignViewIconHeader" import { SignViewStakingStakeLess } from "../../Views/staking/SignViewStakingStakeLess" -import { getContractCallArg } from "../getContractCallArg" +import { getContractCallArgOld } from "../getContractCallArg" import { useEthSignKnownTransactionRequest } from "../shared/useEthSignKnownTransactionRequest" export const EthSignMoonStakingStakeLess: FC = () => { @@ -13,7 +13,7 @@ export const EthSignMoonStakingStakeLess: FC = () => { const { network, transactionInfo } = useEthSignKnownTransactionRequest() const less = useMemo( - () => getContractCallArg(transactionInfo.contractCall, "less")?.toBigInt(), + () => getContractCallArgOld(transactionInfo.contractCall, "less")?.toBigInt(), [transactionInfo.contractCall] ) diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/staking/EthSignMoonStakingStakeMore.tsx b/apps/extension/src/ui/domains/Sign/Ethereum/staking/EthSignMoonStakingStakeMore.tsx index 3754e2395c..b99b8b6bea 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/staking/EthSignMoonStakingStakeMore.tsx +++ b/apps/extension/src/ui/domains/Sign/Ethereum/staking/EthSignMoonStakingStakeMore.tsx @@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next" import { SignContainer } from "../../SignContainer" import { SignViewIconHeader } from "../../Views/SignViewIconHeader" import { SignViewStakingStakeMore } from "../../Views/staking/SignViewStakingStakeMore" -import { getContractCallArg } from "../getContractCallArg" +import { getContractCallArgOld } from "../getContractCallArg" import { useEthSignKnownTransactionRequest } from "../shared/useEthSignKnownTransactionRequest" export const EthSignMoonStakingStakeMore: FC = () => { @@ -13,7 +13,7 @@ export const EthSignMoonStakingStakeMore: FC = () => { const { network, transactionInfo } = useEthSignKnownTransactionRequest() const more = useMemo( - () => getContractCallArg(transactionInfo.contractCall, "more")?.toBigInt(), + () => getContractCallArgOld(transactionInfo.contractCall, "more")?.toBigInt(), [transactionInfo.contractCall] ) diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/xTokens/EthSignMoonXTokensTransfer.tsx b/apps/extension/src/ui/domains/Sign/Ethereum/xTokens/EthSignMoonXTokensTransfer.tsx index 6da342a995..5f5361b2a9 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/xTokens/EthSignMoonXTokensTransfer.tsx +++ b/apps/extension/src/ui/domains/Sign/Ethereum/xTokens/EthSignMoonXTokensTransfer.tsx @@ -15,7 +15,7 @@ import { useTranslation } from "react-i18next" import { SignContainer } from "../../SignContainer" import { SignViewIconHeader } from "../../Views/SignViewIconHeader" import { SignViewXTokensTransfer } from "../../Views/transfer/SignViewCrossChainTransfer" -import { getContractCallArg } from "../getContractCallArg" +import { getContractCallArgOld } from "../getContractCallArg" import { useEthSignKnownTransactionRequest } from "../shared/useEthSignKnownTransactionRequest" type DecodedMultilocation = { @@ -74,12 +74,12 @@ export const EthSignMoonXTokensTransfer: FC = () => { const { tokens } = useTokens(true) const { destination, amount, currencyAddress } = useMemo(() => { - const destination = getContractCallArg<{ parents: number; interior: string[] }>( + const destination = getContractCallArgOld<{ parents: number; interior: string[] }>( transactionInfo.contractCall, "destination" ) - const amount = getContractCallArg(transactionInfo.contractCall, "amount") - const currencyAddress = getContractCallArg( + const amount = getContractCallArgOld(transactionInfo.contractCall, "amount") + const currencyAddress = getContractCallArgOld( transactionInfo.contractCall, "currency_address" ) From 5bf79ea9cb267d740c637041b4b3705c6f1122d3 Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Fri, 3 Nov 2023 15:20:58 +0900 Subject: [PATCH 32/50] chore: getContractCallArgs --- .../src/core/domains/ethereum/errors.ts | 58 +-- .../domains/ethereum/handler.extension.ts | 1 - .../src/core/domains/ethereum/types.ts | 8 + apps/extension/src/core/handlers/index.ts | 30 +- .../src/core/util/decodeEvmTransaction.ts | 172 ++++++++ .../src/core/util/getEthTransactionInfo.ts | 414 ------------------ .../popup/pages/Sign/ethereum/Transaction.tsx | 2 +- .../CustomGasSettingsFormEip1559.tsx | 2 +- .../CustomGasSettingsFormLegacy.tsx | 2 +- .../Ethereum/getExtensionEthereumProvider.ts | 1 - .../ui/domains/Ethereum/useEthTransaction.ts | 49 +-- ...EthereumProvider.ts => usePublicClient.ts} | 12 +- .../ui/domains/Sign/Ethereum/EthSignBody.tsx | 2 +- .../Sign/Ethereum/EthSignBodyDefault.tsx | 22 +- .../Sign/Ethereum/EthSignBodyErc20Approve.tsx | 52 ++- .../Ethereum/EthSignBodyErc721Approve.tsx | 27 +- .../Ethereum/EthSignBodyErc721ApproveAll.tsx | 18 +- .../Ethereum/EthSignBodyErc721Transfer.tsx | 30 +- .../EthSignMoonVotingDelegate.tsx | 30 +- .../EthSignMoonVotingUndelegate.tsx | 9 +- .../EthSignMoonVotingVote.tsx | 28 +- .../Sign/Ethereum/getContractCallArg.ts | 12 +- .../shared/SignParamErc20TokenButton.tsx | 2 +- .../useEthSignKnownTransactionRequest.ts | 5 +- .../EthSignMoonStakingSetAutoCompound.tsx | 12 +- .../staking/EthSignMoonStakingStake.tsx | 21 +- .../staking/EthSignMoonStakingStakeLess.tsx | 10 +- .../staking/EthSignMoonStakingStakeMore.tsx | 10 +- .../xTokens/EthSignMoonXTokensTransfer.tsx | 30 +- .../src/ui/domains/Sign/SignContainer.tsx | 4 +- .../EthereumSignTransactionRequestContext.ts | 2 - .../Sign/ViewDetails/ViewDetailsEth.tsx | 13 +- .../src/ui/hooks/useErc20TokenInfo.ts | 2 +- .../talisman-ui/src/components/Tooltip.tsx | 2 +- 34 files changed, 401 insertions(+), 693 deletions(-) create mode 100644 apps/extension/src/core/util/decodeEvmTransaction.ts delete mode 100644 apps/extension/src/core/util/getEthTransactionInfo.ts delete mode 100644 apps/extension/src/ui/domains/Ethereum/getExtensionEthereumProvider.ts rename apps/extension/src/ui/domains/Ethereum/{useEthereumProvider.ts => usePublicClient.ts} (91%) diff --git a/apps/extension/src/core/domains/ethereum/errors.ts b/apps/extension/src/core/domains/ethereum/errors.ts index 36c2e8b3fd..52e5123aca 100644 --- a/apps/extension/src/core/domains/ethereum/errors.ts +++ b/apps/extension/src/core/domains/ethereum/errors.ts @@ -1,6 +1,8 @@ import { log } from "@core/log" -export const getEthersErrorLabelFromCode = (code: number) => { +import { AnyEvmError } from "./types" + +export const getErrorLabelFromCode = (code: number) => { // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1474.md switch (code) { case -32700: @@ -34,42 +36,46 @@ export const getEthersErrorLabelFromCode = (code: number) => { } } +export const getEvmErrorCause = (error: AnyEvmError): AnyEvmError => { + return error.cause ? getEvmErrorCause(error.cause) : error +} + // turns errors into short and human readable message. // main use case is teling the user why a transaction failed without going into details and clutter the UI export const getHumanReadableErrorMessage = (error: unknown) => { if (!error) return undefined - const { - reason, - error: serverError, - shortMessage, - details, - code, - } = error as { - reason?: string - // eslint-disable-next-line @typescript-eslint/no-explicit-any - error?: any - shortMessage?: string - details?: string - code?: number - } + const { message, shortMessage, details, code } = error as AnyEvmError if (details) return details if (shortMessage) return shortMessage - if (serverError) { - const message = serverError.error?.message ?? serverError.reason ?? serverError.message - return message - .replace("VM Exception while processing transaction: reverted with reason string ", "") - .replace("VM Exception while processing transaction: revert", "") - .replace("VM Exception while processing transaction:", "") - .trim() - } + // if (serverError) { + // const message = serverError.error?.message ?? serverError.reason ?? serverError.message + // return message + // .replace("VM Exception while processing transaction: reverted with reason string ", "") + // .replace("VM Exception while processing transaction: revert", "") + // .replace("VM Exception while processing transaction:", "") + // .trim() + // } - if (reason === "processing response error") return "Invalid transaction" + // if (reason === "processing response error") return "Invalid transaction" - if (reason) return reason + // if (reason) return reason + + if (code) return getErrorLabelFromCode(code) + + if (message) return message + + return undefined +} - if (code) return getEthersErrorLabelFromCode(code) +export const cleanupEvmErrorMessage = (message: string) => { + if (!message) return "Unknown error" + return message + .replace("VM Exception while processing transaction: reverted with reason string ", "") + .replace("VM Exception while processing transaction: revert", "") + .replace("VM Exception while processing transaction:", "") + .trim() } diff --git a/apps/extension/src/core/domains/ethereum/handler.extension.ts b/apps/extension/src/core/domains/ethereum/handler.extension.ts index 688b714dfb..c8a28fcc6b 100644 --- a/apps/extension/src/core/domains/ethereum/handler.extension.ts +++ b/apps/extension/src/core/domains/ethereum/handler.extension.ts @@ -531,7 +531,6 @@ export class EthHandler extends ExtensionHandler { private ethRequest: MessageHandler<"pri(eth.request)"> = async ({ chainId, method, params }) => { const client = await chainConnectorEvm.getPublicClientForEvmNetwork(chainId) assert(client, `No client for chain ${chainId}`) - return client.request({ method: method as never, params: params as never, diff --git a/apps/extension/src/core/domains/ethereum/types.ts b/apps/extension/src/core/domains/ethereum/types.ts index 0d1c84652e..0ce9ef469b 100644 --- a/apps/extension/src/core/domains/ethereum/types.ts +++ b/apps/extension/src/core/domains/ethereum/types.ts @@ -17,6 +17,14 @@ import { WalletTransactionTransferInfo } from "../transactions" export type { EvmAddress, EvmChain } +export type AnyEvmError = { + message?: string + shortMessage?: string + details?: string + code?: number + cause?: AnyEvmError +} + export type { EvmNetwork, CustomEvmNetwork, diff --git a/apps/extension/src/core/handlers/index.ts b/apps/extension/src/core/handlers/index.ts index 40299a235b..c5de9fc6e6 100644 --- a/apps/extension/src/core/handlers/index.ts +++ b/apps/extension/src/core/handlers/index.ts @@ -1,4 +1,5 @@ import { PORT_EXTENSION } from "@core/constants" +import { cleanupEvmErrorMessage, getEvmErrorCause } from "@core/domains/ethereum/errors" import { AnyEthRequest } from "@core/injectEth/types" import { log } from "@core/log" import { assert } from "@polkadot/util" @@ -104,15 +105,26 @@ const talismanHandler = ( // only send message back to port if it's still connected, unfortunately this check is not reliable in all browsers if (port) { try { - if (["pub(eth.request)", "pri(eth.request)"].includes(message)) - port.postMessage({ - id, - error: error.message, - code: error.code, - data: error.data, - isEthProviderRpcError: true, - }) - else port.postMessage({ id, error: error.message }) + switch (message) { + case "pub(eth.request)": + case "pri(eth.request)": { + const evmError = getEvmErrorCause(error) + port.postMessage({ + id, + error: cleanupEvmErrorMessage( + (message === "pri(eth.request)" && evmError.details) || + (evmError.shortMessage ?? evmError.message ?? "Unknown error") + ), + code: error.code, + isEthProviderRpcError: true, + }) + break + } + default: { + port.postMessage({ id, error: error.message }) + break + } + } } catch (caughtError) { /** * no-op diff --git a/apps/extension/src/core/util/decodeEvmTransaction.ts b/apps/extension/src/core/util/decodeEvmTransaction.ts new file mode 100644 index 0000000000..3a65046567 --- /dev/null +++ b/apps/extension/src/core/util/decodeEvmTransaction.ts @@ -0,0 +1,172 @@ +import { + PublicClient, + TransactionRequestBase, + decodeFunctionData, + getAbiItem, + getContract, + parseAbi, +} from "viem" + +import { abiErc1155, abiErc20, abiErc721, abiMoonStaking } from "./abi" +import { abiMoonConvictionVoting } from "./abi/abiMoonConvictionVoting" +import { abiMoonXTokens } from "./abi/abiMoonXTokens" +import { isContractAddress } from "./isContractAddress" + +const KNOWN_ABI = { + ERC20: parseAbi(abiErc20), + ERC721: parseAbi(abiErc721), + ERC1155: parseAbi(abiErc1155), + MoonStaking: abiMoonStaking, + MoonConvictionVoting: abiMoonConvictionVoting, + MoonXTokens: abiMoonXTokens, + unknown: null, +} as const + +export type ContractType = keyof typeof KNOWN_ABI + +const MOON_CHAIN_PRECOMPILES = [ + { + address: "0x0000000000000000000000000000000000000800", + contractType: "MoonStaking", + abi: abiMoonStaking, + }, + { + address: "0x0000000000000000000000000000000000000812", + contractType: "MoonConvictionVoting", + abi: abiMoonConvictionVoting, + }, + { + address: "0x0000000000000000000000000000000000000804", + contractType: "MoonXTokens", + abi: abiMoonXTokens, + }, +] as const + +const STANDARD_CONTRACTS = [ + { + contractType: "ERC20", + abi: parseAbi(abiErc20), + }, + { + contractType: "ERC721", + abi: parseAbi(abiErc721), + }, + { + contractType: "ERC1155", + abi: parseAbi(abiErc1155), + }, +] as const + +export const decodeEvmTransaction = async ( + publicClient: PublicClient, + tx: TransactionRequestBase +) => { + // transactions that provision a contract have an empty 'to' field + const { to: targetAddress, value, data } = tx + + const isContractCall = targetAddress + ? await isContractAddress(publicClient, targetAddress) + : false + + if (isContractCall && data && targetAddress) { + // moon chains precompiles + if (publicClient.chain?.id && [1284, 1285, 1287].includes(publicClient.chain.id)) { + for (const { address, contractType, abi } of MOON_CHAIN_PRECOMPILES) { + if (address === targetAddress) { + //const { contractType, abi } = precompile + const contractCall = decodeFunctionData({ abi, data }) + return { + contractType: contractType as TContractType, + contractCall, + targetAddress, + isContractCall: true, + value, + abi, + } + } + } + } + + // common contracts + for (const { contractType, abi } of STANDARD_CONTRACTS) { + try { + if (contractType === "ERC20") { + const contractCall = decodeFunctionData({ abi, data }) + + const contract = getContract({ + address: targetAddress, + abi: KNOWN_ABI.ERC20, + publicClient, + }) + + const [name, symbol, decimals] = await Promise.all([ + contract.read.name(), + contract.read.symbol(), + contract.read.decimals(), + ]) + + return { + contractType: contractType as TContractType, + contractCall, + abi, + targetAddress, + isContractCall: true, + value, + asset: { name, symbol, decimals }, + } + } + if (contractType === "ERC721") { + const contractCall = decodeFunctionData({ abi, data }) + + const abiItem = getAbiItem({ + abi, + args: contractCall.args, + name: contractCall.functionName, + }) + const tokenIdIndex = abiItem.inputs.findIndex((input) => input.name === "tokenId") + const tokenId = + tokenIdIndex > -1 ? (contractCall.args?.[tokenIdIndex] as bigint) : undefined + + const contract = getContract({ + address: targetAddress, + abi: KNOWN_ABI.ERC721, + publicClient, + }) + + // some calls may fail as not all NFTs implement the metadata functions + const [name, symbol, tokenURI] = await Promise.allSettled([ + contract.read.name(), + contract.read.symbol(), + tokenId ? contract.read.tokenURI([tokenId]) : undefined, + ]) + + const asset = [name.status, symbol.status, tokenURI].includes("fulfilled") + ? { + name: name.status === "fulfilled" ? name.value : undefined, + symbol: symbol.status === "fulfilled" ? symbol.value : undefined, + tokenId, + tokenURI: tokenURI.status === "fulfilled" ? tokenURI.value : undefined, + decimals: 1, + } + : undefined + + return { + contractType: contractType as TContractType, + contractCall, + abi, + targetAddress, + isContractCall: true, + value, + asset, + } + } + } catch { + // ignore + } + } + } + + return { contractType: "unknown" as TContractType, targetAddress, isContractCall, value } +} + +export type DecodedEvmTransaction = Awaited> diff --git a/apps/extension/src/core/util/getEthTransactionInfo.ts b/apps/extension/src/core/util/getEthTransactionInfo.ts deleted file mode 100644 index ed727a8772..0000000000 --- a/apps/extension/src/core/util/getEthTransactionInfo.ts +++ /dev/null @@ -1,414 +0,0 @@ -import { EvmAddress } from "@core/domains/ethereum/types" -import { log } from "@core/log" -import * as Sentry from "@sentry/browser" -import { getContractCallArgOld } from "@ui/domains/Sign/Ethereum/getContractCallArg" -import { BigNumber, ethers } from "ethers" -import { - PublicClient, - TransactionRequestBase, - decodeFunctionData, - getAbiItem, - getAddress, - getContract, - parseAbi, -} from "viem" - -import { abiErc1155, abiErc20, abiErc721, abiMoonStaking } from "./abi" -import { abiMoonConvictionVoting } from "./abi/abiMoonConvictionVoting" -import { abiMoonXTokens } from "./abi/abiMoonXTokens" -import { isContractAddress } from "./isContractAddress" - -const KNOWN_ABI = { - ERC20: parseAbi(abiErc20), - ERC721: parseAbi(abiErc721), - ERC1155: parseAbi(abiErc1155), - MoonStaking: abiMoonStaking, - MoonConvictionVoting: abiMoonConvictionVoting, - MoonXTokens: abiMoonXTokens, - unknown: null, -} as const - -export type ContractType = keyof typeof KNOWN_ABI -//type KnownAbi = T extends "unknown" ? never : (typeof KNOWN_ABI)[T] - -// export type ContractType = -// | "ERC20" -// | "ERC721" -// | "ERC1155" -// | "MoonXTokens" -// | "MoonStaking" -// | "MoonConvictionVoting" -// | "unknown" - -// type MoonbeamPrecompileDef = Record< -// EvmAddress, -// | { -// contractType: TContractType -// abi: KnownAbi -// } -// | undefined -// > - -// const MOON_CHAIN_PRECOMPILE_ADDRESSES_2 = { -// "0x0000000000000000000000000000000000000800": { -// contractType: "MoonStaking", -// abi: abiMoonStaking, -// }, -// "0x0000000000000000000000000000000000000812": { -// contractType: "MoonConvictionVoting", -// abi: abiMoonConvictionVoting, -// }, -// "0x0000000000000000000000000000000000000804": { -// contractType: "MoonXTokens", -// abi: abiMoonXTokens, -// }, -// } as const - -const MOON_CHAIN_PRECOMPILES = [ - { - address: "0x0000000000000000000000000000000000000800", - contractType: "MoonStaking", - abi: abiMoonStaking, - }, - { - address: "0x0000000000000000000000000000000000000812", - contractType: "MoonConvictionVoting", - abi: abiMoonConvictionVoting, - }, - { - address: "0x0000000000000000000000000000000000000804", - contractType: "MoonXTokens", - abi: abiMoonXTokens, - }, -] as const - -const STANDARD_CONTRACTS = [ - { - contractType: "ERC20", - abi: parseAbi(abiErc20), - }, - { - contractType: "ERC721", - abi: parseAbi(abiErc721), - }, - { - contractType: "ERC1155", - abi: parseAbi(abiErc1155), - }, -] as const - -const MOON_CHAIN_PRECOMPILE_ADDRESSES: Record< - EvmAddress, - { contractType: ContractType; abi: unknown } -> = { - "0x0000000000000000000000000000000000000800": { - contractType: "MoonStaking", - abi: abiMoonStaking, - }, - "0x0000000000000000000000000000000000000812": { - contractType: "MoonConvictionVoting", - abi: abiMoonConvictionVoting, - }, - "0x0000000000000000000000000000000000000804": { - contractType: "MoonXTokens", - abi: abiMoonXTokens, - }, -} - -// type ContractAbis = { -// erc20: ParseAbi -// erc721: ParseAbi -// erc1155: ParseAbi -// MoonXTokens: typeof abiMoonXTokens -// MoonStaking: typeof abiMoonStaking -// abiMoonConvictionVoting: typeof abiMoonConvictionVoting -// } - -// type ContractDef = { -// contractType: TContractType -// abi: ContractAbis[TContractType] -// } - -// TODO yeet -// note : order may be important here as some contracts may inherit from others -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const knownContracts: { contractType: ContractType; abi: any }[] = [ - { - contractType: "ERC20", - abi: abiErc20, - }, - { - contractType: "ERC721", - abi: abiErc721, - }, - { - contractType: "ERC1155", - abi: abiErc1155, - }, -] - -export type TransactionInfo = { - targetAddress?: EvmAddress - isContractCall: boolean - value?: bigint - contractType?: ContractType - contractCall?: ethers.utils.TransactionDescription - // contractCall2?: DecodeFunctionDataReturnType>> | never - asset?: { - name: string - symbol: string - decimals: number - image?: string - tokenId?: bigint - tokenURI?: string - } -} - -// type UnknownTransactionInfo = { -// contractType: TContractType -// contractCall: TContractType extends "unknown" -// ? null -// : DecodeFunctionDataReturnType> -// targetAddress?: EvmAddress -// isContractCall: boolean -// value?: bigint - -// // contractCall2?: DecodeFunctionDataReturnType>> | never -// asset?: { -// name: string -// symbol: string -// decimals: number -// image?: string -// tokenId?: bigint -// tokenURI?: string -// } -// } - -export type KnownTransactionInfo = Required - -export const decodeEvmTransaction = async ( - publicClient: PublicClient, - tx: TransactionRequestBase -) => { - // transactions that provision a contract have an empty 'to' field - const { to: targetAddress, value, data } = tx - - const isContractCall = targetAddress - ? await isContractAddress(publicClient, targetAddress) - : false - - if (isContractCall && data && targetAddress) { - // moon chains precompiles - if (publicClient.chain?.id && [1284, 1285, 1287].includes(publicClient.chain.id)) { - for (const { address, contractType, abi } of MOON_CHAIN_PRECOMPILES) { - if (address === targetAddress) { - //const { contractType, abi } = precompile - const contractCall = decodeFunctionData({ abi, data }) - return { - contractType: contractType as TContractType, - contractCall, - targetAddress, - isContractCall: true, - value, - abi, - } - } - } - } - - // common contracts - for (const { contractType, abi } of STANDARD_CONTRACTS) { - if (contractType === "ERC20") { - const contractCall = decodeFunctionData({ abi, data }) - - const contract = getContract({ - address: targetAddress, - abi: KNOWN_ABI.ERC20, - publicClient, - }) - - const [name, symbol, decimals] = await Promise.all([ - contract.read.name(), - contract.read.symbol(), - contract.read.decimals(), - ]) - - return { - contractType: contractType as TContractType, - contractCall, - abi, - targetAddress, - isContractCall: true, - value, - asset: { name, symbol, decimals }, - } - } - if (contractType === "ERC721") { - const contractCall = decodeFunctionData({ abi, data }) - const abiItem = getAbiItem({ - abi, - args: contractCall.args, - name: contractCall.functionName, - }) - const tokenIdIndex = abiItem.inputs.findIndex((input) => input.name === "tokenId") - const tokenId = - tokenIdIndex > -1 ? (contractCall.args?.[tokenIdIndex] as bigint) : undefined - - const contract = getContract({ - address: targetAddress, - abi: KNOWN_ABI.ERC721, - publicClient, - }) - - // some calls may fail as not all NFTs implement the metadata functions - const [name, symbol, tokenURI] = await Promise.allSettled([ - contract.read.name(), - contract.read.symbol(), - tokenId ? contract.read.tokenURI([tokenId]) : undefined, - ]) - - const asset = [name.status, symbol.status, tokenURI].includes("fulfilled") - ? { - name: name.status === "fulfilled" ? name.value : undefined, - symbol: symbol.status === "fulfilled" ? symbol.value : undefined, - tokenId, - tokenURI: tokenURI.status === "fulfilled" ? tokenURI.value : undefined, - decimals: 1, - } - : undefined - - return { - contractType: contractType as TContractType, - contractCall, - abi, - targetAddress, - isContractCall: true, - value, - asset, - } - } - } - } - - return { contractType: "unknown" as TContractType, targetAddress, isContractCall, value } -} - -// export type DecodedEvmTransaction = Awaited< -// ReturnType> -// > - -export type DecodedEvmTransaction = Awaited> - -/** @deprecated */ -export const getEthTransactionInfoOld = async ( - publicClient: PublicClient, - tx: TransactionRequestBase -): Promise => { - try { - const test = await decodeEvmTransaction(publicClient, tx) - log.log("test", test) - } catch (err) { - log.log("failed test", { err }) - } - // transactions that provision a contract have an empty 'to' field - const targetAddress = tx.to ? getAddress(tx.to) : undefined - - const isContractCall = targetAddress - ? await isContractAddress(publicClient, targetAddress) - : false - - const result: TransactionInfo = { - targetAddress, - isContractCall, - contractType: isContractCall ? "unknown" : undefined, - //contractType: isContractCall ? "unknown" as const : undefined, - value: tx.value ? BigNumber.from(tx.value).toBigInt() : undefined, - } - - // moon chains precompiles - if ( - tx.data && - targetAddress && - publicClient?.chain?.id && - [1284, 1285, 1287].includes(publicClient.chain.id) && - !!MOON_CHAIN_PRECOMPILE_ADDRESSES[targetAddress] - ) { - const { contractType, abi } = MOON_CHAIN_PRECOMPILE_ADDRESSES[targetAddress] - try { - const data = ethers.utils.hexlify(tx.data) - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const contractInterface = new ethers.utils.Interface(abi as any) - // error will be thrown here if contract doesn't match the abi - const contractCall = contractInterface.parseTransaction({ data, value: tx.value }) - result.contractType = contractType - result.contractCall = contractCall - - return result - } catch (err) { - Sentry.captureException(err, { extra: { to: tx.to, chainId: publicClient.chain.id } }) - } - } - - if (targetAddress && tx.data) { - const data = ethers.utils.hexlify(tx.data) - - for (const { contractType, abi } of knownContracts) { - try { - const contractInterface = new ethers.utils.Interface(abi) - - // TODO find a way to parse method arguments without ethers - // error will be thrown here if contract doesn't match the abi - const contractCall = contractInterface.parseTransaction({ data, value: tx.value }) - result.contractType = contractType - result.contractCall = contractCall - - if (contractType === "ERC20") { - const contract = getContract({ - address: targetAddress, - abi: KNOWN_ABI.ERC20, - publicClient, - }) - - const [name, symbol, decimals] = await Promise.all([ - contract.read.name(), - contract.read.symbol(), - contract.read.decimals(), - ]) - - result.asset = { - name, - symbol, - decimals, - } - } else if (contractType === "ERC721") { - const tokenId = getContractCallArgOld(contractCall, "tokenId")?.toBigInt() - if (tokenId) { - try { - const contract = getContract({ - address: targetAddress, - abi: KNOWN_ABI.ERC721, - publicClient, - }) - const [name, symbol, tokenURI] = await Promise.all([ - contract.read.name(), - contract.read.symbol(), - tokenId ? contract.read.tokenURI([tokenId]) : undefined, - ]) - - result.asset = { name, symbol, tokenId, tokenURI, decimals: 1 } - } catch (err) { - // some NFTs don't implement the metadata functions - } - } - } - - return result - } catch (err) { - // transaction doesn't match this contract interface - } - } - } - - return result -} diff --git a/apps/extension/src/ui/apps/popup/pages/Sign/ethereum/Transaction.tsx b/apps/extension/src/ui/apps/popup/pages/Sign/ethereum/Transaction.tsx index fcffb010b9..067ca112b3 100644 --- a/apps/extension/src/ui/apps/popup/pages/Sign/ethereum/Transaction.tsx +++ b/apps/extension/src/ui/apps/popup/pages/Sign/ethereum/Transaction.tsx @@ -12,7 +12,7 @@ import { import { TokensAndFiat } from "@ui/domains/Asset/TokensAndFiat" import { EthFeeSelect } from "@ui/domains/Ethereum/GasSettings/EthFeeSelect" import { useEthBalance } from "@ui/domains/Ethereum/useEthBalance" -import { usePublicClient } from "@ui/domains/Ethereum/useEthereumProvider" +import { usePublicClient } from "@ui/domains/Ethereum/usePublicClient" import { EthSignBody } from "@ui/domains/Sign/Ethereum/EthSignBody" import { SignAlertMessage } from "@ui/domains/Sign/SignAlertMessage" import { SignHardwareEthereum } from "@ui/domains/Sign/SignHardwareEthereum" diff --git a/apps/extension/src/ui/domains/Ethereum/GasSettings/CustomGasSettingsFormEip1559.tsx b/apps/extension/src/ui/domains/Ethereum/GasSettings/CustomGasSettingsFormEip1559.tsx index 4ddea36932..9429a546d9 100644 --- a/apps/extension/src/ui/domains/Ethereum/GasSettings/CustomGasSettingsFormEip1559.tsx +++ b/apps/extension/src/ui/domains/Ethereum/GasSettings/CustomGasSettingsFormEip1559.tsx @@ -20,8 +20,8 @@ import { TransactionRequest, formatGwei, parseGwei } from "viem" import * as yup from "yup" import { NetworkUsage } from "../NetworkUsage" -import { usePublicClient } from "../useEthereumProvider" import { useIsValidEthTransaction } from "../useIsValidEthTransaction" +import { usePublicClient } from "../usePublicClient" import { Indicator, MessageRow } from "./common" const INPUT_PROPS = { diff --git a/apps/extension/src/ui/domains/Ethereum/GasSettings/CustomGasSettingsFormLegacy.tsx b/apps/extension/src/ui/domains/Ethereum/GasSettings/CustomGasSettingsFormLegacy.tsx index dc942b216f..aed9f4678c 100644 --- a/apps/extension/src/ui/domains/Ethereum/GasSettings/CustomGasSettingsFormLegacy.tsx +++ b/apps/extension/src/ui/domains/Ethereum/GasSettings/CustomGasSettingsFormLegacy.tsx @@ -17,8 +17,8 @@ import { Button, FormFieldContainer, FormFieldInputText } from "talisman-ui" import { TransactionRequest, formatGwei, parseGwei } from "viem" import * as yup from "yup" -import { usePublicClient } from "../useEthereumProvider" import { useIsValidEthTransaction } from "../useIsValidEthTransaction" +import { usePublicClient } from "../usePublicClient" import { Indicator, MessageRow } from "./common" const INPUT_PROPS = { diff --git a/apps/extension/src/ui/domains/Ethereum/getExtensionEthereumProvider.ts b/apps/extension/src/ui/domains/Ethereum/getExtensionEthereumProvider.ts deleted file mode 100644 index 029f788d6d..0000000000 --- a/apps/extension/src/ui/domains/Ethereum/getExtensionEthereumProvider.ts +++ /dev/null @@ -1 +0,0 @@ -export default 0 diff --git a/apps/extension/src/ui/domains/Ethereum/useEthTransaction.ts b/apps/extension/src/ui/domains/Ethereum/useEthTransaction.ts index bff988e25b..78a81e2669 100644 --- a/apps/extension/src/ui/domains/Ethereum/useEthTransaction.ts +++ b/apps/extension/src/ui/domains/Ethereum/useEthTransaction.ts @@ -20,12 +20,12 @@ import { GasSettingsByPriority, } from "@core/domains/signing/types" import { ETH_ERROR_EIP1474_METHOD_NOT_FOUND } from "@core/injectEth/EthProviderRpcError" -import { decodeEvmTransaction, getEthTransactionInfoOld } from "@core/util/getEthTransactionInfo" +import { decodeEvmTransaction } from "@core/util/decodeEvmTransaction" import { FeeHistoryAnalysis, getFeeHistoryAnalysis } from "@core/util/getFeeHistoryAnalysis" import { isBigInt } from "@talismn/util" import { useQuery } from "@tanstack/react-query" import { api } from "@ui/api" -import { usePublicClient } from "@ui/domains/Ethereum/useEthereumProvider" +import { usePublicClient } from "@ui/domains/Ethereum/usePublicClient" import { useEffect, useMemo, useState } from "react" import { useTranslation } from "react-i18next" import { PublicClient, TransactionRequest } from "viem" @@ -193,29 +193,6 @@ const useDecodeEvmTransaction = ( return { decodedTx: data, ...rest } } -const useTransactionInfo = ( - publicClient: PublicClient | undefined, - tx: TransactionRequest | undefined -) => { - const { data, ...rest } = useQuery({ - // check tx as boolean as it's not pure - queryKey: [ - "useTransactionInfo", - publicClient?.chain?.id, - tx && serializeTransactionRequest(tx), - ], - queryFn: async () => { - if (!publicClient || !tx) return null - return await getEthTransactionInfoOld(publicClient, tx) - }, - refetchInterval: false, - refetchOnWindowFocus: false, // prevents error to be cleared when window gets focus - enabled: !!publicClient && !!tx, - }) - - return { transactionInfo: data ?? undefined, ...rest } -} - const getEthGasSettingsFromTransaction = ( tx: TransactionRequest | undefined, hasEip1559Support: boolean | undefined, @@ -394,8 +371,7 @@ export const useEthTransaction = ( isReplacement = false ) => { const publicClient = usePublicClient(evmNetworkId) - const { transactionInfo, error: errorTransactionInfo } = useTransactionInfo(publicClient, tx) - const { decodedTx } = useDecodeEvmTransaction(publicClient, tx) + const { decodedTx, isLoading: isDecoding } = useDecodeEvmTransaction(publicClient, tx) const { hasEip1559Support, error: errorEip1559Support } = useHasEip1559Support(publicClient) const { nonce, error: nonceError } = useNonce( tx?.from as `0x${string}` | undefined, @@ -436,7 +412,7 @@ export const useEthTransaction = ( blockGasLimit, feeHistoryAnalysis, isReplacement, - isContractCall: transactionInfo?.isContractCall, + isContractCall: decodedTx?.isContractCall, }) const liveUpdatingTransaction = useMemo(() => { @@ -502,31 +478,26 @@ export const useEthTransaction = ( const anyError = (errorEip1559Support ?? nonceError ?? blockFeeDataError ?? - errorTransactionInfo ?? - isValidError) as Error & { code?: string; error?: Error } + isValidError) as Error const userFriendlyError = getHumanReadableErrorMessage(anyError) - // if ethers.js error, display underlying error that shows the RPC's error message - const errorToDisplay = anyError?.error ?? anyError - - if (errorToDisplay) + if (anyError) return { error: userFriendlyError ?? t("Failed to prepare transaction"), - errorDetails: errorToDisplay.message, + errorDetails: anyError.message, } return { error: undefined, errorDetails: undefined } - }, [blockFeeDataError, isValidError, errorEip1559Support, errorTransactionInfo, nonceError, t]) + }, [blockFeeDataError, isValidError, errorEip1559Support, nonceError, t]) const isLoading = useMemo( - () => tx && !transactionInfo && !txDetails && !error, - [tx, transactionInfo, txDetails, error] + () => tx && isDecoding && !txDetails && !error, + [tx, isDecoding, txDetails, error] ) return { decodedTx, - transactionInfo, transaction, txDetails, gasSettings, diff --git a/apps/extension/src/ui/domains/Ethereum/useEthereumProvider.ts b/apps/extension/src/ui/domains/Ethereum/usePublicClient.ts similarity index 91% rename from apps/extension/src/ui/domains/Ethereum/useEthereumProvider.ts rename to apps/extension/src/ui/domains/Ethereum/usePublicClient.ts index 10ae430e52..c479139569 100644 --- a/apps/extension/src/ui/domains/Ethereum/useEthereumProvider.ts +++ b/apps/extension/src/ui/domains/Ethereum/usePublicClient.ts @@ -46,9 +46,15 @@ export const getExtensionPublicClient = ( }, }, // TODO check timers, decide if they should be here (remove them on backend) or clear them here and use defaults on backend - transport: custom({ - request: viemRequest(evmNetwork.id), - }), + transport: custom( + { + request: viemRequest(evmNetwork.id), + }, + { + // backend will retry 3 times, no need to retry here + retryCount: 0, + } + ), }) } diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBody.tsx b/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBody.tsx index 5d303517e6..6fb562ec9a 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBody.tsx +++ b/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBody.tsx @@ -1,4 +1,4 @@ -import { DecodedEvmTransaction } from "@core/util/getEthTransactionInfo" +import { DecodedEvmTransaction } from "@core/util/decodeEvmTransaction" import { ErrorBoundary, FallbackRender } from "@sentry/react" import { FC } from "react" diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyDefault.tsx b/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyDefault.tsx index ba55e6fbdb..ffa8c03714 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyDefault.tsx +++ b/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyDefault.tsx @@ -11,22 +11,18 @@ import { SignParamTokensDisplay } from "./shared/SignParamTokensDisplay" export const EthSignBodyDefault: FC = () => { const { t } = useTranslation("request") - const { network, transactionInfo, request } = useEthSignTransactionRequest() + const { network, request, decodedTx } = useEthSignTransactionRequest() const nativeToken = useToken(network?.nativeToken?.id) const nativeTokenRates = useTokenRates(nativeToken?.id) const amount = useMemo(() => { - return nativeToken && transactionInfo?.value && transactionInfo.value > 0n - ? new BalanceFormatter( - transactionInfo.value.toString(), - nativeToken.decimals, - nativeTokenRates - ) + return nativeToken && decodedTx?.value && decodedTx.value > 0n + ? new BalanceFormatter(decodedTx.value.toString(), nativeToken.decimals, nativeTokenRates) : null - }, [nativeToken, nativeTokenRates, transactionInfo?.value]) + }, [nativeToken, nativeTokenRates, decodedTx?.value]) - if (!transactionInfo) return null + if (!decodedTx) return null if (!network) return null if (!nativeToken) return null @@ -53,8 +49,8 @@ export const EthSignBodyDefault: FC = () => {
- {transactionInfo.isContractCall ? t("to contract") : t("to account")} - {transactionInfo.isContractCall ? ( + {decodedTx.isContractCall ? t("to contract") : t("to account")} + {decodedTx.isContractCall ? ( ) : ( { ) : null} )} - {transactionInfo.contractCall?.name && ( + {decodedTx.contractCall?.functionName && (
- {t("method:")} {transactionInfo.contractCall.name} + {t("method:")} {decodedTx.contractCall.functionName}
)} diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc20Approve.tsx b/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc20Approve.tsx index d072f1236d..63720c6af3 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc20Approve.tsx +++ b/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc20Approve.tsx @@ -1,16 +1,17 @@ import { BalanceFormatter } from "@core/domains/balances" import { CustomErc20Token } from "@core/domains/tokens/types" +import { assert } from "@polkadot/util" import useToken from "@ui/hooks/useToken" import { useTokenRates } from "@ui/hooks/useTokenRates" import useTokens from "@ui/hooks/useTokens" -import { BigNumber } from "ethers" import { FC, useMemo } from "react" import { Trans, useTranslation } from "react-i18next" +import { toHex } from "viem" import { SignAlertMessage } from "../SignAlertMessage" import { SignContainer } from "../SignContainer" import { SignViewBodyShimmer } from "../Views/SignViewBodyShimmer" -import { getContractCallArgOld } from "./getContractCallArg" +import { getContractCallArg } from "./getContractCallArg" import { SignParamAccountButton, SignParamNetworkAddressButton, @@ -23,7 +24,7 @@ const ALLOWANCE_UNLIMITED = "0xfffffffffffffffffffffffffffffffffffffffffffffffff export const EthSignBodyErc20Approve: FC = () => { const { t } = useTranslation("request") - const { account, network, transactionInfo } = useEthSignKnownTransactionRequest() + const { account, network, decodedTx } = useEthSignKnownTransactionRequest() const { tokens } = useTokens(true) const token = useMemo(() => { @@ -32,40 +33,47 @@ export const EthSignBodyErc20Approve: FC = () => { (t) => t.type === "evm-erc20" && t.evmNetwork?.id === network.id && - t.contractAddress === transactionInfo.targetAddress + t.contractAddress === decodedTx.targetAddress ) as CustomErc20Token) : undefined - }, [network, tokens, transactionInfo.targetAddress]) + }, [network, tokens, decodedTx.targetAddress]) const tokenRates = useTokenRates(token?.id) const { symbol } = useMemo(() => { - const symbol = token?.symbol ?? (transactionInfo.asset.symbol as string) + assert(decodedTx.asset?.symbol, "missing asset symbol") + const symbol = token?.symbol ?? (decodedTx.asset.symbol as string) return { symbol } - }, [token?.symbol, transactionInfo.asset.symbol]) + }, [token?.symbol, decodedTx.asset?.symbol]) const nativeToken = useToken(network?.nativeToken?.id) const { spender, allowance, isInfinite } = useMemo(() => { - const rawAllowance = getContractCallArgOld(transactionInfo.contractCall, "amount") - const isInfinite = rawAllowance?.toHexString() === ALLOWANCE_UNLIMITED + assert(decodedTx.asset?.decimals !== undefined, "missing asset decimals") + const rawAllowance = getContractCallArg(decodedTx, "amount") + const isInfinite = toHex(rawAllowance) === ALLOWANCE_UNLIMITED return { - spender: getContractCallArgOld(transactionInfo.contractCall, "spender"), + spender: getContractCallArg(decodedTx, "spender"), allowance: rawAllowance && !isInfinite - ? new BalanceFormatter( - rawAllowance.toString(), - transactionInfo.asset.decimals, - tokenRates - ) + ? new BalanceFormatter(rawAllowance.toString(), decodedTx.asset.decimals, tokenRates) : undefined, isInfinite, } - }, [tokenRates, transactionInfo.asset.decimals, transactionInfo.contractCall]) + }, [decodedTx, tokenRates]) - if (!nativeToken || !spender || !account || !network) return + if ( + !nativeToken || + !spender || + !account || + !network || + !decodedTx.targetAddress || + !decodedTx.asset?.symbol || + decodedTx.asset?.decimals === undefined + ) + return return ( {
{isInfinite ? t("to spend infinite") : t("to spend")}
{allowance ? ( ) : ( diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc721Approve.tsx b/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc721Approve.tsx index e1d9c83dbe..456606d402 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc721Approve.tsx +++ b/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc721Approve.tsx @@ -8,7 +8,7 @@ import { UnsafeImage } from "talisman-ui" import { SignContainer } from "../SignContainer" import { SignViewBodyShimmer } from "../Views/SignViewBodyShimmer" -import { getContractCallArgOld } from "./getContractCallArg" +import { getContractCallArg } from "./getContractCallArg" import { SignParamAccountButton, SignParamNetworkAddressButton } from "./shared" import { useEthSignKnownTransactionRequest } from "./shared/useEthSignKnownTransactionRequest" @@ -16,31 +16,34 @@ const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" export const EthSignBodyErc721Approve: FC = () => { const { t } = useTranslation("request") - const { account, network, transactionInfo } = useEthSignKnownTransactionRequest() + const { account, network, decodedTx } = useEthSignKnownTransactionRequest() + + const asset = decodedTx.asset as { name?: string; tokenURI?: string } | undefined const qMetadata = useQuery({ - queryKey: [transactionInfo.asset?.tokenURI], - queryFn: () => getNftMetadata(transactionInfo.asset?.tokenURI, 96, 96), + queryKey: [asset?.tokenURI], + queryFn: () => getNftMetadata(asset?.tokenURI, 96, 96), }) const { operator, approve, tokenId } = useMemo(() => { - const operator = getContractCallArgOld(transactionInfo.contractCall, "operator") + const operator = getContractCallArg(decodedTx, "operator") return { - operator: getContractCallArgOld(transactionInfo.contractCall, "operator"), + operator: getContractCallArg(decodedTx, "operator"), approve: operator !== ZERO_ADDRESS, - tokenId: BigNumber.from(getContractCallArgOld(transactionInfo.contractCall, "tokenId")), + tokenId: BigNumber.from(getContractCallArg(decodedTx, "tokenId")), } - }, [transactionInfo.contractCall]) + }, [decodedTx]) const { name, image } = useMemo( () => ({ - name: qMetadata?.data?.name ?? `${transactionInfo?.asset?.name} #${tokenId.toString()}`, + name: qMetadata?.data?.name ?? `${asset?.name} #${tokenId.toString()}`, image: qMetadata?.data?.image, }), - [qMetadata?.data?.image, qMetadata?.data?.name, tokenId, transactionInfo?.asset?.name] + [qMetadata?.data?.image, qMetadata?.data?.name, tokenId, asset?.name] ) - if (qMetadata.isLoading || !operator || !account || !network) return + if (qMetadata.isLoading || !operator || !account || !network || !decodedTx.targetAddress) + return return ( {
{t("to transfer")}
diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc721ApproveAll.tsx b/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc721ApproveAll.tsx index 116fc2ca6f..2e13ce365b 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc721ApproveAll.tsx +++ b/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc721ApproveAll.tsx @@ -4,22 +4,22 @@ import { useTranslation } from "react-i18next" import { SignAlertMessage } from "../SignAlertMessage" import { SignContainer } from "../SignContainer" import { SignViewBodyShimmer } from "../Views/SignViewBodyShimmer" -import { getContractCallArgOld } from "./getContractCallArg" +import { getContractCallArg } from "./getContractCallArg" import { SignParamAccountButton, SignParamNetworkAddressButton } from "./shared" import { useEthSignKnownTransactionRequest } from "./shared/useEthSignKnownTransactionRequest" export const EthSignBodyErc721ApproveAll: FC = () => { const { t } = useTranslation("request") - const { account, network, transactionInfo } = useEthSignKnownTransactionRequest() + const { account, network, decodedTx } = useEthSignKnownTransactionRequest() const { operator, approve } = useMemo(() => { return { - operator: getContractCallArgOld(transactionInfo.contractCall, "operator"), - approve: getContractCallArgOld(transactionInfo.contractCall, "approved"), + operator: getContractCallArg(decodedTx, "operator"), + approve: getContractCallArg(decodedTx, "approved"), } - }, [transactionInfo.contractCall]) + }, [decodedTx]) - if (!operator || !account || !network) return + if (!operator || !account || !network || !decodedTx.targetAddress) return return ( {
-
{t("to transfer all")}
+
{t("to transfer all")}
diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc721Transfer.tsx b/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc721Transfer.tsx index d9c7bc8378..3bdf83a5f0 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc721Transfer.tsx +++ b/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc721Transfer.tsx @@ -1,41 +1,40 @@ import { getNftMetadata } from "@core/util/getNftMetadata" import { useQuery } from "@tanstack/react-query" -import { BigNumber, BigNumberish } from "ethers" import { FC, useMemo } from "react" import { useTranslation } from "react-i18next" import { UnsafeImage } from "talisman-ui" import { SignContainer } from "../SignContainer" import { SignViewBodyShimmer } from "../Views/SignViewBodyShimmer" -import { getContractCallArgOld } from "./getContractCallArg" +import { getContractCallArg } from "./getContractCallArg" import { SignParamAccountButton, SignParamNetworkAddressButton } from "./shared" import { useEthSignKnownTransactionRequest } from "./shared/useEthSignKnownTransactionRequest" export const EthSignBodyErc721Transfer: FC = () => { const { t } = useTranslation("request") - const { account, network, transactionInfo } = useEthSignKnownTransactionRequest() + const { account, network, decodedTx } = useEthSignKnownTransactionRequest() + + const asset = decodedTx.asset as { tokenURI?: string; name?: string } | undefined const qMetadata = useQuery({ - queryKey: [transactionInfo.asset?.tokenURI], - queryFn: () => getNftMetadata(transactionInfo.asset?.tokenURI, 96, 96), + queryKey: [asset?.tokenURI], + queryFn: () => getNftMetadata(asset?.tokenURI, 96, 96), }) const { from, to, tokenId } = useMemo(() => { return { - from: getContractCallArgOld(transactionInfo.contractCall, "from"), - to: getContractCallArgOld(transactionInfo.contractCall, "to"), - tokenId: BigNumber.from( - getContractCallArgOld(transactionInfo.contractCall, "tokenId") - ), + from: getContractCallArg(decodedTx, "from"), + to: getContractCallArg(decodedTx, "to"), + tokenId: getContractCallArg(decodedTx, "tokenId"), } - }, [transactionInfo.contractCall]) + }, [decodedTx]) const { name, image } = useMemo( () => ({ - name: qMetadata?.data?.name ?? `${transactionInfo?.asset?.name} #${tokenId.toString()}`, + name: qMetadata?.data?.name ?? `${asset?.name} #${tokenId.toString()}`, image: qMetadata?.data?.image, }), - [qMetadata?.data?.image, qMetadata?.data?.name, tokenId, transactionInfo?.asset?.name] + [asset?.name, qMetadata?.data?.image, qMetadata?.data?.name, tokenId] ) const isOnBehalf = useMemo( @@ -43,14 +42,15 @@ export const EthSignBodyErc721Transfer: FC = () => { [account, from] ) - if (qMetadata.isLoading || !from || !to || !account || !network) return + if (qMetadata.isLoading || !from || !to || !account || !network || !decodedTx.targetAddress) + return return (
{t("Transfer")}
diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/convictionVoting/EthSignMoonVotingDelegate.tsx b/apps/extension/src/ui/domains/Sign/Ethereum/convictionVoting/EthSignMoonVotingDelegate.tsx index 4493b2c301..eedf917424 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/convictionVoting/EthSignMoonVotingDelegate.tsx +++ b/apps/extension/src/ui/domains/Sign/Ethereum/convictionVoting/EthSignMoonVotingDelegate.tsx @@ -1,33 +1,25 @@ -import { BigNumber } from "ethers" import { FC, useMemo } from "react" import { useTranslation } from "react-i18next" import { SignContainer } from "../../SignContainer" import { SignViewVotingDelegate } from "../../Views/convictionVoting/SignViewVotingDelegate" import { SignViewIconHeader } from "../../Views/SignViewIconHeader" -import { getContractCallArgOld } from "../getContractCallArg" +import { getContractCallArg } from "../getContractCallArg" import { useEthSignKnownTransactionRequest } from "../shared/useEthSignKnownTransactionRequest" export const EthSignMoonVotingDelegate: FC = () => { const { t } = useTranslation("request") - const { network, transactionInfo } = useEthSignKnownTransactionRequest() + const { network, decodedTx } = useEthSignKnownTransactionRequest() - const { amount, representative, conviction, trackId } = useMemo(() => { - const representative = getContractCallArgOld( - transactionInfo.contractCall, - "representative" - ) - const amount = getContractCallArgOld(transactionInfo.contractCall, "amount") - const conviction = getContractCallArgOld(transactionInfo.contractCall, "conviction") - const trackId = getContractCallArgOld(transactionInfo.contractCall, "trackId") - - return { - representative, - amount: amount?.toBigInt(), - conviction, - trackId, - } - }, [transactionInfo.contractCall]) + const [representative, amount, conviction, trackId] = useMemo( + () => [ + getContractCallArg(decodedTx, "representative"), + getContractCallArg(decodedTx, "amount"), + getContractCallArg(decodedTx, "conviction"), + getContractCallArg(decodedTx, "trackId"), + ], + [decodedTx] + ) if ( !network?.nativeToken?.id || diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/convictionVoting/EthSignMoonVotingUndelegate.tsx b/apps/extension/src/ui/domains/Sign/Ethereum/convictionVoting/EthSignMoonVotingUndelegate.tsx index 03a00674ac..1a18b408f9 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/convictionVoting/EthSignMoonVotingUndelegate.tsx +++ b/apps/extension/src/ui/domains/Sign/Ethereum/convictionVoting/EthSignMoonVotingUndelegate.tsx @@ -4,17 +4,14 @@ import { useTranslation } from "react-i18next" import { SignContainer } from "../../SignContainer" import { SignViewVotingUndelegate } from "../../Views/convictionVoting/SignViewVotingUndelegate" import { SignViewIconHeader } from "../../Views/SignViewIconHeader" -import { getContractCallArgOld } from "../getContractCallArg" +import { getContractCallArg } from "../getContractCallArg" import { useEthSignKnownTransactionRequest } from "../shared/useEthSignKnownTransactionRequest" export const EthSignMoonVotingUndelegate: FC = () => { const { t } = useTranslation("request") - const { network, transactionInfo } = useEthSignKnownTransactionRequest() + const { network, decodedTx } = useEthSignKnownTransactionRequest() - const trackId = useMemo( - () => getContractCallArgOld(transactionInfo.contractCall, "trackId"), - [transactionInfo.contractCall] - ) + const trackId = useMemo(() => getContractCallArg(decodedTx, "trackId"), [decodedTx]) if (!network?.nativeToken?.id || trackId === undefined) return null diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/convictionVoting/EthSignMoonVotingVote.tsx b/apps/extension/src/ui/domains/Sign/Ethereum/convictionVoting/EthSignMoonVotingVote.tsx index 508b7a18f9..13ce652e92 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/convictionVoting/EthSignMoonVotingVote.tsx +++ b/apps/extension/src/ui/domains/Sign/Ethereum/convictionVoting/EthSignMoonVotingVote.tsx @@ -1,19 +1,18 @@ -import { BigNumber } from "ethers" import { FC, useMemo } from "react" import { useTranslation } from "react-i18next" import { SignContainer } from "../../SignContainer" import { SignViewVotingVote } from "../../Views/convictionVoting/SignViewVotingVote" import { SignViewIconHeader } from "../../Views/SignViewIconHeader" -import { getContractCallArgOld } from "../getContractCallArg" +import { getContractCallArg } from "../getContractCallArg" import { useEthSignKnownTransactionRequest } from "../shared/useEthSignKnownTransactionRequest" export const EthSignMoonVotingVote: FC = () => { const { t } = useTranslation("request") - const { network, transactionInfo } = useEthSignKnownTransactionRequest() + const { network, decodedTx } = useEthSignKnownTransactionRequest() const { title, icon } = useMemo(() => { - switch (transactionInfo.contractCall.name) { + switch (decodedTx.contractCall?.functionName) { case "voteYes": return { title: t("Vote Yes"), @@ -27,19 +26,16 @@ export const EthSignMoonVotingVote: FC = () => { default: return {} } - }, [t, transactionInfo.contractCall.name]) + }, [t, decodedTx.contractCall?.functionName]) - const { voteAmount, pollIndex, conviction } = useMemo(() => { - const pollIndex = getContractCallArgOld(transactionInfo.contractCall, "pollIndex") - const voteAmount = getContractCallArgOld(transactionInfo.contractCall, "voteAmount") - const conviction = getContractCallArgOld(transactionInfo.contractCall, "conviction") - - return { - pollIndex, - voteAmount: voteAmount?.toBigInt(), - conviction, - } - }, [transactionInfo.contractCall]) + const [pollIndex, voteAmount, conviction] = useMemo( + () => [ + getContractCallArg(decodedTx, "pollIndex"), + getContractCallArg(decodedTx, "voteAmount"), + getContractCallArg(decodedTx, "conviction"), + ], + [decodedTx] + ) if ( !network?.nativeToken?.id || diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/getContractCallArg.ts b/apps/extension/src/ui/domains/Sign/Ethereum/getContractCallArg.ts index 01fe135529..375652f891 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/getContractCallArg.ts +++ b/apps/extension/src/ui/domains/Sign/Ethereum/getContractCallArg.ts @@ -1,16 +1,6 @@ -import { DecodedEvmTransaction } from "@core/util/getEthTransactionInfo" -import { ethers } from "ethers" +import { DecodedEvmTransaction } from "@core/util/decodeEvmTransaction" import { getAbiItem } from "viem" -/** @deprecated */ -export const getContractCallArgOld = ( - contractCall: ethers.utils.TransactionDescription, - argName: string -): T | undefined => { - const paramIndex = contractCall.functionFragment.inputs.findIndex((arg) => arg.name === argName) - return contractCall.args[paramIndex] as T -} - export const getContractCallArg = ( decodedTx: DecodedEvmTransaction, argName: string diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/shared/SignParamErc20TokenButton.tsx b/apps/extension/src/ui/domains/Sign/Ethereum/shared/SignParamErc20TokenButton.tsx index bfc0866669..cf5b1e1180 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/shared/SignParamErc20TokenButton.tsx +++ b/apps/extension/src/ui/domains/Sign/Ethereum/shared/SignParamErc20TokenButton.tsx @@ -8,7 +8,7 @@ import { SignParamButton } from "./SignParamButton" type SignParamErc20TokenButtonProps = { network: EvmNetwork | CustomEvmNetwork - asset: { name: string; symbol: string; decimals: number; image?: string } + asset: { symbol?: string } address: string withIcon?: boolean className?: string diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/shared/useEthSignKnownTransactionRequest.ts b/apps/extension/src/ui/domains/Sign/Ethereum/shared/useEthSignKnownTransactionRequest.ts index ce652b4a0e..12062e2d4e 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/shared/useEthSignKnownTransactionRequest.ts +++ b/apps/extension/src/ui/domains/Sign/Ethereum/shared/useEthSignKnownTransactionRequest.ts @@ -1,12 +1,11 @@ -import { DecodedEvmTransaction, KnownTransactionInfo } from "@core/util/getEthTransactionInfo" +import { DecodedEvmTransaction } from "@core/util/decodeEvmTransaction" import { useEthSignTransactionRequest } from "@ui/domains/Sign/SignRequestContext" // only call this hook from known contracts (ERC20, ERC721...) display components export const useEthSignKnownTransactionRequest = () => { - const { transactionInfo, decodedTx, ...rest } = useEthSignTransactionRequest() + const { decodedTx, ...rest } = useEthSignTransactionRequest() return { - transactionInfo: transactionInfo as KnownTransactionInfo, decodedTx: decodedTx as DecodedEvmTransaction, ...rest, } diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/staking/EthSignMoonStakingSetAutoCompound.tsx b/apps/extension/src/ui/domains/Sign/Ethereum/staking/EthSignMoonStakingSetAutoCompound.tsx index 183fab1c64..ef8a02d8e9 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/staking/EthSignMoonStakingSetAutoCompound.tsx +++ b/apps/extension/src/ui/domains/Sign/Ethereum/staking/EthSignMoonStakingSetAutoCompound.tsx @@ -4,20 +4,14 @@ import { useTranslation } from "react-i18next" import { SignContainer } from "../../SignContainer" import { SignViewIconHeader } from "../../Views/SignViewIconHeader" import { SignViewStakingSetAutoCompound } from "../../Views/staking/SignViewStakingSetAutoCompound" -import { getContractCallArgOld } from "../getContractCallArg" +import { getContractCallArg } from "../getContractCallArg" import { useEthSignKnownTransactionRequest } from "../shared/useEthSignKnownTransactionRequest" export const EthSignMoonStakingSetAutoCompound: FC = () => { const { t } = useTranslation("request") - const { network, transactionInfo } = useEthSignKnownTransactionRequest() + const { network, decodedTx } = useEthSignKnownTransactionRequest() - const { autoCompound } = useMemo(() => { - const autoCompound = getContractCallArgOld(transactionInfo.contractCall, "value") - - return { - autoCompound, - } - }, [transactionInfo.contractCall]) + const autoCompound = useMemo(() => getContractCallArg(decodedTx, "value"), [decodedTx]) if (!network?.nativeToken?.id || autoCompound === undefined) return null diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/staking/EthSignMoonStakingStake.tsx b/apps/extension/src/ui/domains/Sign/Ethereum/staking/EthSignMoonStakingStake.tsx index f6307dc9fd..0df7d152af 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/staking/EthSignMoonStakingStake.tsx +++ b/apps/extension/src/ui/domains/Sign/Ethereum/staking/EthSignMoonStakingStake.tsx @@ -1,28 +1,25 @@ import useToken from "@ui/hooks/useToken" -import { BigNumber } from "ethers" import { FC, useMemo } from "react" import { useTranslation } from "react-i18next" import { SignContainer } from "../../SignContainer" import { SignViewIconHeader } from "../../Views/SignViewIconHeader" import { SignViewStakingStake } from "../../Views/staking/SignViewStakingStake" -import { getContractCallArgOld } from "../getContractCallArg" +import { getContractCallArg } from "../getContractCallArg" import { useEthSignKnownTransactionRequest } from "../shared/useEthSignKnownTransactionRequest" export const EthSignMoonStakingStake: FC = () => { const { t } = useTranslation("request") - const { network, transactionInfo } = useEthSignKnownTransactionRequest() + const { network, decodedTx } = useEthSignKnownTransactionRequest() const token = useToken(network?.nativeToken?.id) - const { planck, autoCompound } = useMemo(() => { - const amount = getContractCallArgOld(transactionInfo.contractCall, "amount") - const autoCompound = getContractCallArgOld(transactionInfo.contractCall, "autoCompound") - - return { - planck: amount?.toBigInt(), - autoCompound, - } - }, [transactionInfo.contractCall]) + const [planck, autoCompound] = useMemo( + () => [ + getContractCallArg(decodedTx, "amount"), + getContractCallArg(decodedTx, "autoCompound"), + ], + [decodedTx] + ) if (!network?.nativeToken?.id || !planck || !token) return null diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/staking/EthSignMoonStakingStakeLess.tsx b/apps/extension/src/ui/domains/Sign/Ethereum/staking/EthSignMoonStakingStakeLess.tsx index 03c4f947ca..1fffc98f03 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/staking/EthSignMoonStakingStakeLess.tsx +++ b/apps/extension/src/ui/domains/Sign/Ethereum/staking/EthSignMoonStakingStakeLess.tsx @@ -1,21 +1,17 @@ -import { BigNumber } from "ethers" import { FC, useMemo } from "react" import { useTranslation } from "react-i18next" import { SignContainer } from "../../SignContainer" import { SignViewIconHeader } from "../../Views/SignViewIconHeader" import { SignViewStakingStakeLess } from "../../Views/staking/SignViewStakingStakeLess" -import { getContractCallArgOld } from "../getContractCallArg" +import { getContractCallArg } from "../getContractCallArg" import { useEthSignKnownTransactionRequest } from "../shared/useEthSignKnownTransactionRequest" export const EthSignMoonStakingStakeLess: FC = () => { const { t } = useTranslation("request") - const { network, transactionInfo } = useEthSignKnownTransactionRequest() + const { network, decodedTx } = useEthSignKnownTransactionRequest() - const less = useMemo( - () => getContractCallArgOld(transactionInfo.contractCall, "less")?.toBigInt(), - [transactionInfo.contractCall] - ) + const less = useMemo(() => getContractCallArg(decodedTx, "less"), [decodedTx]) if (!network?.nativeToken?.id || !less) return null diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/staking/EthSignMoonStakingStakeMore.tsx b/apps/extension/src/ui/domains/Sign/Ethereum/staking/EthSignMoonStakingStakeMore.tsx index b99b8b6bea..4423ff777b 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/staking/EthSignMoonStakingStakeMore.tsx +++ b/apps/extension/src/ui/domains/Sign/Ethereum/staking/EthSignMoonStakingStakeMore.tsx @@ -1,21 +1,17 @@ -import { BigNumber } from "ethers" import { FC, useMemo } from "react" import { useTranslation } from "react-i18next" import { SignContainer } from "../../SignContainer" import { SignViewIconHeader } from "../../Views/SignViewIconHeader" import { SignViewStakingStakeMore } from "../../Views/staking/SignViewStakingStakeMore" -import { getContractCallArgOld } from "../getContractCallArg" +import { getContractCallArg } from "../getContractCallArg" import { useEthSignKnownTransactionRequest } from "../shared/useEthSignKnownTransactionRequest" export const EthSignMoonStakingStakeMore: FC = () => { const { t } = useTranslation("request") - const { network, transactionInfo } = useEthSignKnownTransactionRequest() + const { network, decodedTx } = useEthSignKnownTransactionRequest() - const more = useMemo( - () => getContractCallArgOld(transactionInfo.contractCall, "more")?.toBigInt(), - [transactionInfo.contractCall] - ) + const more = useMemo(() => getContractCallArg(decodedTx, "more"), [decodedTx]) if (!network?.nativeToken?.id || !more) return null diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/xTokens/EthSignMoonXTokensTransfer.tsx b/apps/extension/src/ui/domains/Sign/Ethereum/xTokens/EthSignMoonXTokensTransfer.tsx index 5f5361b2a9..9e690873fc 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/xTokens/EthSignMoonXTokensTransfer.tsx +++ b/apps/extension/src/ui/domains/Sign/Ethereum/xTokens/EthSignMoonXTokensTransfer.tsx @@ -8,14 +8,13 @@ import { useCoinGeckoTokenRates } from "@ui/hooks/useCoingeckoTokenRates" import { useErc20TokenInfo } from "@ui/hooks/useErc20TokenInfo" import useToken from "@ui/hooks/useToken" import useTokens from "@ui/hooks/useTokens" -import { BigNumber } from "ethers" import { FC, useMemo } from "react" import { useTranslation } from "react-i18next" import { SignContainer } from "../../SignContainer" import { SignViewIconHeader } from "../../Views/SignViewIconHeader" import { SignViewXTokensTransfer } from "../../Views/transfer/SignViewCrossChainTransfer" -import { getContractCallArgOld } from "../getContractCallArg" +import { getContractCallArg } from "../getContractCallArg" import { useEthSignKnownTransactionRequest } from "../shared/useEthSignKnownTransactionRequest" type DecodedMultilocation = { @@ -69,27 +68,18 @@ const decodeMultilocation = (multilocation?: { export const EthSignMoonXTokensTransfer: FC = () => { const { t } = useTranslation("request") - const { network, transactionInfo, account } = useEthSignKnownTransactionRequest() + const { network, decodedTx, account } = useEthSignKnownTransactionRequest() const substrateChain = useChain(network?.substrateChain?.id) const { tokens } = useTokens(true) - const { destination, amount, currencyAddress } = useMemo(() => { - const destination = getContractCallArgOld<{ parents: number; interior: string[] }>( - transactionInfo.contractCall, - "destination" - ) - const amount = getContractCallArgOld(transactionInfo.contractCall, "amount") - const currencyAddress = getContractCallArgOld( - transactionInfo.contractCall, - "currency_address" - ) - - return { - destination, - amount: amount?.toBigInt(), - currencyAddress, - } - }, [transactionInfo.contractCall]) + const [destination, amount, currencyAddress] = useMemo( + () => [ + getContractCallArg<{ parents: number; interior: string[] }>(decodedTx, "destination"), + getContractCallArg(decodedTx, "amount"), + getContractCallArg(decodedTx, "currency_address"), + ], + [decodedTx] + ) const erc20 = useErc20TokenInfo(network?.id, currencyAddress) const nativeToken = useToken(network?.nativeToken?.id) diff --git a/apps/extension/src/ui/domains/Sign/SignContainer.tsx b/apps/extension/src/ui/domains/Sign/SignContainer.tsx index 81019ecb77..32c63f48b2 100644 --- a/apps/extension/src/ui/domains/Sign/SignContainer.tsx +++ b/apps/extension/src/ui/domains/Sign/SignContainer.tsx @@ -22,6 +22,8 @@ export const SignContainer: FC = ({ header, networkType, }) => { + const alertContainer = document.getElementById("sign-alerts-inject") as Element + return ( {header} @@ -31,7 +33,7 @@ export const SignContainer: FC = ({ {networkType === "ethereum" && } {networkType === "substrate" && }
- {alert && createPortal(alert, document.getElementById("sign-alerts-inject") as Element)} + {alert && alertContainer && createPortal(alert, alertContainer)} ) } diff --git a/apps/extension/src/ui/domains/Sign/SignRequestContext/EthereumSignTransactionRequestContext.ts b/apps/extension/src/ui/domains/Sign/SignRequestContext/EthereumSignTransactionRequestContext.ts index 8b32aecaa4..7ca77cd43f 100644 --- a/apps/extension/src/ui/domains/Sign/SignRequestContext/EthereumSignTransactionRequestContext.ts +++ b/apps/extension/src/ui/domains/Sign/SignRequestContext/EthereumSignTransactionRequestContext.ts @@ -34,7 +34,6 @@ const useEthSignTransactionRequestProvider = ({ id }: KnownSigningRequestIdOnly< const { decodedTx, transaction, - transactionInfo, txDetails, priority, setPriority, @@ -89,7 +88,6 @@ const useEthSignTransactionRequestProvider = ({ id }: KnownSigningRequestIdOnly< networkUsage, decodedTx, transaction, - transactionInfo, approve, approveHardware, isPayloadLocked, diff --git a/apps/extension/src/ui/domains/Sign/ViewDetails/ViewDetailsEth.tsx b/apps/extension/src/ui/domains/Sign/ViewDetails/ViewDetailsEth.tsx index 0fd7f0fbde..62b8573b1a 100644 --- a/apps/extension/src/ui/domains/Sign/ViewDetails/ViewDetailsEth.tsx +++ b/apps/extension/src/ui/domains/Sign/ViewDetails/ViewDetailsEth.tsx @@ -56,17 +56,12 @@ const ViewDetailsContent: FC = ({ onClose }) => { txDetails, priority, transaction, - transactionInfo, + decodedTx, error, errorDetails, } = useEthSignTransactionRequest() const { genericEvent } = useAnalytics() - const txInfo = useMemo(() => { - if (transactionInfo && transactionInfo.contractType !== "unknown") return transactionInfo - return undefined - }, [transactionInfo]) - const nativeToken = useToken(network?.nativeToken?.id) const formatEthValue = useCallback( (value: bigint = 0n) => { @@ -117,10 +112,10 @@ const ViewDetailsContent: FC = ({ onClose }) => {
{t("Details")}
- {!!txInfo?.isContractCall && ( + {!!decodedTx?.isContractCall && ( - {txInfo?.contractType - ? `${txInfo?.contractType} : ${txInfo?.contractCall?.name ?? t("N/A")}` + {decodedTx?.contractType + ? `${decodedTx?.contractType} : ${decodedTx?.contractCall?.functionName ?? t("N/A")}` : t("Unknown")} )} diff --git a/apps/extension/src/ui/hooks/useErc20TokenInfo.ts b/apps/extension/src/ui/hooks/useErc20TokenInfo.ts index 5ddf4062a0..cbdb9ef8fc 100644 --- a/apps/extension/src/ui/hooks/useErc20TokenInfo.ts +++ b/apps/extension/src/ui/hooks/useErc20TokenInfo.ts @@ -2,7 +2,7 @@ import { EvmAddress } from "@core/domains/ethereum/types" import { CustomErc20TokenCreate } from "@core/domains/tokens/types" import { getErc20TokenInfo } from "@core/util/getErc20TokenInfo" import { EvmNetworkId } from "@talismn/chaindata-provider" -import { usePublicClient } from "@ui/domains/Ethereum/useEthereumProvider" +import { usePublicClient } from "@ui/domains/Ethereum/usePublicClient" import { useEffect, useState } from "react" export const useErc20TokenInfo = (evmNetworkId?: EvmNetworkId, contractAddress?: EvmAddress) => { diff --git a/packages/talisman-ui/src/components/Tooltip.tsx b/packages/talisman-ui/src/components/Tooltip.tsx index 2174f7dcc5..3846f1fbab 100644 --- a/packages/talisman-ui/src/components/Tooltip.tsx +++ b/packages/talisman-ui/src/components/Tooltip.tsx @@ -136,7 +136,7 @@ export const TooltipTrigger = React.forwardRef< export const TooltipContent = React.forwardRef>( function TooltipContent( { - className = "rounded-xs text-body-secondary border-grey-700 z-20 border-[0.5px] bg-black p-3 text-xs shadow", + className = "rounded-xs text-body-secondary border-grey-700 z-20 border-[0.5px] bg-black p-3 text-xs shadow max-w-full overflow-hidden", ...props }, propRef From 9ae419061f3162af071e3f5b8a94b9a17d4054f4 Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Fri, 3 Nov 2023 15:23:40 +0900 Subject: [PATCH 33/50] chore: cleanup --- .../src/core/util/decodeEvmTransaction.ts | 26 +++++-------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/apps/extension/src/core/util/decodeEvmTransaction.ts b/apps/extension/src/core/util/decodeEvmTransaction.ts index 3a65046567..3723f25b98 100644 --- a/apps/extension/src/core/util/decodeEvmTransaction.ts +++ b/apps/extension/src/core/util/decodeEvmTransaction.ts @@ -12,18 +12,6 @@ import { abiMoonConvictionVoting } from "./abi/abiMoonConvictionVoting" import { abiMoonXTokens } from "./abi/abiMoonXTokens" import { isContractAddress } from "./isContractAddress" -const KNOWN_ABI = { - ERC20: parseAbi(abiErc20), - ERC721: parseAbi(abiErc721), - ERC1155: parseAbi(abiErc1155), - MoonStaking: abiMoonStaking, - MoonConvictionVoting: abiMoonConvictionVoting, - MoonXTokens: abiMoonXTokens, - unknown: null, -} as const - -export type ContractType = keyof typeof KNOWN_ABI - const MOON_CHAIN_PRECOMPILES = [ { address: "0x0000000000000000000000000000000000000800", @@ -57,7 +45,7 @@ const STANDARD_CONTRACTS = [ }, ] as const -export const decodeEvmTransaction = async ( +export const decodeEvmTransaction = async ( publicClient: PublicClient, tx: TransactionRequestBase ) => { @@ -76,7 +64,7 @@ export const decodeEvmTransaction = async ( //const { contractType, abi } = precompile const contractCall = decodeFunctionData({ abi, data }) return { - contractType: contractType as TContractType, + contractType, contractCall, targetAddress, isContractCall: true, @@ -95,7 +83,7 @@ export const decodeEvmTransaction = async ( const contract = getContract({ address: targetAddress, - abi: KNOWN_ABI.ERC20, + abi, publicClient, }) @@ -106,7 +94,7 @@ export const decodeEvmTransaction = async ( ]) return { - contractType: contractType as TContractType, + contractType, contractCall, abi, targetAddress, @@ -129,7 +117,7 @@ export const decodeEvmTransaction = async ( const contract = getContract({ address: targetAddress, - abi: KNOWN_ABI.ERC721, + abi, publicClient, }) @@ -151,7 +139,7 @@ export const decodeEvmTransaction = async ( : undefined return { - contractType: contractType as TContractType, + contractType, contractCall, abi, targetAddress, @@ -166,7 +154,7 @@ export const decodeEvmTransaction = async ( } } - return { contractType: "unknown" as TContractType, targetAddress, isContractCall, value } + return { contractType: "unknown", targetAddress, isContractCall, value } } export type DecodedEvmTransaction = Awaited> From f2b6fdcff63630d5d946add1f01fa126fb4e46a6 Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Fri, 3 Nov 2023 15:24:57 +0900 Subject: [PATCH 34/50] chore: cleanup --- .../src/ui/domains/Sign/Ethereum/EthSignBodyErc721Approve.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc721Approve.tsx b/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc721Approve.tsx index 456606d402..fa1d4b698a 100644 --- a/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc721Approve.tsx +++ b/apps/extension/src/ui/domains/Sign/Ethereum/EthSignBodyErc721Approve.tsx @@ -1,7 +1,6 @@ import { getNftMetadata } from "@core/util/getNftMetadata" import { useQuery } from "@tanstack/react-query" import { SignAlertMessage } from "@ui/domains/Sign/SignAlertMessage" -import { BigNumber } from "ethers" import { FC, useMemo } from "react" import { useTranslation } from "react-i18next" import { UnsafeImage } from "talisman-ui" @@ -30,7 +29,7 @@ export const EthSignBodyErc721Approve: FC = () => { return { operator: getContractCallArg(decodedTx, "operator"), approve: operator !== ZERO_ADDRESS, - tokenId: BigNumber.from(getContractCallArg(decodedTx, "tokenId")), + tokenId: getContractCallArg(decodedTx, "tokenId"), } }, [decodedTx]) From c6db575e75b6921c1deb62c7497e7ed90eb5dde6 Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Fri, 3 Nov 2023 16:25:29 +0900 Subject: [PATCH 35/50] wip: dcent --- .../src/ui/domains/Sign/SignDcentEthereum.tsx | 141 ++++++++++++------ 1 file changed, 96 insertions(+), 45 deletions(-) diff --git a/apps/extension/src/ui/domains/Sign/SignDcentEthereum.tsx b/apps/extension/src/ui/domains/Sign/SignDcentEthereum.tsx index 63b98a9f78..259c464b7e 100644 --- a/apps/extension/src/ui/domains/Sign/SignDcentEthereum.tsx +++ b/apps/extension/src/ui/domains/Sign/SignDcentEthereum.tsx @@ -9,15 +9,36 @@ import { classNames } from "@talismn/util" import { DcentError, dcent } from "@ui/util/dcent" import { useBringPopupBackInFront } from "@ui/util/dcent/useBringPopupBackInFront" import DcentWebConnector from "dcent-web-connector" -import { ethers } from "ethers" import { FC, useCallback, useEffect, useState } from "react" import { useTranslation } from "react-i18next" import { Button } from "talisman-ui" +import { + Signature, + TransactionRequest, + TransactionSerializable, + hexToBigInt, + serializeTransaction, +} from "viem" import { ErrorMessageDrawer } from "./ErrorMessageDrawer" import { SignHardwareEthereumProps } from "./SignHardwareEthereum" +const toSignature = ({ + sign_v, + sign_r, + sign_s, +}: { + sign_v: string | number + sign_r: string + sign_s: string +}): Signature => ({ + v: typeof sign_v === "string" ? hexToBigInt(`0x${sign_v}`) : BigInt(sign_v), + r: `0x${sign_r}`, + s: `0x${sign_s}`, +}) + const signWithDcent = async ( + chainId: number, method: EthSignMessageMethod | "eth_sendTransaction", // eslint-disable-next-line @typescript-eslint/no-explicit-any payload: any, @@ -44,62 +65,76 @@ const signWithDcent = async ( } case "eth_sendTransaction": { - const { - accessList, - to, - nonce, - gasLimit, - gasPrice, - data, - value, - chainId, - type, - maxPriorityFeePerGas, - maxFeePerGas, - } = await ethers.utils.resolveProperties(payload as ethers.providers.TransactionRequest) - - const baseTx: ethers.utils.UnsignedTransaction = { - to, - gasLimit, + const txRequest = payload as TransactionRequest + const baseTx: TransactionSerializable = { + ...txRequest, + // viem's legacy serialization changes the chainId once deserialized, tx can't be submitted + // can be tested on BSC + type: txRequest.type === "legacy" ? "eip2930" : "eip1559", chainId, - type, } - - if (nonce !== undefined) baseTx.nonce = ethers.BigNumber.from(nonce).toNumber() - if (maxPriorityFeePerGas) baseTx.maxPriorityFeePerGas = maxPriorityFeePerGas - if (maxFeePerGas) baseTx.maxFeePerGas = maxFeePerGas - if (gasPrice) baseTx.gasPrice = gasPrice - if (data) baseTx.data = data - if (value) baseTx.value = value - if (accessList) baseTx.accessList = accessList + // console.log("baseTx", baseTx) + + // const { + // accessList, + // to, + // nonce, + // gasLimit, + // gasPrice, + // data, + // value, + // chainId, + // type, + // maxPriorityFeePerGas, + // maxFeePerGas, + // } = await ethers.utils.resolveProperties(payload as ethers.providers.TransactionRequest) + + // const baseTx: ethers.utils.UnsignedTransaction = { + // to, + // gasLimit, + // chainId, + // type, + // } + + // if (nonce !== undefined) baseTx.nonce = ethers.BigNumber.from(nonce).toNumber() + // if (maxPriorityFeePerGas) baseTx.maxPriorityFeePerGas = maxPriorityFeePerGas + // if (maxFeePerGas) baseTx.maxFeePerGas = maxFeePerGas + // if (gasPrice) baseTx.gasPrice = gasPrice + // if (data) baseTx.data = data + // if (value) baseTx.value = value + // if (accessList) baseTx.accessList = accessList // Note : most fields can't be undefined const args = [ DcentWebConnector.coinType.ETHEREUM, - ethers.BigNumber.from(nonce).toString(), - type === 2 ? undefined : gasPrice?.toString(), - gasLimit?.toString() ?? "21000", - to, - value?.toString() ?? "0", - data ?? "0x", + (baseTx.nonce ?? 0).toString(), // ethers.BigNumber.from(nonce).toString(), + baseTx.type === "eip1559" ? undefined : baseTx.gasPrice?.toString(), // type === 2 ? undefined : gasPrice?.toString(), + baseTx.gas?.toString() ?? "21000", + baseTx.to, + baseTx.value?.toString() ?? "0", + baseTx.data ?? "0x", accountPath, chainId, ] - if (type === 2) + if (baseTx.type === "eip1559") args.push(2, { - accessList: accessList ?? [], - maxPriorityFeePerGas: maxPriorityFeePerGas?.toString(), - maxFeePerGas: maxFeePerGas?.toString(), + accessList: baseTx.accessList ?? [], + maxPriorityFeePerGas: baseTx.maxPriorityFeePerGas?.toString(), + maxFeePerGas: baseTx.maxFeePerGas?.toString(), }) - const result = await dcent.getEthereumSignedTransaction(...args) + const sig = await dcent.getEthereumSignedTransaction(...args) + + // console.log("dcent sig", { sig }) + + return serializeTransaction(baseTx, toSignature(sig)) - return ethers.utils.serializeTransaction(baseTx, { - v: ethers.BigNumber.from(result.sign_v).toNumber(), - r: result.sign_r, - s: result.sign_s, - }) as `0x${string}` + // return ethers.utils.serializeTransaction(baseTx, { + // v: ethers.BigNumber.from(result.sign_v).toNumber(), + // r: result.sign_r, + // s: result.sign_s, + // }) as `0x${string}` } // other message types are unsupported // case "eth_signTypedData": @@ -111,6 +146,7 @@ const signWithDcent = async ( } const SignDcentEthereum: FC = ({ + evmNetworkId, account, method, payload, @@ -141,7 +177,12 @@ const SignDcentEthereum: FC = ({ try { // this will open the bridge page that may hide Talisman popup => bring talisman back in front startListening() - const signature = await signWithDcent(method, payload, (account as AccountJsonDcent).path) + const signature = await signWithDcent( + Number(evmNetworkId), + method, + payload, + (account as AccountJsonDcent).path + ) stopListening() await onSigned({ signature }) @@ -157,7 +198,17 @@ const SignDcentEthereum: FC = ({ } onSentToDevice?.(false) setIsSigning(false) - }, [onSigned, payload, account, onSentToDevice, startListening, method, stopListening, t]) + }, [ + onSigned, + payload, + account, + onSentToDevice, + startListening, + evmNetworkId, + method, + stopListening, + t, + ]) return (
From 82d77001cdcb5034aebfb464fb29ebde72b9bfa5 Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Fri, 3 Nov 2023 17:14:31 +0900 Subject: [PATCH 36/50] feat: dcent for viem --- .../src/ui/domains/Sign/SignDcentEthereum.tsx | 62 +------------------ 1 file changed, 2 insertions(+), 60 deletions(-) diff --git a/apps/extension/src/ui/domains/Sign/SignDcentEthereum.tsx b/apps/extension/src/ui/domains/Sign/SignDcentEthereum.tsx index 259c464b7e..2c89480ae7 100644 --- a/apps/extension/src/ui/domains/Sign/SignDcentEthereum.tsx +++ b/apps/extension/src/ui/domains/Sign/SignDcentEthereum.tsx @@ -12,31 +12,11 @@ import DcentWebConnector from "dcent-web-connector" import { FC, useCallback, useEffect, useState } from "react" import { useTranslation } from "react-i18next" import { Button } from "talisman-ui" -import { - Signature, - TransactionRequest, - TransactionSerializable, - hexToBigInt, - serializeTransaction, -} from "viem" +import { TransactionRequest, TransactionSerializable } from "viem" import { ErrorMessageDrawer } from "./ErrorMessageDrawer" import { SignHardwareEthereumProps } from "./SignHardwareEthereum" -const toSignature = ({ - sign_v, - sign_r, - sign_s, -}: { - sign_v: string | number - sign_r: string - sign_s: string -}): Signature => ({ - v: typeof sign_v === "string" ? hexToBigInt(`0x${sign_v}`) : BigInt(sign_v), - r: `0x${sign_r}`, - s: `0x${sign_s}`, -}) - const signWithDcent = async ( chainId: number, method: EthSignMessageMethod | "eth_sendTransaction", @@ -73,36 +53,6 @@ const signWithDcent = async ( type: txRequest.type === "legacy" ? "eip2930" : "eip1559", chainId, } - // console.log("baseTx", baseTx) - - // const { - // accessList, - // to, - // nonce, - // gasLimit, - // gasPrice, - // data, - // value, - // chainId, - // type, - // maxPriorityFeePerGas, - // maxFeePerGas, - // } = await ethers.utils.resolveProperties(payload as ethers.providers.TransactionRequest) - - // const baseTx: ethers.utils.UnsignedTransaction = { - // to, - // gasLimit, - // chainId, - // type, - // } - - // if (nonce !== undefined) baseTx.nonce = ethers.BigNumber.from(nonce).toNumber() - // if (maxPriorityFeePerGas) baseTx.maxPriorityFeePerGas = maxPriorityFeePerGas - // if (maxFeePerGas) baseTx.maxFeePerGas = maxFeePerGas - // if (gasPrice) baseTx.gasPrice = gasPrice - // if (data) baseTx.data = data - // if (value) baseTx.value = value - // if (accessList) baseTx.accessList = accessList // Note : most fields can't be undefined const args = [ @@ -126,15 +76,7 @@ const signWithDcent = async ( const sig = await dcent.getEthereumSignedTransaction(...args) - // console.log("dcent sig", { sig }) - - return serializeTransaction(baseTx, toSignature(sig)) - - // return ethers.utils.serializeTransaction(baseTx, { - // v: ethers.BigNumber.from(result.sign_v).toNumber(), - // r: result.sign_r, - // s: result.sign_s, - // }) as `0x${string}` + return `0x${sig.signed}` } // other message types are unsupported // case "eth_signTypedData": From 6ae34ec62421d0f94823fece2e13ebbd045193b3 Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Fri, 3 Nov 2023 18:41:23 +0900 Subject: [PATCH 37/50] wip: self review --- .../src/core/domains/ethereum/errors.ts | 13 --- .../domains/ethereum/handler.extension.ts | 7 +- .../src/core/domains/ethereum/handler.tabs.ts | 32 ++----- .../src/core/domains/ethereum/helpers.ts | 93 +------------------ .../src/core/domains/ethereum/types.ts | 5 +- .../src/core/domains/signing/types.ts | 2 +- .../src/core/domains/transfers/handler.ts | 21 +++-- apps/extension/src/core/handlers/index.ts | 33 +++---- .../src/ui/domains/Sign/SignDcentEthereum.tsx | 4 +- 9 files changed, 45 insertions(+), 165 deletions(-) diff --git a/apps/extension/src/core/domains/ethereum/errors.ts b/apps/extension/src/core/domains/ethereum/errors.ts index 52e5123aca..709b39782c 100644 --- a/apps/extension/src/core/domains/ethereum/errors.ts +++ b/apps/extension/src/core/domains/ethereum/errors.ts @@ -51,19 +51,6 @@ export const getHumanReadableErrorMessage = (error: unknown) => { if (shortMessage) return shortMessage - // if (serverError) { - // const message = serverError.error?.message ?? serverError.reason ?? serverError.message - // return message - // .replace("VM Exception while processing transaction: reverted with reason string ", "") - // .replace("VM Exception while processing transaction: revert", "") - // .replace("VM Exception while processing transaction:", "") - // .trim() - // } - - // if (reason === "processing response error") return "Invalid transaction" - - // if (reason) return reason - if (code) return getErrorLabelFromCode(code) if (message) return message diff --git a/apps/extension/src/core/domains/ethereum/handler.extension.ts b/apps/extension/src/core/domains/ethereum/handler.extension.ts index c8a28fcc6b..4f550d61c9 100644 --- a/apps/extension/src/core/domains/ethereum/handler.extension.ts +++ b/apps/extension/src/core/domains/ethereum/handler.extension.ts @@ -88,7 +88,6 @@ export class EthHandler extends ExtensionHandler { assert(isEthereumAddress(account.address), "Invalid ethereum address") - // rebuild BigNumber property values (converted to json when serialized) const tx = parseTransactionRequest(transaction) if (tx.nonce === undefined) tx.nonce = await getTransactionCount(account.address, ethChainId) @@ -110,7 +109,6 @@ export class EthHandler extends ExtensionHandler { }) if (result.ok) { - // long running operation, we do not want this inside getPairForAddressSafely watchEthereumTransaction(ethChainId, result.val, transaction, { siteUrl: queued.url, notifications: true, @@ -154,7 +152,6 @@ export class EthHandler extends ExtensionHandler { try { const hash = await client.sendRawTransaction({ serializedTransaction: signed }) - // long running operation, we do not want this inside getPairForAddressSafely watchEthereumTransaction(evmNetworkId, hash, unsigned, { notifications: true, transferInfo, @@ -180,8 +177,6 @@ export class EthHandler extends ExtensionHandler { assert(evmNetworkId, "chainId is not defined") assert(unsigned.from, "from is not defined") - // rebuild BigNumber property values (converted to json when serialized) - const result = await getPairForAddressSafely(unsigned.from, async (pair) => { const client = await chainConnectorEvm.getWalletClientForEvmNetwork(evmNetworkId) assert(client, "Missing client for chain " + evmNetworkId) @@ -201,7 +196,6 @@ export class EthHandler extends ExtensionHandler { }) if (result.ok) { - // long running operation, we do not want this inside getPairForAddressSafely watchEthereumTransaction(evmNetworkId, result.val, unsigned, { notifications: true, transferInfo, @@ -531,6 +525,7 @@ export class EthHandler extends ExtensionHandler { private ethRequest: MessageHandler<"pri(eth.request)"> = async ({ chainId, method, params }) => { const client = await chainConnectorEvm.getPublicClientForEvmNetwork(chainId) assert(client, `No client for chain ${chainId}`) + return client.request({ method: method as never, params: params as never, diff --git a/apps/extension/src/core/domains/ethereum/handler.tabs.ts b/apps/extension/src/core/domains/ethereum/handler.tabs.ts index 944425b2fa..ec22231370 100644 --- a/apps/extension/src/core/domains/ethereum/handler.tabs.ts +++ b/apps/extension/src/core/domains/ethereum/handler.tabs.ts @@ -45,7 +45,7 @@ import { recoverMessageAddress, toHex, } from "viem" -import { hexToNumber } from "viem/utils" +import { hexToNumber, isHex } from "viem/utils" import { getErc20TokenInfo } from "../../util/getErc20TokenInfo" import { @@ -62,7 +62,7 @@ import { import { requestAddNetwork, requestWatchAsset } from "./requests" import { EthProviderMessage, - EthRequestArgsViem, + EthRequestArgs, EthRequestArguments, EthRequestResult, EthRequestSignArguments, @@ -335,7 +335,7 @@ export class EthTabsHandler extends TabsHandler { client.request({ method: "eth_chainId" }), throwAfter(10_000, "timeout"), // 10 sec timeout ]) - const rpcChainId = hexToNumber(rpcChainIdHex as `0x${string}`) + const rpcChainId = hexToNumber(rpcChainIdHex) assert(rpcChainId === chainId, "chainId mismatch") } catch (err) { @@ -580,8 +580,7 @@ export class EthTabsHandler extends TabsHandler { let specifiedChainId = (txRequest as unknown as { chainId?: string | number }).chainId // ensure chainId isn't an hex (ex: Zerion) - if (typeof specifiedChainId === "string" && (specifiedChainId as string).startsWith("0x")) - specifiedChainId = parseInt(specifiedChainId, 16) + if (isHex(specifiedChainId)) specifiedChainId = hexToNumber(specifiedChainId) // checks that the request targets currently selected network if (specifiedChainId && Number(site.ethChainId) !== Number(specifiedChainId)) @@ -699,7 +698,7 @@ export class EthTabsHandler extends TabsHandler { private async ethRequest( id: string, url: string, - request: EthRequestArgsViem, + request: EthRequestArgs, port: Port ): Promise { if ( @@ -748,25 +747,6 @@ export class EthTabsHandler extends TabsHandler { // legacy, but still used by etherscan prior calling eth_watchAsset return (await this.getChainId(url)).toString() - // TODO check if this is ever called, it doesn't exist in viem's public rpc schema - // case "estimateGas": { - // const { params } = request as EthRequestArguments<"estimateGas"> - // console.log("estimateGas", params) - // if (params[1] && params[1] !== "latest") { - // throw new EthProviderRpcError( - // "estimateGas does not support blockTag", - // ETH_ERROR_EIP1474_INVALID_PARAMS - // ) - // } - - // await this.checkAccountAuthorised(url, params[0].from) - - // const req = ethers.providers.JsonRpcProvider.hexlifyTransaction(params[0]) - // const provider = await this.getProvider(url) - // const result = await provider.estimateGas(req) - // return result.toHexString() - // } - case "personal_sign": case "eth_signTypedData": case "eth_signTypedData_v1": @@ -823,7 +803,7 @@ export class EthTabsHandler extends TabsHandler { case "pub(eth.request)": { try { - return await this.ethRequest(id, url, request as EthRequestArgsViem, port) + return await this.ethRequest(id, url, request as EthRequestArgs, port) } catch (err) { if (err instanceof EthProviderRpcError) throw err // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/apps/extension/src/core/domains/ethereum/helpers.ts b/apps/extension/src/core/domains/ethereum/helpers.ts index ef97ffe2e6..5b24522649 100644 --- a/apps/extension/src/core/domains/ethereum/helpers.ts +++ b/apps/extension/src/core/domains/ethereum/helpers.ts @@ -41,38 +41,6 @@ export const getEthLedgerDerivationPath = (type: LedgerEthDerivationPathType, in return getDerivationPathFromPattern(index, DERIVATION_PATHS_PATTERNS[type]) } -// export const getEthTransferTransactionBaseOld = async ( -// evmNetworkId: EvmNetworkId, -// from: string, -// to: string, -// token: Token, -// planck: string -// ): Promise => { -// assert(evmNetworkId, "evmNetworkId is required") -// assert(token, "token is required") -// assert(planck, "planck is required") -// assert(isEthereumAddress(from), "from address is required") -// assert(isEthereumAddress(to), "to address is required") - -// let tx: ethers.providers.TransactionRequest - -// if (token.type === "evm-native") { -// tx = { -// value: ethers.BigNumber.from(planck), -// to: ethers.utils.getAddress(to), -// } -// } else if (token.type === "evm-erc20") { -// const contract = new ethers.Contract(token.contractAddress, erc20Abi) -// tx = await contract.populateTransaction["transfer"](to, ethers.BigNumber.from(planck)) -// } else throw new Error(`Invalid token type ${token.type} - token ${token.id}`) - -// return { -// chainId: parseInt(evmNetworkId, 10), -// from, -// ...tx, -// } -// } - export const getEthTransferTransactionBase = async ( evmNetworkId: EvmNetworkId, from: EvmAddress, @@ -218,29 +186,6 @@ export const parseRpcTransactionRequestBase = ( return txBase } -/** @deprecated */ -export const rebuildGasSettings = ( - gasSettings: EthGasSettings -): EthGasSettings => { - return gasSettings.type === "eip1559" - ? { - type: "eip1559", - gas: isHex(gasSettings.gas) ? hexToBigInt(gasSettings.gas) : gasSettings.gas, - maxFeePerGas: isHex(gasSettings.maxFeePerGas) - ? hexToBigInt(gasSettings.maxFeePerGas) - : gasSettings.maxFeePerGas, - maxPriorityFeePerGas: isHex(gasSettings.maxPriorityFeePerGas) - ? hexToBigInt(gasSettings.maxPriorityFeePerGas) - : gasSettings.maxPriorityFeePerGas, - } - : { - type: gasSettings.type, - gas: isHex(gasSettings.gas) ? hexToBigInt(gasSettings.gas) : gasSettings.gas, - gasPrice: isHex(gasSettings.gasPrice) - ? hexToBigInt(gasSettings.gasPrice) - : gasSettings.gasPrice, - } -} const TX_GAS_LIMIT_DEFAULT = 250000n const TX_GAS_LIMIT_MIN = 21000n @@ -348,39 +293,11 @@ export const prepareTransaction = ( txBase: TransactionRequestBase, gasSettings: EthGasSettings, nonce: number -): TransactionRequest => { - return { - ...txBase, - ...gasSettings, - nonce, - } - - // const { - // chainId, - // data, - // from, - // to, - // value = BigNumber.from(0), - // accessList, - // ccipReadEnabled, - // customData, - // } = tx - - // const transaction: ethers.providers.TransactionRequest = { - // chainId, - // from, - // to, - // value, - // nonce: BigNumber.from(nonce), - // data, - // ...gasSettings, - // } - // if (accessList) transaction.accessList = accessList - // if (customData) transaction.customData = customData - // if (ccipReadEnabled !== undefined) transaction.ccipReadEnabled = ccipReadEnabled - - // return transaction -} +): TransactionRequest => ({ + ...txBase, + ...gasSettings, + nonce, +}) const testNoScriptTag = (text?: string) => !text?.toLowerCase().includes(" = { readonly params: EthRequestParams } -export type EthRequestArgsViem = EIP1193Parameters +// TODO yeet ? +export type EthRequestArgs = EIP1193Parameters export type EthRequestSignArguments = EthRequestArguments< | "personal_sign" | "eth_signTypedData" diff --git a/apps/extension/src/core/domains/signing/types.ts b/apps/extension/src/core/domains/signing/types.ts index 6260697d63..eef49f62a2 100644 --- a/apps/extension/src/core/domains/signing/types.ts +++ b/apps/extension/src/core/domains/signing/types.ts @@ -155,7 +155,7 @@ export type EthTransactionDetails = { estimatedGas: bigint gasPrice: bigint estimatedFee: bigint - maxFee: bigint // TODO yeet ! + maxFee: bigint baseFeePerGas?: bigint | null baseFeeTrend?: EthBaseFeeTrend } diff --git a/apps/extension/src/core/domains/transfers/handler.ts b/apps/extension/src/core/domains/transfers/handler.ts index 36a1ee2186..daa2da08d4 100644 --- a/apps/extension/src/core/domains/transfers/handler.ts +++ b/apps/extension/src/core/domains/transfers/handler.ts @@ -1,4 +1,8 @@ -import { getEthTransferTransactionBase, parseGasSettings } from "@core/domains/ethereum/helpers" +import { + getEthTransferTransactionBase, + parseGasSettings, + prepareTransaction, +} from "@core/domains/ethereum/helpers" import { getTransactionCount, incrementTransactionCount, @@ -26,6 +30,7 @@ import * as Sentry from "@sentry/browser" import { isEthereumAddress, planckToTokens } from "@talismn/util" import { privateKeyToAccount } from "viem/accounts" +import { serializeTransactionRequest } from "../ethereum/helpers" import { transferAnalytics } from "./helpers" export default class AssetTransferHandler extends ExtensionHandler { @@ -213,6 +218,9 @@ export default class AssetTransferHandler extends ExtensionHandler { const parsedGasSettings = parseGasSettings(gasSettings) const nonce = await getTransactionCount(fromAddress, evmNetworkId) + const transaction = prepareTransaction(transfer, parsedGasSettings, nonce) + const unsigned = serializeTransactionRequest(transaction) + const result = await getPairForAddressSafely(fromAddress, async (pair) => { const client = await chainConnectorEvm.getWalletClientForEvmNetwork(evmNetworkId) assert(client, "Missing client for chain " + evmNetworkId) @@ -226,9 +234,7 @@ export default class AssetTransferHandler extends ExtensionHandler { const hash = await client.sendTransaction({ chain: client.chain, account, - ...transfer, - ...parsedGasSettings, - nonce, + ...transaction, }) incrementTransactionCount(fromAddress, evmNetworkId) @@ -237,9 +243,10 @@ export default class AssetTransferHandler extends ExtensionHandler { }) if (result.ok) { - // watchEthereumTransaction(evmNetworkId, result.val.hash, transaction, { - // transferInfo: { tokenId: token.id, value: amount, to: toAddress }, - // }) + // TODO test this + watchEthereumTransaction(evmNetworkId, result.val.hash, unsigned, { + transferInfo: { tokenId: token.id, value: amount, to: toAddress }, + }) transferAnalytics({ network: { evmNetworkId }, diff --git a/apps/extension/src/core/handlers/index.ts b/apps/extension/src/core/handlers/index.ts index c5de9fc6e6..16e2ee7806 100644 --- a/apps/extension/src/core/handlers/index.ts +++ b/apps/extension/src/core/handlers/index.ts @@ -105,26 +105,19 @@ const talismanHandler = ( // only send message back to port if it's still connected, unfortunately this check is not reliable in all browsers if (port) { try { - switch (message) { - case "pub(eth.request)": - case "pri(eth.request)": { - const evmError = getEvmErrorCause(error) - port.postMessage({ - id, - error: cleanupEvmErrorMessage( - (message === "pri(eth.request)" && evmError.details) || - (evmError.shortMessage ?? evmError.message ?? "Unknown error") - ), - code: error.code, - isEthProviderRpcError: true, - }) - break - } - default: { - port.postMessage({ id, error: error.message }) - break - } - } + if (["pub(eth.request)", "pri(eth.request)"].includes(message)) { + const evmError = getEvmErrorCause(error) + // TODO test on dapps if behavior is different without data + port.postMessage({ + id, + error: cleanupEvmErrorMessage( + (message === "pri(eth.request)" && evmError.details) || + (evmError.shortMessage ?? evmError.message ?? "Unknown error") + ), + code: error.code, + isEthProviderRpcError: true, + }) + } else port.postMessage({ id, error: error.message }) } catch (caughtError) { /** * no-op diff --git a/apps/extension/src/ui/domains/Sign/SignDcentEthereum.tsx b/apps/extension/src/ui/domains/Sign/SignDcentEthereum.tsx index 2c89480ae7..136f004103 100644 --- a/apps/extension/src/ui/domains/Sign/SignDcentEthereum.tsx +++ b/apps/extension/src/ui/domains/Sign/SignDcentEthereum.tsx @@ -57,8 +57,8 @@ const signWithDcent = async ( // Note : most fields can't be undefined const args = [ DcentWebConnector.coinType.ETHEREUM, - (baseTx.nonce ?? 0).toString(), // ethers.BigNumber.from(nonce).toString(), - baseTx.type === "eip1559" ? undefined : baseTx.gasPrice?.toString(), // type === 2 ? undefined : gasPrice?.toString(), + (baseTx.nonce ?? 0).toString(), + baseTx.type === "eip1559" ? undefined : baseTx.gasPrice?.toString(), baseTx.gas?.toString() ?? "21000", baseTx.to, baseTx.value?.toString() ?? "0", From 6dc48cefb7c80c153d7d6558817aa770ef5bff31 Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Mon, 6 Nov 2023 12:22:31 +0900 Subject: [PATCH 38/50] chore: remove acala provider --- apps/extension/webpack/webpack.common.js | 4 - packages/chain-connector-evm/package.json | 2 - .../src/getTransportForEvmNetwork.ts | 20 +-- packages/chain-connector-evm/src/util.ts | 84 ----------- yarn.lock | 131 +----------------- 5 files changed, 4 insertions(+), 237 deletions(-) diff --git a/apps/extension/webpack/webpack.common.js b/apps/extension/webpack/webpack.common.js index 540ab8cacf..869f680793 100644 --- a/apps/extension/webpack/webpack.common.js +++ b/apps/extension/webpack/webpack.common.js @@ -28,10 +28,6 @@ const config = (env) => ({ "@substrate/txwrapper-core", "@talismn/chaindata-provider-extension", "@metamask/eth-sig-util", - "@acala-network/types", - "@acala-network/eth-providers", - "@acala-network/eth-transactions", - "@acala-network/api-derive", ], // Wallet injected scripts diff --git a/packages/chain-connector-evm/package.json b/packages/chain-connector-evm/package.json index a0492bb477..be119d921c 100644 --- a/packages/chain-connector-evm/package.json +++ b/packages/chain-connector-evm/package.json @@ -26,8 +26,6 @@ "clean": "rm -rf dist && rm -rf .turbo rm -rf node_modules" }, "dependencies": { - "@acala-network/api": "^6.0.0", - "@acala-network/eth-providers": "^2.7.8", "@talismn/chaindata-provider": "workspace:*", "@talismn/util": "workspace:*", "anylogger": "^1.0.11", diff --git a/packages/chain-connector-evm/src/getTransportForEvmNetwork.ts b/packages/chain-connector-evm/src/getTransportForEvmNetwork.ts index 475f42f1ce..981d15337d 100644 --- a/packages/chain-connector-evm/src/getTransportForEvmNetwork.ts +++ b/packages/chain-connector-evm/src/getTransportForEvmNetwork.ts @@ -1,7 +1,7 @@ import { EvmNetwork } from "@talismn/chaindata-provider" -import { custom, fallback, http } from "viem" +import { fallback, http } from "viem" -import { AcalaRpcProvider, addOnfinalityApiKey, isAcalaNetwork } from "./util" +import { addOnfinalityApiKey } from "./util" const HTTP_BATCH_WAIT = 25 const HTTP_BATCH_SIZE = 30 @@ -16,22 +16,6 @@ export const getTransportForEvmNetwork = ( ) => { if (!evmNetwork.rpcs?.length) throw new Error("No RPCs found for EVM network") - const chainId = Number(evmNetwork.id) - // TODO use a proper viem implementation (this only supports 1 RPC url) - if (isAcalaNetwork(chainId)) { - const ethersProvider = new AcalaRpcProvider(evmNetwork.rpcs[0].url, { - chainId, - name: evmNetwork.name ?? `EVM Chain ${evmNetwork.id}`, - }) - - return custom({ - request: (request) => { - const { method, params } = request as { method: string; params: unknown[] } - return ethersProvider.send(method, params) - }, - }) - } - return fallback( evmNetwork.rpcs.map((rpc) => http(addOnfinalityApiKey(rpc.url, options.onFinalityApiKey), { diff --git a/packages/chain-connector-evm/src/util.ts b/packages/chain-connector-evm/src/util.ts index 8fcc4c1750..ec7e4a7a1c 100644 --- a/packages/chain-connector-evm/src/util.ts +++ b/packages/chain-connector-evm/src/util.ts @@ -1,7 +1,3 @@ -import { AcalaJsonRpcProvider } from "@acala-network/eth-providers" - -import { ACALA_NETWORK_IDS } from "./constants" - /** * Helper function to add our onfinality api key to a public onfinality RPC url. */ @@ -19,83 +15,3 @@ export const addOnfinalityApiKey = (rpcUrl: string, onfinalityApiKey?: string) = `https://$1.api.onfinality.io/rpc?apikey=${onfinalityApiKey}` ) } - -// TODO yeet everything below - -// export const isHealthyRpc = async (url: string, chainId: number) => { -// try { -// // StaticJsonRpcProvider is better suited for this as it will not do health check requests on it's own -// const provider = new ethers.providers.StaticJsonRpcProvider(url, { -// chainId, -// name: `EVM Network ${chainId}`, -// }) - -// // check that RPC responds in time -// const rpcChainId = await Promise.race([ -// provider.send("eth_chainId", []), -// throwAfter(RPC_HEALTHCHECK_TIMEOUT, "timeout"), -// ]) - -// // with expected chain id -// return parseInt(rpcChainId, 16) === chainId -// } catch (err) { -// log.error("Unhealthy EVM RPC %s", url, { err }) -// return false -// } -// } - -export const isAcalaNetwork = (chainId: number) => ACALA_NETWORK_IDS.includes(chainId) - -// export const getHealthyRpc = async (rpcUrls: string[], network: ethers.providers.Network) => { -// for (const rpcUrl of rpcUrls) if (await isHealthyRpc(rpcUrl, network.chainId)) return rpcUrl - -// log.warn("No healthy RPC for EVM network %s (%d)", network.name, network.chainId) -// return null -// } - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const isUnhealthyRpcError = (err: any) => { - // expected errors that are not related to RPC health - // ex : throw revert on a transaction call that fails - if (err?.message === "BATCH_FAILED") return false - if (err?.reason === "processing response error") return false - - // if unknown, assume RPC is unhealthy - return true -} - -// export class StandardRpcProvider extends ethers.providers.JsonRpcProvider { -// async send(method: string, params: Array): Promise { -// try { -// return await super.send(method, params) -// } catch (err) { -// // emit error so rpc manager considers this rpc unhealthy -// if (isUnhealthyRpcError(err)) this.emit("error", err) -// throw err -// } -// } -// } - -export class AcalaRpcProvider extends AcalaJsonRpcProvider { - async send(method: string, params: Array): Promise { - try { - return await super.send(method, params) - } catch (err) { - // emit error so rpc manager considers this rpc unhealthy - if (isUnhealthyRpcError(err)) this.emit("error", err) - throw err - } - } -} - -// export class BatchRpcProvider extends EvmJsonRpcBatchProvider { -// async send(method: string, params: Array): Promise { -// try { -// return await super.send(method, params) -// } catch (err) { -// // emit error so rpc manager considers this rpc unhealthy -// if (isUnhealthyRpcError(err)) this.emit("error", err) -// throw err -// } -// } -// } diff --git a/yarn.lock b/yarn.lock index 05f930bd33..cf2367c5be 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12,65 +12,6 @@ __metadata: languageName: node linkType: hard -"@acala-network/api-derive@npm:6.0.0": - version: 6.0.0 - resolution: "@acala-network/api-derive@npm:6.0.0" - dependencies: - "@acala-network/types": 6.0.0 - peerDependencies: - "@polkadot/api": ^10.9.1 - checksum: 8656e7b65bfef498cac780d7beb0d3e43a59b5343d09a8b28ce7ec3537494b7477b0162a01b4f6c53260cfa7fa63c59e311b3a254020da6a104832826e6099ed - languageName: node - linkType: hard - -"@acala-network/api@npm:^6.0.0": - version: 6.0.0 - resolution: "@acala-network/api@npm:6.0.0" - dependencies: - "@acala-network/api-derive": 6.0.0 - "@acala-network/types": 6.0.0 - peerDependencies: - "@polkadot/api": ^10.9.1 - checksum: 5ec48f880b80ad7a9acf070dbd90938e6aae8248407a283c19560e80b21179fae35f4e4d828d9f1ac300be4e119800ece834df2c96f02dbc35e73be1e83da8ad - languageName: node - linkType: hard - -"@acala-network/contracts@npm:4.3.4": - version: 4.3.4 - resolution: "@acala-network/contracts@npm:4.3.4" - checksum: 65cfa0dbd2aba323168232385c7651ccd4ae65262f8032780998d4a7d96b0de7066a13390628b901f05dcfc20f134dc549c8aada3ad452d9aa9b5bc673ca6ae0 - languageName: node - linkType: hard - -"@acala-network/eth-providers@npm:^2.7.8": - version: 2.7.8 - resolution: "@acala-network/eth-providers@npm:2.7.8" - dependencies: - "@acala-network/contracts": 4.3.4 - "@acala-network/eth-transactions": 2.7.8 - bn.js: ~5.2.0 - ethers: ~5.7.0 - graphql: ~16.0.1 - graphql-request: ~3.6.1 - lru-cache: ~7.8.2 - peerDependencies: - "@acala-network/api": ~6.0.0 - "@polkadot/api": ^10.9.1 - checksum: 6aee2f8322068883078da4a8909964a930b652c222ca5a0063b81797e80d7ee3b8e78b06de05b8decd86d9d91e7af4973b10ae0bde75697efff4a6ad7fc3fbb9 - languageName: node - linkType: hard - -"@acala-network/eth-transactions@npm:2.7.8": - version: 2.7.8 - resolution: "@acala-network/eth-transactions@npm:2.7.8" - dependencies: - ethers: ~5.7.0 - peerDependencies: - "@polkadot/util-crypto": ^12.4.2 - checksum: b28badf1fb1141532d2f00bf83062f937147e11972291f8752660fb1d3657e62cc2a97ef16ee7fd26490ff6fcb1a728370a78ec68714d25943962f8fb29be4e7 - languageName: node - linkType: hard - "@acala-network/type-definitions@npm:5.1.1": version: 5.1.1 resolution: "@acala-network/type-definitions@npm:5.1.1" @@ -80,15 +21,6 @@ __metadata: languageName: node linkType: hard -"@acala-network/types@npm:6.0.0": - version: 6.0.0 - resolution: "@acala-network/types@npm:6.0.0" - peerDependencies: - "@polkadot/api": ^10.9.1 - checksum: d5187f6eccf46e094cda7a69e23c6fd1f8221da4f1e7d30964c9769bb40795e0aa25c5a400c51b3ae4a2bb9dcc47996738febe08b7b4f2378ece7aa14461a74d - languageName: node - linkType: hard - "@adobe/css-tools@npm:^4.0.1": version: 4.0.1 resolution: "@adobe/css-tools@npm:4.0.1" @@ -8178,8 +8110,6 @@ __metadata: version: 0.0.0-use.local resolution: "@talismn/chain-connector-evm@workspace:packages/chain-connector-evm" dependencies: - "@acala-network/api": ^6.0.0 - "@acala-network/eth-providers": ^2.7.8 "@talismn/chaindata-provider": "workspace:*" "@talismn/eslint-config": "workspace:*" "@talismn/tsconfig": "workspace:*" @@ -11725,7 +11655,7 @@ __metadata: languageName: node linkType: hard -"bn.js@npm:^5.0.0, bn.js@npm:^5.1.1, bn.js@npm:^5.2.0, bn.js@npm:^5.2.1, bn.js@npm:~5.2.0": +"bn.js@npm:^5.0.0, bn.js@npm:^5.1.1, bn.js@npm:^5.2.0, bn.js@npm:^5.2.1": version: 5.2.1 resolution: "bn.js@npm:5.2.1" checksum: 3dd8c8d38055fedfa95c1d5fc3c99f8dd547b36287b37768db0abab3c239711f88ff58d18d155dd8ad902b0b0cee973747b7ae20ea12a09473272b0201c9edd3 @@ -13194,15 +13124,6 @@ __metadata: languageName: node linkType: hard -"cross-fetch@npm:^3.0.6": - version: 3.1.8 - resolution: "cross-fetch@npm:3.1.8" - dependencies: - node-fetch: ^2.6.12 - checksum: 78f993fa099eaaa041122ab037fe9503ecbbcb9daef234d1d2e0b9230a983f64d645d088c464e21a247b825a08dc444a6e7064adfa93536d3a9454b4745b3632 - languageName: node - linkType: hard - "cross-spawn@npm:^5.1.0": version: 5.1.0 resolution: "cross-spawn@npm:5.1.0" @@ -15224,7 +15145,7 @@ __metadata: languageName: node linkType: hard -"ethers@npm:5.7.2, ethers@npm:^5.7.1, ethers@npm:^5.7.2, ethers@npm:~5.7.0": +"ethers@npm:5.7.2, ethers@npm:^5.7.1, ethers@npm:^5.7.2": version: 5.7.2 resolution: "ethers@npm:5.7.2" dependencies: @@ -15638,13 +15559,6 @@ __metadata: languageName: node linkType: hard -"extract-files@npm:^9.0.0": - version: 9.0.0 - resolution: "extract-files@npm:9.0.0" - checksum: c31781d090f8d8f62cc541f1023b39ea863f24bd6fb3d4011922d71cbded70cef8191f2b70b43ec6cb5c5907cdad1dc5e9f29f78228936c10adc239091d8ab64 - languageName: node - linkType: hard - "extsprintf@npm:1.3.0": version: 1.3.0 resolution: "extsprintf@npm:1.3.0" @@ -16742,19 +16656,6 @@ __metadata: languageName: node linkType: hard -"graphql-request@npm:~3.6.1": - version: 3.6.1 - resolution: "graphql-request@npm:3.6.1" - dependencies: - cross-fetch: ^3.0.6 - extract-files: ^9.0.0 - form-data: ^3.0.0 - peerDependencies: - graphql: 14.x || 15.x - checksum: 15e98c29760fca8ce230459dd2448a56eca54f7fcce9cb5f3fd82c9af1e31bfb5a85b67ef405479d9a45acae4d2a1f278ebda6ca1950cad9a5b40dc215b82dd3 - languageName: node - linkType: hard - "graphql-tag@npm:^2.11.0, graphql-tag@npm:^2.12.6": version: 2.12.6 resolution: "graphql-tag@npm:2.12.6" @@ -16782,13 +16683,6 @@ __metadata: languageName: node linkType: hard -"graphql@npm:~16.0.1": - version: 16.0.1 - resolution: "graphql@npm:16.0.1" - checksum: e2fbddc78ac93b84af5b1871cdced5ce796102373b91f888983e8bae26856788833aa641f5da15b9e4da05cef2edcc82b744a86a27e7e1ef6c9ce422571ab16c - languageName: node - linkType: hard - "growly@npm:^1.3.0": version: 1.3.0 resolution: "growly@npm:1.3.0" @@ -20700,13 +20594,6 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:~7.8.2": - version: 7.8.2 - resolution: "lru-cache@npm:7.8.2" - checksum: 58b5d9881581f9db23ebd9491a84e9268a1841bafd0e5dcb5492589bffffaa7cf3e07acb197a9bf98477eb6c55eb5f21a0176a63bc69bd39c5a531d93c61b652 - languageName: node - linkType: hard - "lru-queue@npm:^0.1.0": version: 0.1.0 resolution: "lru-queue@npm:0.1.0" @@ -21590,20 +21477,6 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:^2.6.12": - version: 2.7.0 - resolution: "node-fetch@npm:2.7.0" - dependencies: - whatwg-url: ^5.0.0 - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - checksum: d76d2f5edb451a3f05b15115ec89fc6be39de37c6089f1b6368df03b91e1633fd379a7e01b7ab05089a25034b2023d959b47e59759cb38d88341b2459e89d6e5 - languageName: node - linkType: hard - "node-fetch@npm:^3.3.1": version: 3.3.1 resolution: "node-fetch@npm:3.3.1" From 310b07f914d929cc31d4191e9efcea7b6fb3a1a7 Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Mon, 6 Nov 2023 12:34:05 +0900 Subject: [PATCH 39/50] chore: yeet ethers --- apps/extension/package.json | 1 - packages/chain-connector-evm/package.json | 1 - yarn.lock | 2 -- 3 files changed, 4 deletions(-) diff --git a/apps/extension/package.json b/apps/extension/package.json index 40fac5d9fb..c7e964ec71 100644 --- a/apps/extension/package.json +++ b/apps/extension/package.json @@ -93,7 +93,6 @@ "dotenv-webpack": "^7.1.1", "downshift": "^6.1.12", "eth-phishing-detect": "latest", - "ethers": "5.7.2", "fork-ts-checker-notifier-webpack-plugin": "^6.0.0", "fork-ts-checker-webpack-plugin": "^7.2.14", "framer-motion": "10.12.18", diff --git a/packages/chain-connector-evm/package.json b/packages/chain-connector-evm/package.json index be119d921c..39e21a7989 100644 --- a/packages/chain-connector-evm/package.json +++ b/packages/chain-connector-evm/package.json @@ -29,7 +29,6 @@ "@talismn/chaindata-provider": "workspace:*", "@talismn/util": "workspace:*", "anylogger": "^1.0.11", - "ethers": "5.7.2", "lodash": "4.17.21", "viem": "^1.18.3" }, diff --git a/yarn.lock b/yarn.lock index cf2367c5be..1cbe0ad63a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8118,7 +8118,6 @@ __metadata: "@types/lodash": ^4.14.180 anylogger: ^1.0.11 eslint: ^8.52.0 - ethers: 5.7.2 jest: ^28.1.0 lodash: 4.17.21 ts-jest: ^28.0.2 @@ -15468,7 +15467,6 @@ __metadata: eslint: ^8.52.0 eslint-webpack-plugin: ^4.0.1 eth-phishing-detect: latest - ethers: 5.7.2 fake-indexeddb: 4.0.1 fork-ts-checker-notifier-webpack-plugin: ^6.0.0 fork-ts-checker-webpack-plugin: ^7.2.14 From 8546052df702b8e1e7c6c3ca24979141c0bda13c Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Mon, 6 Nov 2023 14:12:24 +0900 Subject: [PATCH 40/50] fix: send funds amount check --- apps/extension/src/core/domains/ethereum/helpers.ts | 2 +- apps/extension/src/core/log/index.ts | 11 +++++++++++ apps/extension/src/core/util/getFeeHistoryAnalysis.ts | 8 -------- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/apps/extension/src/core/domains/ethereum/helpers.ts b/apps/extension/src/core/domains/ethereum/helpers.ts index 5b24522649..5bfbef85ac 100644 --- a/apps/extension/src/core/domains/ethereum/helpers.ts +++ b/apps/extension/src/core/domains/ethereum/helpers.ts @@ -50,7 +50,7 @@ export const getEthTransferTransactionBase = async ( ) => { assert(evmNetworkId, "evmNetworkId is required") assert(token, "token is required") - assert(planck, "planck is required") + assert(typeof planck === "bigint", "planck is required") assert(isAddress(from), "from address is required") assert(isAddress(to), "to address is required") diff --git a/apps/extension/src/core/log/index.ts b/apps/extension/src/core/log/index.ts index 88987aee58..cae061a560 100644 --- a/apps/extension/src/core/log/index.ts +++ b/apps/extension/src/core/log/index.ts @@ -10,4 +10,15 @@ export const log = { warn: (message: any, ...args: any[]) => DEBUG && console.warn(message, ...args), log: (message: any, ...args: any[]) => DEBUG && console.log(message, ...args), debug: (message: any, ...args: any[]) => DEBUG && console.debug(message, ...args), + + timer: (label: string) => { + if (!DEBUG) return () => {} + + const timeKey = `${label} (${crypto.randomUUID()})` + console.time(timeKey) + + return () => { + console.timeEnd(timeKey) + } + }, } diff --git a/apps/extension/src/core/util/getFeeHistoryAnalysis.ts b/apps/extension/src/core/util/getFeeHistoryAnalysis.ts index 1160a03e95..403c692e98 100644 --- a/apps/extension/src/core/util/getFeeHistoryAnalysis.ts +++ b/apps/extension/src/core/util/getFeeHistoryAnalysis.ts @@ -15,13 +15,6 @@ const DEFAULT_ETH_PRIORITY_OPTIONS: EthBasePriorityOptionsEip1559 = { high: parseGwei("1.7"), } -// type FeeHistory = { -// oldestBlock: bigint -// baseFeePerGas: bigint[] -// gasUsedRatio: (number)[] // can have null values (ex astar) -// reward?: BigNumber[][] // TODO find network that doesn't return this property, for testing -// } - export type FeeHistoryAnalysis = { maxPriorityPerGasOptions: EthBasePriorityOptionsEip1559 avgGasUsedRatio: number @@ -121,7 +114,6 @@ export const getFeeHistoryAnalysis = async ( ) log.log("=========================================") } - return result } catch (err) { Sentry.captureException(err) From c3a37f0d935b0e88b2c506d11152bae4b282819c Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Mon, 6 Nov 2023 16:39:40 +0900 Subject: [PATCH 41/50] fix: pass-through revert data --- .../src/core/domains/ethereum/errors.ts | 5 +++-- .../src/core/domains/ethereum/handler.tabs.ts | 16 +++++++++++---- .../src/core/domains/ethereum/types.ts | 1 + apps/extension/src/core/handlers/index.ts | 1 + .../src/core/injectEth/EthProviderRpcError.ts | 20 +++++++++++++++++++ .../injectEth/getInjectableEvmProvider.ts | 5 +++-- apps/extension/src/core/injectEth/types.ts | 16 --------------- .../extension/src/core/libs/MessageService.ts | 8 ++++---- 8 files changed, 44 insertions(+), 28 deletions(-) diff --git a/apps/extension/src/core/domains/ethereum/errors.ts b/apps/extension/src/core/domains/ethereum/errors.ts index 709b39782c..20da94b185 100644 --- a/apps/extension/src/core/domains/ethereum/errors.ts +++ b/apps/extension/src/core/domains/ethereum/errors.ts @@ -36,8 +36,9 @@ export const getErrorLabelFromCode = (code: number) => { } } -export const getEvmErrorCause = (error: AnyEvmError): AnyEvmError => { - return error.cause ? getEvmErrorCause(error.cause) : error +export const getEvmErrorCause = (err: unknown): AnyEvmError => { + const error = err as AnyEvmError + return error?.cause ? getEvmErrorCause(error.cause) : error } // turns errors into short and human readable message. diff --git a/apps/extension/src/core/domains/ethereum/handler.tabs.ts b/apps/extension/src/core/domains/ethereum/handler.tabs.ts index ec22231370..907534810e 100644 --- a/apps/extension/src/core/domains/ethereum/handler.tabs.ts +++ b/apps/extension/src/core/domains/ethereum/handler.tabs.ts @@ -52,6 +52,7 @@ import { ERROR_DUPLICATE_AUTH_REQUEST_MESSAGE, requestAuthoriseSite, } from "../sitesAuthorised/requests" +import { getEvmErrorCause } from "./errors" import { getErc20TokenId, isValidAddEthereumRequestParam, @@ -61,6 +62,7 @@ import { } from "./helpers" import { requestAddNetwork, requestWatchAsset } from "./requests" import { + AnyEvmError, EthProviderMessage, EthRequestArgs, EthRequestArguments, @@ -805,14 +807,20 @@ export class EthTabsHandler extends TabsHandler { try { return await this.ethRequest(id, url, request as EthRequestArgs, port) } catch (err) { + // error may already be formatted by our handler if (err instanceof EthProviderRpcError) throw err - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const { code, shortMessage, message } = err as RpcError - throw new EthProviderRpcError( + + const { code, message, shortMessage, details } = err as RpcError + const cause = getEvmErrorCause(err as AnyEvmError) + + const myError = new EthProviderRpcError( shortMessage ?? message ?? "Internal error", code ?? ETH_ERROR_EIP1474_INTERNAL_ERROR, - shortMessage ? message : undefined + // assume if data property is present, it's an EVM revert => dapp expects that underlying error object + cause.data ? cause : details ) + + throw myError } } diff --git a/apps/extension/src/core/domains/ethereum/types.ts b/apps/extension/src/core/domains/ethereum/types.ts index b5e2def00c..f22c0f7059 100644 --- a/apps/extension/src/core/domains/ethereum/types.ts +++ b/apps/extension/src/core/domains/ethereum/types.ts @@ -23,6 +23,7 @@ export type AnyEvmError = { details?: string code?: number cause?: AnyEvmError + data?: unknown } export type { diff --git a/apps/extension/src/core/handlers/index.ts b/apps/extension/src/core/handlers/index.ts index 16e2ee7806..4744891f41 100644 --- a/apps/extension/src/core/handlers/index.ts +++ b/apps/extension/src/core/handlers/index.ts @@ -115,6 +115,7 @@ const talismanHandler = ( (evmError.shortMessage ?? evmError.message ?? "Unknown error") ), code: error.code, + rpcData: evmError.data, // don't use "data" as property name or viem will interpret it differently isEthProviderRpcError: true, }) } else port.postMessage({ id, error: error.message }) diff --git a/apps/extension/src/core/injectEth/EthProviderRpcError.ts b/apps/extension/src/core/injectEth/EthProviderRpcError.ts index 00456e2b7d..1e61607838 100644 --- a/apps/extension/src/core/injectEth/EthProviderRpcError.ts +++ b/apps/extension/src/core/injectEth/EthProviderRpcError.ts @@ -46,3 +46,23 @@ export class EthProviderRpcError extends Error { Object.setPrototypeOf(this, EthProviderRpcError.prototype) } } + +/** + * Wrapped error so viem doesn't see the "data" property + */ +export class WrappedEthProviderRpcError extends Error { + code: number + message: string + rpcData?: unknown //hex encoded error or underlying error object + + constructor(message: string, code: number, rpcData?: unknown) { + super(message) + + this.code = code + this.message = message + this.rpcData = rpcData + + // Set the prototype explicitly. + Object.setPrototypeOf(this, WrappedEthProviderRpcError.prototype) + } +} diff --git a/apps/extension/src/core/injectEth/getInjectableEvmProvider.ts b/apps/extension/src/core/injectEth/getInjectableEvmProvider.ts index 6dd15851f3..191aa8f8e9 100644 --- a/apps/extension/src/core/injectEth/getInjectableEvmProvider.ts +++ b/apps/extension/src/core/injectEth/getInjectableEvmProvider.ts @@ -8,6 +8,7 @@ import { ETH_ERROR_EIP1474_INTERNAL_ERROR, ETH_ERROR_EIP1993_USER_REJECTED, EthProviderRpcError, + WrappedEthProviderRpcError, } from "./EthProviderRpcError" interface RequestArguments { @@ -150,7 +151,7 @@ export const getInjectableEvmProvider = (sendRequest: SendRequest) => { } catch (err) { log.debug("[talismanEth.request] error on %s", args.method, { err }) - const { code, message, data } = err as EthProviderRpcError + const { code, message, rpcData } = err as WrappedEthProviderRpcError if (code > 0) { // standard wallet error (user rejected, etc.) @@ -164,7 +165,7 @@ export const getInjectableEvmProvider = (sendRequest: SendRequest) => { throw new EthProviderRpcError( "Internal JSON-RPC error.", ETH_ERROR_EIP1474_INTERNAL_ERROR, - { code, message, data } + rpcData ) } } diff --git a/apps/extension/src/core/injectEth/types.ts b/apps/extension/src/core/injectEth/types.ts index 9e45cb008f..51e9f599f2 100644 --- a/apps/extension/src/core/injectEth/types.ts +++ b/apps/extension/src/core/injectEth/types.ts @@ -1,21 +1,5 @@ import EventEmitter from "events" -// export type EthSubscriptionId = string - -// export interface EthSubscriptionData { -// readonly subscription: EthSubscriptionId -// readonly result: unknown -// } - -// export interface EthSubscriptionMessage extends EthProviderMessage { -// readonly type: "eth_subscription" -// readonly data: EthSubscriptionData -// } - -// export interface ProviderConnectInfo { -// readonly chainId: string -// } - export interface AnyEthRequest { readonly method: string readonly params?: readonly unknown[] | object diff --git a/apps/extension/src/core/libs/MessageService.ts b/apps/extension/src/core/libs/MessageService.ts index ecfebc67ba..9de843bb83 100644 --- a/apps/extension/src/core/libs/MessageService.ts +++ b/apps/extension/src/core/libs/MessageService.ts @@ -5,7 +5,7 @@ import { ETH_ERROR_EIP1474_INTERNAL_ERROR, - EthProviderRpcError, + WrappedEthProviderRpcError, } from "@core/injectEth/EthProviderRpcError" import { log } from "@core/log" import type { @@ -132,7 +132,7 @@ export default class MessageService { data: TransportResponseMessage & { subscription?: string code?: number - data?: unknown + rpcData?: unknown isEthProviderRpcError?: boolean } ): void { @@ -159,10 +159,10 @@ export default class MessageService { else if (data.error) { if (data.isEthProviderRpcError) { handler.reject( - new EthProviderRpcError( + new WrappedEthProviderRpcError( data.error, data.code ?? ETH_ERROR_EIP1474_INTERNAL_ERROR, - data.data + data.rpcData ) ) } else handler.reject(new Error(data.error)) From 494bd8569609e3d04ee5a8c0375e2a7b4300215e Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Mon, 6 Nov 2023 18:14:58 +0900 Subject: [PATCH 42/50] wip: cleanup --- .../src/core/domains/ethereum/handler.tabs.ts | 11 +++++------ apps/extension/src/core/domains/ethereum/helpers.ts | 2 +- apps/extension/src/core/handlers/index.ts | 1 - .../extension/src/core/util/getErc20ContractData.ts | 1 - .../src/core/util/getFeeHistoryAnalysis.ts | 7 ------- apps/extension/src/ui/api/types.ts | 2 +- .../apps/popup/pages/Sign/ethereum/Transaction.tsx | 5 +++-- .../GasSettings/CustomGasSettingsFormEip1559.tsx | 3 --- .../domains/Ethereum/GasSettings/FeeOptionsForm.tsx | 3 ++- .../src/ui/domains/Ethereum/useEthBalance.ts | 3 ++- .../src/ui/domains/Ethereum/useEthTransaction.ts | 2 +- .../src/ui/domains/Ethereum/usePublicClient.ts | 10 +++------- .../src/ui/domains/SendFunds/SendFundsProgress.tsx | 11 ----------- .../src/ui/domains/SendFunds/useSendFunds.ts | 13 ++++++++----- .../src/ui/domains/Sign/SignHardwareEthereum.tsx | 1 - .../src/ui/domains/Sign/SignLedgerEthereum.tsx | 1 - .../EthereumSignTransactionRequestContext.ts | 5 ----- packages/balances-evm-erc20/src/EvmErc20Module.ts | 13 +++++-------- .../src/SubstrateEquilibriumModule.ts | 4 ++-- packages/balances/src/BalanceModule.ts | 1 - packages/balances/src/types/balances.ts | 6 +++--- packages/talisman-ui/src/components/Tooltip.tsx | 2 +- 22 files changed, 37 insertions(+), 70 deletions(-) diff --git a/apps/extension/src/core/domains/ethereum/handler.tabs.ts b/apps/extension/src/core/domains/ethereum/handler.tabs.ts index 907534810e..ab7f563845 100644 --- a/apps/extension/src/core/domains/ethereum/handler.tabs.ts +++ b/apps/extension/src/core/domains/ethereum/handler.tabs.ts @@ -206,7 +206,7 @@ export class EthTabsHandler extends TabsHandler { if (!site) return siteId = site.id if (site.ethChainId && site.ethAddresses?.length) { - chainId = typeof site?.ethChainId !== "undefined" ? toHex(site.ethChainId) : undefined + chainId = site?.ethChainId !== undefined ? toHex(site.ethChainId) : undefined accounts = site.ethAddresses ?? [] // check that the network is still registered before broadcasting @@ -241,7 +241,7 @@ export class EthTabsHandler extends TabsHandler { try { // new state for this dapp - chainId = typeof site?.ethChainId !== "undefined" ? toHex(site.ethChainId) : undefined + chainId = site?.ethChainId !== undefined ? toHex(site.ethChainId) : undefined //TODO check eth addresses still exist accounts = site?.ethAddresses ?? [] connected = !!accounts.length @@ -332,7 +332,7 @@ export class EthTabsHandler extends TabsHandler { await Promise.all( network.rpcUrls.map(async (rpcUrl) => { try { - const client = createClient({ transport: http(rpcUrl) }) + const client = createClient({ transport: http(rpcUrl, { retryCount: 1 }) }) const rpcChainIdHex = await Promise.race([ client.request({ method: "eth_chainId" }), throwAfter(10_000, "timeout"), // 10 sec timeout @@ -578,7 +578,6 @@ export class EthTabsHandler extends TabsHandler { { // eventhough not standard, some transactions specify a chainId in the request // throw an error if it's not the current tab's chainId - let specifiedChainId = (txRequest as unknown as { chainId?: string | number }).chainId // ensure chainId isn't an hex (ex: Zerion) @@ -718,7 +717,7 @@ export class EthTabsHandler extends TabsHandler { ) await this.checkAccountAuthorised(url) - // TODO typecheck return types against EthRequestArgumentsViem / EthRequestResultsViem + // TODO typecheck return types against rpc schema switch (request.method) { case "eth_requestAccounts": await this.requestPermissions( @@ -754,7 +753,7 @@ export class EthTabsHandler extends TabsHandler { case "eth_signTypedData_v1": case "eth_signTypedData_v3": case "eth_signTypedData_v4": { - return this.signMessage(url, request as EthRequestSignArguments, port) + return this.signMessage(url, request, port) } case "personal_ecRecover": { diff --git a/apps/extension/src/core/domains/ethereum/helpers.ts b/apps/extension/src/core/domains/ethereum/helpers.ts index 5bfbef85ac..9b186a3525 100644 --- a/apps/extension/src/core/domains/ethereum/helpers.ts +++ b/apps/extension/src/core/domains/ethereum/helpers.ts @@ -50,7 +50,7 @@ export const getEthTransferTransactionBase = async ( ) => { assert(evmNetworkId, "evmNetworkId is required") assert(token, "token is required") - assert(typeof planck === "bigint", "planck is required") + assert(isBigInt(planck), "planck is required") assert(isAddress(from), "from address is required") assert(isAddress(to), "to address is required") diff --git a/apps/extension/src/core/handlers/index.ts b/apps/extension/src/core/handlers/index.ts index 4744891f41..e456f5ac36 100644 --- a/apps/extension/src/core/handlers/index.ts +++ b/apps/extension/src/core/handlers/index.ts @@ -107,7 +107,6 @@ const talismanHandler = ( try { if (["pub(eth.request)", "pri(eth.request)"].includes(message)) { const evmError = getEvmErrorCause(error) - // TODO test on dapps if behavior is different without data port.postMessage({ id, error: cleanupEvmErrorMessage( diff --git a/apps/extension/src/core/util/getErc20ContractData.ts b/apps/extension/src/core/util/getErc20ContractData.ts index 548be2ba1d..9ee6690e4c 100644 --- a/apps/extension/src/core/util/getErc20ContractData.ts +++ b/apps/extension/src/core/util/getErc20ContractData.ts @@ -22,6 +22,5 @@ export const getErc20ContractData = async ( publicClient: client, }) const [symbol, decimals] = await Promise.all([contract.read.symbol(), contract.read.decimals()]) - //const [symbol, decimals] = await Promise.all([erc20.symbol(), erc20.decimals()]) return { symbol, decimals } } diff --git a/apps/extension/src/core/util/getFeeHistoryAnalysis.ts b/apps/extension/src/core/util/getFeeHistoryAnalysis.ts index 403c692e98..2dac1adff6 100644 --- a/apps/extension/src/core/util/getFeeHistoryAnalysis.ts +++ b/apps/extension/src/core/util/getFeeHistoryAnalysis.ts @@ -34,13 +34,6 @@ export const getFeeHistoryAnalysis = async ( rewardPercentiles: REWARD_PERCENTILES, }) - // how busy the network is over this period - // values can be null (ex astar) - // TODO check that with viem values can't be null - // const avgGasUsedRatio = feeHistory.gasUsedRatio.includes(null) - // ? null - // : (feeHistory.gasUsedRatio as number[]).reduce((prev, curr) => prev + curr, 0) / - // feeHistory.gasUsedRatio.length const avgGasUsedRatio = (feeHistory.gasUsedRatio as number[]).reduce((prev, curr) => prev + curr, 0) / feeHistory.gasUsedRatio.length diff --git a/apps/extension/src/ui/api/types.ts b/apps/extension/src/ui/api/types.ts index 0ef81b5af8..033103507d 100644 --- a/apps/extension/src/ui/api/types.ts +++ b/apps/extension/src/ui/api/types.ts @@ -280,7 +280,7 @@ export default interface MessageTypes { ) => Promise ethCancelSign: (id: SigningRequestID<"eth-sign" | "eth-send">) => Promise ethRequest: (request: AnyEthRequestChainId) => Promise - ethGetTransactionsCount: (address: `0x${string}`, evmNetworkId: EvmNetworkId) => Promise + ethGetTransactionsCount: (address: EvmAddress, evmNetworkId: EvmNetworkId) => Promise ethNetworkAddGetRequests: () => Promise ethNetworkAddApprove: (id: AddEthereumChainRequestId) => Promise ethNetworkAddCancel: (is: AddEthereumChainRequestId) => Promise diff --git a/apps/extension/src/ui/apps/popup/pages/Sign/ethereum/Transaction.tsx b/apps/extension/src/ui/apps/popup/pages/Sign/ethereum/Transaction.tsx index 067ca112b3..bdad2cd6c9 100644 --- a/apps/extension/src/ui/apps/popup/pages/Sign/ethereum/Transaction.tsx +++ b/apps/extension/src/ui/apps/popup/pages/Sign/ethereum/Transaction.tsx @@ -1,3 +1,4 @@ +import { EvmAddress } from "@core/domains/ethereum/types" import { EthPriorityOptionName } from "@core/domains/signing/types" import { AppPill } from "@talisman/components/AppPill" import { WithTooltip } from "@talisman/components/Tooltip" @@ -23,7 +24,7 @@ import { Button, Tooltip, TooltipContent, TooltipTrigger } from "talisman-ui" import { SignAccountAvatar } from "../SignAccountAvatar" -const useEvmBalance = (address: string, evmNetworkId: EvmNetworkId | undefined) => { +const useEvmBalance = (address: EvmAddress, evmNetworkId: EvmNetworkId | undefined) => { const publicClient = usePublicClient(evmNetworkId) return useEthBalance(publicClient, address) } @@ -109,7 +110,7 @@ export const EthSignTransactionRequest = () => { isValid, networkUsage, } = useEthSignTransactionRequest() - const { balance } = useEvmBalance(account?.address, network?.id) + const { balance } = useEvmBalance(account?.address as EvmAddress, network?.id) const { processing, errorMessage } = useMemo(() => { return { diff --git a/apps/extension/src/ui/domains/Ethereum/GasSettings/CustomGasSettingsFormEip1559.tsx b/apps/extension/src/ui/domains/Ethereum/GasSettings/CustomGasSettingsFormEip1559.tsx index 9429a546d9..cdf1493197 100644 --- a/apps/extension/src/ui/domains/Ethereum/GasSettings/CustomGasSettingsFormEip1559.tsx +++ b/apps/extension/src/ui/domains/Ethereum/GasSettings/CustomGasSettingsFormEip1559.tsx @@ -231,9 +231,6 @@ export const CustomGasSettingsFormEip1559: FC else if ( maxPriorityFee && parseGwei(String(maxPriorityFee)) > 2n * highSettings.maxPriorityFeePerGas - // BigNumber.from(ethers.utils.parseUnits(String(maxPriorityFee), "gwei")).gt( - // BigNumber.from(2).mul(highSettings?.maxPriorityFeePerGas) - // ) ) warningFee = t("Max Priority Fee seems higher than required") diff --git a/apps/extension/src/ui/domains/Ethereum/GasSettings/FeeOptionsForm.tsx b/apps/extension/src/ui/domains/Ethereum/GasSettings/FeeOptionsForm.tsx index e20081258a..20b25e30fe 100644 --- a/apps/extension/src/ui/domains/Ethereum/GasSettings/FeeOptionsForm.tsx +++ b/apps/extension/src/ui/domains/Ethereum/GasSettings/FeeOptionsForm.tsx @@ -7,6 +7,7 @@ import { GasSettingsByPriority, } from "@core/domains/signing/types" import { BalanceFormatter } from "@talismn/balances" +import { TokenId } from "@talismn/chaindata-provider" import { ChevronRightIcon } from "@talismn/icons" import { classNames } from "@talismn/util" import { TokensAndFiat } from "@ui/domains/Asset/TokensAndFiat" @@ -35,7 +36,7 @@ const getGasSettings = ( const Eip1559FeeTooltip: FC<{ estimatedFee: bigint maxFee: bigint - tokenId: string + tokenId: TokenId }> = ({ estimatedFee, maxFee, tokenId }) => { const { t } = useTranslation("request") const token = useToken(tokenId) diff --git a/apps/extension/src/ui/domains/Ethereum/useEthBalance.ts b/apps/extension/src/ui/domains/Ethereum/useEthBalance.ts index b4feeae3c0..fe1d583b43 100644 --- a/apps/extension/src/ui/domains/Ethereum/useEthBalance.ts +++ b/apps/extension/src/ui/domains/Ethereum/useEthBalance.ts @@ -1,10 +1,11 @@ +import { EvmAddress } from "@core/domains/ethereum/types" import { isEthereumAddress } from "@talismn/util" import { useQuery } from "@tanstack/react-query" import { PublicClient } from "viem" export const useEthBalance = ( publicClient: PublicClient | undefined, - address: string | undefined + address: EvmAddress | undefined ) => { const { data: balance, ...rest } = useQuery({ queryKey: ["useEthBalance", publicClient?.chain?.id, address], diff --git a/apps/extension/src/ui/domains/Ethereum/useEthTransaction.ts b/apps/extension/src/ui/domains/Ethereum/useEthTransaction.ts index 78a81e2669..8dd1e4e41d 100644 --- a/apps/extension/src/ui/domains/Ethereum/useEthTransaction.ts +++ b/apps/extension/src/ui/domains/Ethereum/useEthTransaction.ts @@ -54,7 +54,7 @@ const useNonce = ( // TODO : could be skipped for networks that we know already support it, but need to keep checking for legacy network in case they upgrade const useHasEip1559Support = (publicClient: PublicClient | undefined) => { const { data, ...rest } = useQuery({ - queryKey: ["hasEip1559Support", publicClient?.chain?.id], + queryKey: ["useHasEip1559Support", publicClient?.chain?.id], queryFn: async () => { if (!publicClient) return null diff --git a/apps/extension/src/ui/domains/Ethereum/usePublicClient.ts b/apps/extension/src/ui/domains/Ethereum/usePublicClient.ts index c479139569..fd31bfbea0 100644 --- a/apps/extension/src/ui/domains/Ethereum/usePublicClient.ts +++ b/apps/extension/src/ui/domains/Ethereum/usePublicClient.ts @@ -15,11 +15,8 @@ const viemRequest = try { return await api.ethRequest({ chainId, method, params }) } catch (err) { - log.error("[provider.request] error on %s", method, { err }) + log.error("publicClient request error : %s", method, { err }) throw err - // TODO check that we get proper error codes - // const { message, code, data } = err as EthProviderRpcError - // throw new EthProviderRpcError(message, code ?? ETH_ERROR_EIP1474_INTERNAL_ERROR, data) } } @@ -40,18 +37,17 @@ export const getExtensionPublicClient = ( name: nativeToken.symbol, }, rpcUrls: { - // rpcs are a typescript requirement, won't be used by the custom transport + // rpcs are a typescript requirement, but won't be used by the custom transport public: { http: [] }, default: { http: [] }, }, }, - // TODO check timers, decide if they should be here (remove them on backend) or clear them here and use defaults on backend transport: custom( { request: viemRequest(evmNetwork.id), }, { - // backend will retry 3 times, no need to retry here + // backend will retry, at it's own transport level retryCount: 0, } ), diff --git a/apps/extension/src/ui/domains/SendFunds/SendFundsProgress.tsx b/apps/extension/src/ui/domains/SendFunds/SendFundsProgress.tsx index 675dd0d72b..1caa509b03 100644 --- a/apps/extension/src/ui/domains/SendFunds/SendFundsProgress.tsx +++ b/apps/extension/src/ui/domains/SendFunds/SendFundsProgress.tsx @@ -250,17 +250,6 @@ const SendFundsProgressProgressEvm: FC = ({ ) } -// const UNKNOWN_TX: WalletTransaction = { -// hash: "", -// networkType: "evm", -// status: "unknown", -// evmNetworkId: "", -// account: "", -// unsigned: null, -// nonce: 0, -// timestamp: 0, -// } - type SendFundsProgressProps = { hash: HexString networkIdOrHash: string diff --git a/apps/extension/src/ui/domains/SendFunds/useSendFunds.ts b/apps/extension/src/ui/domains/SendFunds/useSendFunds.ts index 7d9c6b0863..d07991df34 100644 --- a/apps/extension/src/ui/domains/SendFunds/useSendFunds.ts +++ b/apps/extension/src/ui/domains/SendFunds/useSendFunds.ts @@ -4,7 +4,6 @@ import { serializeGasSettings, serializeTransactionRequest, } from "@core/domains/ethereum/helpers" -import { EvmAddress } from "@core/domains/ethereum/types" import { AssetTransferMethod } from "@core/domains/transfers/types" import { log } from "@core/log" import { HexString } from "@polkadot/util/types" @@ -133,7 +132,6 @@ const useEvmTransaction = ( const result = useEthTransaction(tx, token?.evmNetwork?.id, isLocked) - // TODO result seems weird, fix typings return { evmTransaction: tx ? { tx, ...result } : undefined, evmInvalidTxError } } @@ -600,13 +598,18 @@ const useSendFundsProvider = () => { gotoProgress({ hash, networkIdOrHash: chain.genesisHash }) return } - if (evmTransaction?.transaction && amount && token?.evmNetwork?.id && to) { + if ( + evmTransaction?.transaction && + amount && + token?.evmNetwork?.id && + isEthereumAddress(to) + ) { const serialized = serializeTransactionRequest(evmTransaction.transaction) const { hash } = await api.assetTransferEthHardware( - token?.evmNetwork.id, + token.evmNetwork.id, token.id, amount, - to as EvmAddress, + to, serialized, signature ) diff --git a/apps/extension/src/ui/domains/Sign/SignHardwareEthereum.tsx b/apps/extension/src/ui/domains/Sign/SignHardwareEthereum.tsx index f9a1c2fc49..3aaa73f436 100644 --- a/apps/extension/src/ui/domains/Sign/SignHardwareEthereum.tsx +++ b/apps/extension/src/ui/domains/Sign/SignHardwareEthereum.tsx @@ -12,7 +12,6 @@ export type SignHardwareEthereumProps = { evmNetworkId?: EvmNetworkId account: AccountJsonAny method: EthSignMessageMethod | "eth_sendTransaction" - // eslint-disable-next-line @typescript-eslint/no-explicit-any payload: unknown // string message, typed object for eip712, TransactionRequest for tx containerId?: string className?: string diff --git a/apps/extension/src/ui/domains/Sign/SignLedgerEthereum.tsx b/apps/extension/src/ui/domains/Sign/SignLedgerEthereum.tsx index 637ae68207..2c6877fc13 100644 --- a/apps/extension/src/ui/domains/Sign/SignLedgerEthereum.tsx +++ b/apps/extension/src/ui/domains/Sign/SignLedgerEthereum.tsx @@ -38,7 +38,6 @@ const signWithLedger = async ( ledger: LedgerEthereumApp, chainId: number, method: EthSignMessageMethod | "eth_sendTransaction", - // eslint-disable-next-line @typescript-eslint/no-explicit-any payload: unknown, accountPath: string ): Promise<`0x${string}`> => { diff --git a/apps/extension/src/ui/domains/Sign/SignRequestContext/EthereumSignTransactionRequestContext.ts b/apps/extension/src/ui/domains/Sign/SignRequestContext/EthereumSignTransactionRequestContext.ts index 7ca77cd43f..0ebf994ee5 100644 --- a/apps/extension/src/ui/domains/Sign/SignRequestContext/EthereumSignTransactionRequestContext.ts +++ b/apps/extension/src/ui/domains/Sign/SignRequestContext/EthereumSignTransactionRequestContext.ts @@ -2,11 +2,6 @@ import { parseRpcTransactionRequestBase, serializeTransactionRequest, } from "@core/domains/ethereum/helpers" -// import { rebuildTransactionRequestNumbers } from "@core/domains/ethereum/helpers" -// import { -// parseRpcTransactionRequestBase, -// parseTransactionRequest, -// } from "@core/domains/ethereum/viemMigration" import { KnownSigningRequestIdOnly } from "@core/domains/signing/types" import { log } from "@core/log" import { HexString } from "@polkadot/util/types" diff --git a/packages/balances-evm-erc20/src/EvmErc20Module.ts b/packages/balances-evm-erc20/src/EvmErc20Module.ts index 0c2abd00f6..d2f859becf 100644 --- a/packages/balances-evm-erc20/src/EvmErc20Module.ts +++ b/packages/balances-evm-erc20/src/EvmErc20Module.ts @@ -18,7 +18,7 @@ import { } from "@talismn/chaindata-provider" import { hasOwnProperty, isEthereumAddress } from "@talismn/util" import isEqual from "lodash/isEqual" -import { PublicClient } from "viem" +import { PublicClient, getContract } from "viem" import { erc20Abi } from "./erc20Abi" import log from "./log" @@ -118,16 +118,14 @@ export const EvmErc20Module: NewBalanceModule< const publicClient = await chainConnector.getPublicClientForEvmNetwork(chainId) if (!publicClient) return [] - const contract = { + const contract = getContract({ abi: erc20Abi, address: contractAddress as `0x${string}`, - } + publicClient, + }) try { - return Promise.all([ - publicClient.readContract({ ...contract, functionName: "symbol" }), - publicClient.readContract({ ...contract, functionName: "decimals" }), - ]) + return Promise.all([contract.read.symbol(), contract.read.decimals()]) } catch (error) { log.error(`Failed to retrieve contract symbol and decimals`, String(error)) return [] @@ -372,7 +370,6 @@ async function getFreeBalance( }) return res.toString() - //((await contract.balanceOf(accountAddress)).toBigInt() ?? 0n).toString() } catch (error) { const errorMessage = hasOwnProperty(error, "shortMessage") ? error.shortMessage diff --git a/packages/balances-substrate-equilibrium/src/SubstrateEquilibriumModule.ts b/packages/balances-substrate-equilibrium/src/SubstrateEquilibriumModule.ts index b78311b68f..2ef3ce20c1 100644 --- a/packages/balances-substrate-equilibrium/src/SubstrateEquilibriumModule.ts +++ b/packages/balances-substrate-equilibrium/src/SubstrateEquilibriumModule.ts @@ -30,7 +30,7 @@ import { metadataIsV14, mutateMetadata, } from "@talismn/mutate-metadata" -import { decodeAnyAddress } from "@talismn/util" +import { decodeAnyAddress, isBigInt } from "@talismn/util" import log from "./log" @@ -446,7 +446,7 @@ const DEFAULT_DECIMALS = 9 const tokenSymbolFromU64Id = (u64: number | bigint | AbstractInt) => { const bytes = [] - let num = typeof u64 === "number" ? BigInt(u64) : typeof u64 === "bigint" ? u64 : u64.toBigInt() + let num = typeof u64 === "number" ? BigInt(u64) : isBigInt(u64) ? u64 : u64.toBigInt() do { bytes.unshift(Number(num % 256n)) num = num / 256n diff --git a/packages/balances/src/BalanceModule.ts b/packages/balances/src/BalanceModule.ts index 5969a51d6a..fb46b742b2 100644 --- a/packages/balances/src/BalanceModule.ts +++ b/packages/balances/src/BalanceModule.ts @@ -17,7 +17,6 @@ export type DefaultTransferParams = undefined export type NewTransferParamsType> = BaseTransferParams & T export type TransferTokenTx = { type: "substrate"; tx: UnsignedTransaction } -// | { type: "evm"; tx: TransactionRequest } export type ChainConnectors = { substrate?: ChainConnector; evm?: ChainConnectorEvm } export type Hydrate = { diff --git a/packages/balances/src/types/balances.ts b/packages/balances/src/types/balances.ts index d9bed0ff76..fac6fc97d1 100644 --- a/packages/balances/src/types/balances.ts +++ b/packages/balances/src/types/balances.ts @@ -1,6 +1,6 @@ import { ChainList, EvmNetworkList, TokenList } from "@talismn/chaindata-provider" import { TokenRateCurrency, TokenRates, TokenRatesList } from "@talismn/token-rates" -import { BigMath, NonFunctionProperties, isArrayOf, planckToTokens } from "@talismn/util" +import { BigMath, NonFunctionProperties, isArrayOf, isBigInt, planckToTokens } from "@talismn/util" import { filterMirrorTokens } from "../helpers" import log from "../log" @@ -301,7 +301,7 @@ export class Balance { #format = (balance: bigint | string) => new BalanceFormatter( - typeof balance === "bigint" ? balance.toString() : balance, + isBigInt(balance) ? balance.toString() : balance, this.decimals || undefined, this.#db?.tokenRates && this.#db.tokenRates[this.tokenId] ) @@ -457,7 +457,7 @@ export class BalanceFormatter { decimals?: number | undefined, fiatRatios?: TokenRates ) { - this.#planck = typeof planck === "bigint" ? planck.toString() : planck ?? "0" + this.#planck = isBigInt(planck) ? planck.toString() : planck ?? "0" this.#decimals = decimals || 0 this.#fiatRatios = fiatRatios || null } diff --git a/packages/talisman-ui/src/components/Tooltip.tsx b/packages/talisman-ui/src/components/Tooltip.tsx index 3846f1fbab..2174f7dcc5 100644 --- a/packages/talisman-ui/src/components/Tooltip.tsx +++ b/packages/talisman-ui/src/components/Tooltip.tsx @@ -136,7 +136,7 @@ export const TooltipTrigger = React.forwardRef< export const TooltipContent = React.forwardRef>( function TooltipContent( { - className = "rounded-xs text-body-secondary border-grey-700 z-20 border-[0.5px] bg-black p-3 text-xs shadow max-w-full overflow-hidden", + className = "rounded-xs text-body-secondary border-grey-700 z-20 border-[0.5px] bg-black p-3 text-xs shadow", ...props }, propRef From 9c83b61ab0cbe6ffbd5296cb59ad67401e36dd17 Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Tue, 7 Nov 2023 11:38:52 +0900 Subject: [PATCH 43/50] fix: remove color transition on button --- packages/talisman-ui/src/components/Button.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/talisman-ui/src/components/Button.tsx b/packages/talisman-ui/src/components/Button.tsx index 6d578afbd6..4aa15b6ea4 100644 --- a/packages/talisman-ui/src/components/Button.tsx +++ b/packages/talisman-ui/src/components/Button.tsx @@ -54,7 +54,7 @@ export const Button: FC = ({ type="button" disabled={disabled || processing} className={classNames( - "bg relative inline-flex items-center justify-center rounded outline-none transition-colors ", + "bg relative inline-flex items-center justify-center rounded outline-none", small ? "h-20 px-8 text-sm" : "text-md h-28 px-12", fullWidth ? "w-full" : "", colors, From 15d820d23428983e582c0b56317ccbe1bc16b7f8 Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Tue, 7 Nov 2023 11:59:33 +0900 Subject: [PATCH 44/50] fix: speedup/cancel behavior --- .../transactions/watchEthereumTransaction.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/apps/extension/src/core/domains/transactions/watchEthereumTransaction.ts b/apps/extension/src/core/domains/transactions/watchEthereumTransaction.ts index dfa90866ef..74df0264ae 100644 --- a/apps/extension/src/core/domains/transactions/watchEthereumTransaction.ts +++ b/apps/extension/src/core/domains/transactions/watchEthereumTransaction.ts @@ -57,12 +57,15 @@ export const watchEthereumTransaction = async ( throwAfter(5 * 60_000, "Transaction not found"), ]) - // to test failing transactions, swap on busy AMM pools with a 0.05% slippage limit - updateTransactionStatus( - hash, - receipt.status === "success" ? "success" : "error", - receipt.blockNumber - ) + // check hash which may be incorrect for cancelled tx, in which case receipt includes the replacement tx hash + if (receipt.transactionHash === hash) { + // to test failing transactions, swap on busy AMM pools with a 0.05% slippage limit + updateTransactionStatus( + hash, + receipt.status === "success" ? "success" : "error", + receipt.blockNumber + ) + } // success if associated to a block number if (withNotifications) From b6d14fc5ea98680071b6eb6675fbcd3d5eeb9ff1 Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Tue, 7 Nov 2023 18:28:23 +0900 Subject: [PATCH 45/50] feat: display error as tooltip in custom gas form --- .../CustomGasSettingsFormEip1559.tsx | 19 +++++++++++++++---- .../CustomGasSettingsFormLegacy.tsx | 19 +++++++++++++++---- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/apps/extension/src/ui/domains/Ethereum/GasSettings/CustomGasSettingsFormEip1559.tsx b/apps/extension/src/ui/domains/Ethereum/GasSettings/CustomGasSettingsFormEip1559.tsx index cdf1493197..8467a3ee12 100644 --- a/apps/extension/src/ui/domains/Ethereum/GasSettings/CustomGasSettingsFormEip1559.tsx +++ b/apps/extension/src/ui/domains/Ethereum/GasSettings/CustomGasSettingsFormEip1559.tsx @@ -1,3 +1,4 @@ +import { getHumanReadableErrorMessage } from "@core/domains/ethereum/errors" import { getMaxFeePerGas } from "@core/domains/ethereum/helpers" import { EthGasSettingsEip1559, EvmNetworkId } from "@core/domains/ethereum/types" import { EthTransactionDetails, GasSettingsByPriorityEip1559 } from "@core/domains/signing/types" @@ -14,7 +15,7 @@ import { FC, FormEventHandler, useCallback, useEffect, useMemo, useRef, useState import { useForm } from "react-hook-form" import { useTranslation } from "react-i18next" import { useDebounce } from "react-use" -import { IconButton } from "talisman-ui" +import { IconButton, Tooltip, TooltipContent, TooltipTrigger } from "talisman-ui" import { Button, FormFieldContainer, FormFieldInputText } from "talisman-ui" import { TransactionRequest, formatGwei, parseGwei } from "viem" import * as yup from "yup" @@ -275,8 +276,11 @@ export const CustomGasSettingsFormEip1559: FC [genericEvent, onConfirm, txDetails.evmNetworkId] ) - const { isValid: isGasSettingsValid, isLoading: isLoadingGasSettingsValid } = - useIsValidGasSettings(txDetails.evmNetworkId, tx, maxBaseFee, maxPriorityFee, gasLimit) + const { + isValid: isGasSettingsValid, + isLoading: isLoadingGasSettingsValid, + error: gasSettingsError, + } = useIsValidGasSettings(txDetails.evmNetworkId, tx, maxBaseFee, maxPriorityFee, gasLimit) const showMaxFeeTotal = isFormValid && isGasSettingsValid && !isLoadingGasSettingsValid @@ -403,7 +407,14 @@ export const CustomGasSettingsFormEip1559: FC ) : isLoadingGasSettingsValid ? ( ) : ( - {t("Invalid settings")} + + + {t("Invalid transaction")} + + {!!gasSettingsError && ( + {getHumanReadableErrorMessage(gasSettingsError)} + )} + )}
diff --git a/apps/extension/src/ui/domains/Ethereum/GasSettings/CustomGasSettingsFormLegacy.tsx b/apps/extension/src/ui/domains/Ethereum/GasSettings/CustomGasSettingsFormLegacy.tsx index aed9f4678c..b820c54168 100644 --- a/apps/extension/src/ui/domains/Ethereum/GasSettings/CustomGasSettingsFormLegacy.tsx +++ b/apps/extension/src/ui/domains/Ethereum/GasSettings/CustomGasSettingsFormLegacy.tsx @@ -1,3 +1,4 @@ +import { getHumanReadableErrorMessage } from "@core/domains/ethereum/errors" import { EthGasSettingsLegacy, EvmNetworkId } from "@core/domains/ethereum/types" import { EthTransactionDetails, GasSettingsByPriorityLegacy } from "@core/domains/signing/types" import { log } from "@core/log" @@ -12,7 +13,7 @@ import { FC, FormEventHandler, useCallback, useEffect, useMemo, useRef, useState import { useForm } from "react-hook-form" import { useTranslation } from "react-i18next" import { useDebounce } from "react-use" -import { IconButton } from "talisman-ui" +import { IconButton, Tooltip, TooltipContent, TooltipTrigger } from "talisman-ui" import { Button, FormFieldContainer, FormFieldInputText } from "talisman-ui" import { TransactionRequest, formatGwei, parseGwei } from "viem" import * as yup from "yup" @@ -229,8 +230,11 @@ export const CustomGasSettingsFormLegacy: FC = [genericEvent, onConfirm, txDetails.evmNetworkId] ) - const { isValid: isGasSettingsValid, isLoading: isLoadingGasSettingsValid } = - useIsValidGasSettings(txDetails.evmNetworkId, tx, gasPrice, gasLimit) + const { + isValid: isGasSettingsValid, + isLoading: isLoadingGasSettingsValid, + error: gasSettingsError, + } = useIsValidGasSettings(txDetails.evmNetworkId, tx, gasPrice, gasLimit) const showMaxFeeTotal = isFormValid && isGasSettingsValid && !isLoadingGasSettingsValid @@ -337,7 +341,14 @@ export const CustomGasSettingsFormLegacy: FC = ) : isLoadingGasSettingsValid ? ( ) : ( - {t("Invalid settings")} + + + {t("Invalid transaction")} + + {!!gasSettingsError && ( + {getHumanReadableErrorMessage(gasSettingsError)} + )} + )}
From c615a360272037dd7318c4d10030cf6a724de364 Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Wed, 8 Nov 2023 12:44:06 +0900 Subject: [PATCH 46/50] fix: ledger signature crafting --- apps/extension/package.json | 2 +- .../ui/domains/Sign/SignLedgerEthereum.tsx | 22 ++++++++++++++----- packages/balances-evm-erc20/package.json | 2 +- packages/balances-evm-native/package.json | 2 +- packages/chain-connector-evm/package.json | 2 +- yarn.lock | 16 +++++++------- 6 files changed, 29 insertions(+), 17 deletions(-) diff --git a/apps/extension/package.json b/apps/extension/package.json index c7e964ec71..30ce25b63b 100644 --- a/apps/extension/package.json +++ b/apps/extension/package.json @@ -137,7 +137,7 @@ "typescript": "^5.2.2", "url-join": "^5.0.0", "uuid": "^8.3.2", - "viem": "^1.18.3", + "viem": "^1.18.9", "webextension-polyfill": "0.8.0", "webpack": "^5.88.1", "webpack-cli": "^4.10.0", diff --git a/apps/extension/src/ui/domains/Sign/SignLedgerEthereum.tsx b/apps/extension/src/ui/domains/Sign/SignLedgerEthereum.tsx index 2c6877fc13..c791077758 100644 --- a/apps/extension/src/ui/domains/Sign/SignLedgerEthereum.tsx +++ b/apps/extension/src/ui/domains/Sign/SignLedgerEthereum.tsx @@ -28,11 +28,23 @@ import { import { LedgerSigningStatus } from "./LedgerSigningStatus" import { SignHardwareEthereumProps } from "./SignHardwareEthereum" -const toSignature = ({ v, r, s }: { v: string | number; r: string; s: string }): Signature => ({ - v: typeof v === "string" ? hexToBigInt(`0x${v}`) : BigInt(v), - r: `0x${r}`, - s: `0x${s}`, -}) +const toSignature = ({ v, r, s }: { v: string | number; r: string; s: string }): Signature => { + const parseV = (v: string | number) => { + const parsed = typeof v === "string" ? hexToBigInt(`0x${v}`) : BigInt(v) + + // ideally this should be done in viem + if (parsed === 0n) return 27n + if (parsed === 1n) return 28n + + return parsed + } + + return { + v: parseV(v), + r: `0x${r}`, + s: `0x${s}`, + } +} const signWithLedger = async ( ledger: LedgerEthereumApp, diff --git a/packages/balances-evm-erc20/package.json b/packages/balances-evm-erc20/package.json index 7bf751f184..8a2b743ded 100644 --- a/packages/balances-evm-erc20/package.json +++ b/packages/balances-evm-erc20/package.json @@ -31,7 +31,7 @@ "@talismn/util": "workspace:*", "anylogger": "^1.0.11", "lodash": "4.17.21", - "viem": "^1.18.3" + "viem": "^1.18.9" }, "devDependencies": { "@polkadot/util": "^11.1.1", diff --git a/packages/balances-evm-native/package.json b/packages/balances-evm-native/package.json index 073a9d68fe..98e1d778c4 100644 --- a/packages/balances-evm-native/package.json +++ b/packages/balances-evm-native/package.json @@ -31,7 +31,7 @@ "@talismn/util": "workspace:*", "anylogger": "^1.0.11", "lodash": "4.17.21", - "viem": "^1.18.3" + "viem": "^1.18.9" }, "devDependencies": { "@talismn/eslint-config": "workspace:*", diff --git a/packages/chain-connector-evm/package.json b/packages/chain-connector-evm/package.json index 39e21a7989..ca4d2f1bd2 100644 --- a/packages/chain-connector-evm/package.json +++ b/packages/chain-connector-evm/package.json @@ -30,7 +30,7 @@ "@talismn/util": "workspace:*", "anylogger": "^1.0.11", "lodash": "4.17.21", - "viem": "^1.18.3" + "viem": "^1.18.9" }, "devDependencies": { "@talismn/eslint-config": "workspace:*", diff --git a/yarn.lock b/yarn.lock index 1cbe0ad63a..a7d88d331a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7857,7 +7857,7 @@ __metadata: lodash: 4.17.21 ts-jest: ^28.0.2 typescript: ^5.2.2 - viem: ^1.18.3 + viem: ^1.18.9 peerDependencies: "@polkadot/util": 11.x languageName: unknown @@ -7880,7 +7880,7 @@ __metadata: lodash: 4.17.21 ts-jest: ^28.0.2 typescript: ^5.2.2 - viem: ^1.18.3 + viem: ^1.18.9 languageName: unknown linkType: soft @@ -8122,7 +8122,7 @@ __metadata: lodash: 4.17.21 ts-jest: ^28.0.2 typescript: ^5.2.2 - viem: ^1.18.3 + viem: ^1.18.9 languageName: unknown linkType: soft @@ -15527,7 +15527,7 @@ __metadata: url: ^0.11.0 url-join: ^5.0.0 uuid: ^8.3.2 - viem: ^1.18.3 + viem: ^1.18.9 webextension-polyfill: 0.8.0 webpack: ^5.88.1 webpack-bundle-analyzer: ^4.9.0 @@ -27151,9 +27151,9 @@ __metadata: languageName: node linkType: hard -"viem@npm:^1.18.3": - version: 1.18.3 - resolution: "viem@npm:1.18.3" +"viem@npm:^1.18.9": + version: 1.18.9 + resolution: "viem@npm:1.18.9" dependencies: "@adraffy/ens-normalize": 1.9.4 "@noble/curves": 1.2.0 @@ -27168,7 +27168,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 263eb99ee46c586a743a37e15e3079546c5ee681d541b5d42db41eeae27638dbeabc6542ac620bdb5e3eaecae1ec3f2ad04cf4693f3f3105bed64a495ff925bd + checksum: 4bcd28a3bc2e0ff2f19a8de2c779dfdc0c7a479fe2c103a03cb4faa96450241075ddbc7e39bde4db9d8ad8e1dbc4d394223cdae00f8e135c2748ba354a2e2665 languageName: node linkType: hard From a63d0eff89d95df7d4eafab0f4af129adf1a2ed2 Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Wed, 8 Nov 2023 13:43:37 +0900 Subject: [PATCH 47/50] fix: unlocks tx if user rejects from ledger --- apps/extension/src/ui/domains/Sign/SignLedgerEthereum.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/extension/src/ui/domains/Sign/SignLedgerEthereum.tsx b/apps/extension/src/ui/domains/Sign/SignLedgerEthereum.tsx index c791077758..9d12ffcdcd 100644 --- a/apps/extension/src/ui/domains/Sign/SignLedgerEthereum.tsx +++ b/apps/extension/src/ui/domains/Sign/SignLedgerEthereum.tsx @@ -185,7 +185,10 @@ const SignLedgerEthereum: FC = ({ } catch (err) { const error = err as Error & { statusCode?: number; reason?: string } // if user rejects from device - if (error.statusCode === 27013) return + if (error.statusCode === 27013) { + onSentToDevice?.(false) + return + } log.error("ledger sign Ethereum", { error }) @@ -196,7 +199,7 @@ const SignLedgerEthereum: FC = ({ ) else setError(error.reason ?? error.message) } - }, [ledger, onSigned, inputsReady, evmNetworkId, method, payload, account, t]) + }, [ledger, onSigned, inputsReady, evmNetworkId, method, payload, account, t, onSentToDevice]) const handleSendClick = useCallback(() => { setIsSigning(true) From ba0dd65706d0d6f763b25e3c66eff29c371f5b10 Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Wed, 8 Nov 2023 14:06:13 +0900 Subject: [PATCH 48/50] fix: ledger error drawer behavior --- .../ui/domains/Sign/SignLedgerEthereum.tsx | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/apps/extension/src/ui/domains/Sign/SignLedgerEthereum.tsx b/apps/extension/src/ui/domains/Sign/SignLedgerEthereum.tsx index 9d12ffcdcd..dc6660c2bb 100644 --- a/apps/extension/src/ui/domains/Sign/SignLedgerEthereum.tsx +++ b/apps/extension/src/ui/domains/Sign/SignLedgerEthereum.tsx @@ -124,6 +124,25 @@ const signWithLedger = async ( } } +const ErrorDrawer: FC<{ error: string | null; containerId?: string; onClose: () => void }> = ({ + error, + containerId, + onClose, +}) => { + // save error so the content doesn't disappear before the drawer closing animation + const [savedError, setSavedError] = useState() + + useEffect(() => { + if (error) setSavedError(error) + }, [error]) + + return ( + + + + ) +} + const SignLedgerEthereum: FC = ({ evmNetworkId, account, @@ -209,6 +228,11 @@ const SignLedgerEthereum: FC = ({ .finally(() => setIsSigning(false)) }, [onSentToDevice, signLedger]) + const handleClearErrorClick = useCallback(() => { + onSentToDevice?.(false) + setError(null) + }, [onSentToDevice]) + return (
{!error && ( @@ -235,16 +259,7 @@ const SignLedgerEthereum: FC = ({ {t("Cancel")} )} - {error && ( - - {/* Shouldn't be a LedgerSigningStatus, just an error message */} - - - )} +
) } From 87f2be982a3e86a6b919720d66e5e69b2c187a97 Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Wed, 8 Nov 2023 16:07:52 +0900 Subject: [PATCH 49/50] fix: in captures use number type for chain tag --- .../src/core/domains/ethereum/handler.extension.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/extension/src/core/domains/ethereum/handler.extension.ts b/apps/extension/src/core/domains/ethereum/handler.extension.ts index 4f550d61c9..0b8149f038 100644 --- a/apps/extension/src/core/domains/ethereum/handler.extension.ts +++ b/apps/extension/src/core/domains/ethereum/handler.extension.ts @@ -123,7 +123,7 @@ export class EthHandler extends ExtensionHandler { type: "evm sign and send", hostName: ok ? host : null, dapp: url, - chain: ethChainId, + chain: Number(ethChainId), networkType: "ethereum", }) @@ -159,7 +159,7 @@ export class EthHandler extends ExtensionHandler { talismanAnalytics.captureDelayed("send transaction", { type: "evm send signed", - chain: evmNetworkId, + chain: Number(evmNetworkId), networkType: "ethereum", }) @@ -203,7 +203,7 @@ export class EthHandler extends ExtensionHandler { talismanAnalytics.captureDelayed("send transaction", { type: "evm sign and send", - chain: evmNetworkId, + chain: Number(evmNetworkId), networkType: "ethereum", }) @@ -241,7 +241,7 @@ export class EthHandler extends ExtensionHandler { isHardware: true, hostName: ok ? host : null, dapp: url, - chain: queued.ethChainId, + chain: Number(queued.ethChainId), networkType: "ethereum", hardwareType: account?.meta.hardwareType, }) @@ -295,7 +295,7 @@ export class EthHandler extends ExtensionHandler { isHardware: true, hostName: ok ? host : null, dapp: queued.url, - chain: queued.ethChainId, + chain: Number(queued.ethChainId), networkType: "ethereum", }) @@ -325,7 +325,7 @@ export class EthHandler extends ExtensionHandler { talismanAnalytics.captureDelayed("sign reject", { method: queued.method, dapp: queued.url, - chain: queued.ethChainId, + chain: Number(queued.ethChainId), }) return true From a09510eba300fed2a0b07b144f27d7080276c35a Mon Sep 17 00:00:00 2001 From: Kheops <26880866+0xKheops@users.noreply.github.com> Date: Wed, 8 Nov 2023 16:08:24 +0900 Subject: [PATCH 50/50] chore: cleanup --- apps/extension/src/core/domains/ethereum/types.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/apps/extension/src/core/domains/ethereum/types.ts b/apps/extension/src/core/domains/ethereum/types.ts index f22c0f7059..c1ea2e1a9a 100644 --- a/apps/extension/src/core/domains/ethereum/types.ts +++ b/apps/extension/src/core/domains/ethereum/types.ts @@ -74,13 +74,6 @@ type EthRequestSignaturesMap = { ] } -// type EthRequestSignaturesMapBetter = { -// [K in TRpcSchema[number]["Method"]]: { -// parameters: Extract["Parameters"] -// returnType: Extract["ReturnType"] -// } -// } - export type EthRequestSignatures = EthRequestSignaturesMap export type EthRequestMethod = keyof EthRequestSignatures