From 1caabbe4d35cd5c93f4e7423f6a216ced25cebe7 Mon Sep 17 00:00:00 2001 From: Thunnini Date: Fri, 20 Mar 2026 17:30:49 +0900 Subject: [PATCH 01/64] =?UTF-8?q?build:=20upgrade=20TypeScript=205.0.4=20?= =?UTF-8?q?=E2=86=92=205.9.3=20and=20fix=20type=20errors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - typescript: 5.0.4 → ^5.7.0 (resolved 5.9.3) - @typescript-eslint/*: ^5.52.0 → ^7.0.0 - @types/node: ^18.13.0 → ^22.0.0 - eslint: ^8.34.0 → ^8.56.0 (required by @typescript-eslint v7) - eslint-plugin-unused-imports: ^2.0.0 → ^4.0.0 Type error fixes for TS 5.7+ compatibility: - Uint8Array generic: use `let digest: Uint8Array` instead of `let digest = new Uint8Array()` to avoid ArrayBuffer narrowing - keyof with symbol: add typeof guard before .startsWith() and wrap symbol in String() for template literals (provider/inject.ts) - NodeJS.Timer → ReturnType (removed in @types/node 22) - mergeStores: change rest parameter to array parameter to fix spread of recursive conditional type (TS 5.4+ stricter check) - Remove deleted @typescript-eslint/camelcase rule from eslintrc - Add @typescript-eslint/no-unused-vars with underscore ignore pattern - Patch miscreant node_modules via post-install for Uint8Array Co-Authored-By: Claude Opus 4.6 (1M context) --- .eslintrc.js | 7 +- .../build/index.d.ts | 2 +- .../noop-keplr-wallet-private/index.ts | 2 +- apps/extension/src/keplr-wallet-private | 2 +- .../general/link-keplr-mobile/index.tsx | 4 +- package.json | 12 +- .../src/keyring-mnemonic/service.ts | 2 +- .../src/keyring-private-key/service.ts | 2 +- packages/provider/src/inject.ts | 18 +- packages/stores/src/account/store.ts | 2 +- packages/stores/src/common/merge.spec.ts | 32 +- packages/stores/src/common/merge.ts | 2 +- packages/stores/src/query/queries.ts | 2 +- scripts/post-install.mjs | 67 +++ yarn.lock | 465 +++++++++--------- 15 files changed, 361 insertions(+), 260 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 8280d78075..5e03bd7274 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -25,8 +25,11 @@ module.exports = { "@typescript-eslint/no-use-before-define": "off", "@typescript-eslint/no-empty-function": "warn", "@typescript-eslint/explicit-module-boundary-types": "off", - "@typescript-eslint/no-loss-of-precision": "off", - "@typescript-eslint/camelcase": "off", + "@typescript-eslint/no-unused-vars": [ + "warn", + { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }, + ], + "no-loss-of-precision": "off", "react-hooks/rules-of-hooks": "error", "react-hooks/exhaustive-deps": "warn", "unicorn/filename-case": [ diff --git a/apps/extension/noop-keplr-wallet-private/build/index.d.ts b/apps/extension/noop-keplr-wallet-private/build/index.d.ts index e35be1589a..212e25a7c8 100644 --- a/apps/extension/noop-keplr-wallet-private/build/index.d.ts +++ b/apps/extension/noop-keplr-wallet-private/build/index.d.ts @@ -14,7 +14,7 @@ export declare const onGoogleSignInClick: ((sceneTransition: { push(name: string, props?: Record): void; replace(name: string, props?: Record): void; }) => void) | undefined; -export declare const exportGenerateQRCodeDataByInterval: (_data: string, _setQRCodeData: (data: string) => void) => NodeJS.Timer; +export declare const exportGenerateQRCodeDataByInterval: (_data: string, _setQRCodeData: (data: string) => void) => ReturnType; export declare const exportUpload: (_encryptedData: string) => Promise<{ otp: string; } | null>; diff --git a/apps/extension/noop-keplr-wallet-private/index.ts b/apps/extension/noop-keplr-wallet-private/index.ts index 8ec3e16094..50f732feda 100644 --- a/apps/extension/noop-keplr-wallet-private/index.ts +++ b/apps/extension/noop-keplr-wallet-private/index.ts @@ -19,7 +19,7 @@ export const onGoogleSignInClick: export const exportGenerateQRCodeDataByInterval = ( _data: string, _setQRCodeData: (data: string) => void -): NodeJS.Timer => { +): ReturnType => { throw new Error("Method not implemented."); }; diff --git a/apps/extension/src/keplr-wallet-private b/apps/extension/src/keplr-wallet-private index adffba908a..3da2b16bf3 160000 --- a/apps/extension/src/keplr-wallet-private +++ b/apps/extension/src/keplr-wallet-private @@ -1 +1 @@ -Subproject commit adffba908a98d1fd76ea8b6c96dba40cf4e16f15 +Subproject commit 3da2b16bf3a587e49367bfd62fe2358f3508949a diff --git a/apps/extension/src/pages/setting/general/link-keplr-mobile/index.tsx b/apps/extension/src/pages/setting/general/link-keplr-mobile/index.tsx index eaf742499d..0f7400e107 100644 --- a/apps/extension/src/pages/setting/general/link-keplr-mobile/index.tsx +++ b/apps/extension/src/pages/setting/general/link-keplr-mobile/index.tsx @@ -279,7 +279,9 @@ const QRCodeView: FunctionComponent<{ }, [confirm, intl]); useEffect(() => { - let intervalId: NodeJS.Timer | undefined; + let intervalId: + | ReturnType + | undefined; try { (async () => { diff --git a/package.json b/package.json index 85b3d61d1d..8cb4fcd9c8 100644 --- a/package.json +++ b/package.json @@ -64,15 +64,15 @@ "@octokit/core": "^3.5.1", "@types/filesystem": "^0.0.32", "@types/jest": "^29.4.0", - "@types/node": "^18.13.0", + "@types/node": "^22.0.0", "@types/react": "^18.2.19", "@types/react-dom": "^18.2.7", "@types/react-is": "^18.2.1", - "@typescript-eslint/eslint-plugin": "^5.52.0", - "@typescript-eslint/parser": "^5.52.0", + "@typescript-eslint/eslint-plugin": "^7.0.0", + "@typescript-eslint/parser": "^7.0.0", "bitcoinjs-lib": "^6.1.7", "cross-env": "^5.2.0", - "eslint": "^8.34.0", + "eslint": "^8.56.0", "eslint-config-prettier": "^8.6.0", "eslint-plugin-import": "^2.27.5", "eslint-plugin-mdx": "^2.0.5", @@ -80,7 +80,7 @@ "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-unicorn": "^45.0.2", - "eslint-plugin-unused-imports": "^2.0.0", + "eslint-plugin-unused-imports": "^4.0.0", "folder-hash": "^4.0.2", "husky": "^9.1.7", "jest": "^29.4.3", @@ -97,7 +97,7 @@ "semver": "^7.6.0", "starknet": "^8.9.1", "ts-jest": "^29.0.5", - "typescript": "5.0.4", + "typescript": "^5.7.0", "zx": "^4.2.0" }, "resolutions": { diff --git a/packages/background/src/keyring-mnemonic/service.ts b/packages/background/src/keyring-mnemonic/service.ts index d85a8a87f6..95ac92de4e 100644 --- a/packages/background/src/keyring-mnemonic/service.ts +++ b/packages/background/src/keyring-mnemonic/service.ts @@ -140,7 +140,7 @@ export class KeyRingMnemonicService { } { const privKey = this.getPrivKey(vault, purpose, coinType); - let digest = new Uint8Array(); + let digest: Uint8Array; switch (digestMethod) { case "sha256": digest = Hash.sha256(data); diff --git a/packages/background/src/keyring-private-key/service.ts b/packages/background/src/keyring-private-key/service.ts index c26d923c37..e0e85e957c 100644 --- a/packages/background/src/keyring-private-key/service.ts +++ b/packages/background/src/keyring-private-key/service.ts @@ -69,7 +69,7 @@ export class KeyRingPrivateKeyService { ] as string; const privateKey = new PrivKeySecp256k1(Buffer.from(privateKeyText, "hex")); - let digest = new Uint8Array(); + let digest: Uint8Array; switch (digestMethod) { case "sha256": digest = Hash.sha256(data); diff --git a/packages/provider/src/inject.ts b/packages/provider/src/inject.ts index cc1e0571a6..636039923f 100644 --- a/packages/provider/src/inject.ts +++ b/packages/provider/src/inject.ts @@ -286,7 +286,10 @@ export class InjectedKeplr implements IKeplr, KeplrCoreTypes { if (method === "ethereum") { const ethereumProviderMethod = message.ethereumProviderMethod; - if (ethereumProviderMethod?.startsWith("protected")) { + if ( + typeof ethereumProviderMethod === "string" && + ethereumProviderMethod.startsWith("protected") + ) { throw new Error("Rejected"); } @@ -315,7 +318,9 @@ export class InjectedKeplr implements IKeplr, KeplrCoreTypes { typeof keplr.ethereum[ethereumProviderMethod] !== "function" ) { throw new Error( - `${message.ethereumProviderMethod} is not function or invalid Ethereum provider method` + `${String( + message.ethereumProviderMethod + )} is not function or invalid Ethereum provider method` ); } @@ -410,7 +415,10 @@ export class InjectedKeplr implements IKeplr, KeplrCoreTypes { if (method === "bitcoin") { const bitcoinProviderMethod = message.bitcoinProviderMethod; - if (bitcoinProviderMethod?.startsWith("protected")) { + if ( + typeof bitcoinProviderMethod === "string" && + bitcoinProviderMethod.startsWith("protected") + ) { throw new Error("Rejected"); } @@ -419,7 +427,9 @@ export class InjectedKeplr implements IKeplr, KeplrCoreTypes { typeof keplr.bitcoin?.[bitcoinProviderMethod] !== "function" ) { throw new Error( - `${message.bitcoinProviderMethod} is not function or invalid Bitcoin provider method` + `${String( + message.bitcoinProviderMethod + )} is not function or invalid Bitcoin provider method` ); } diff --git a/packages/stores/src/account/store.ts b/packages/stores/src/account/store.ts index 361a46b658..0b8f1249e5 100644 --- a/packages/stores/src/account/store.ts +++ b/packages/stores/src/account/store.ts @@ -65,7 +65,7 @@ export class AccountStore< return mergeStores( accountSetBase, [this.chainGetter, chainId], - ...this.accountSetCreators + this.accountSetCreators ); }); diff --git a/packages/stores/src/common/merge.spec.ts b/packages/stores/src/common/merge.spec.ts index c2336f9146..380b837070 100644 --- a/packages/stores/src/common/merge.spec.ts +++ b/packages/stores/src/common/merge.spec.ts @@ -23,21 +23,23 @@ describe("Test merge stores function", () => { base: true, }, [3, "test"], - (base, n) => { - return { - test1: base.base ? n : 0, - }; - }, - (base) => { - return { - test2: base.test1 === 3 ? 4 : 0, - }; - }, - (_base, _n, str) => { - return { - test3: str, - }; - } + [ + (base, n) => { + return { + test1: base.base ? n : 0, + }; + }, + (base) => { + return { + test2: base.test1 === 3 ? 4 : 0, + }; + }, + (_base, _n, str) => { + return { + test3: str, + }; + }, + ] ); expect(result.test1).toBe(3); diff --git a/packages/stores/src/common/merge.ts b/packages/stores/src/common/merge.ts index 6752a14d2d..8ef9687cc0 100644 --- a/packages/stores/src/common/merge.ts +++ b/packages/stores/src/common/merge.ts @@ -71,7 +71,7 @@ export const mergeStores = < >( baseStore: Base, parameters: Params, - ...fns: ChainedFunctionifyTuple + fns: ChainedFunctionifyTuple ): Return => { for (let i = 0; i < fns.length; i++) { const fn = fns[i] as Functionify; diff --git a/packages/stores/src/query/queries.ts b/packages/stores/src/query/queries.ts index 1a2ce707cc..e581438a82 100644 --- a/packages/stores/src/query/queries.ts +++ b/packages/stores/src/query/queries.ts @@ -94,7 +94,7 @@ export class QueriesStore> { const merged = mergeStores( queriesSetBase, [this.sharedContext, chainId, this.chainGetter], - ...this.queriesCreators + this.queriesCreators ); this.queriesMap.set(chainId, merged); diff --git a/scripts/post-install.mjs b/scripts/post-install.mjs index 3a051e6d3c..85d9f770d4 100644 --- a/scripts/post-install.mjs +++ b/scripts/post-install.mjs @@ -31,10 +31,77 @@ import path from "path"; await fs.writeFile(p, "// @ts-nocheck\n" + data); } } + // miscreant 패키지의 .ts 소스에서 TS 5.7+ TypedArray 제네릭 변경으로 인한 타입 에러 수정 + await patchMiscreant(); } catch (e) { console.log(e); process.exit(1); } })(); +async function patchMiscreant() { + const findNodeModulesFile = (relativePath) => { + const maxDepth = 6; + let currentDir = __dirname; + for (let i = 0; i < maxDepth; i++) { + const filePath = path.join(currentDir, "node_modules", relativePath); + if (fs.existsSync(filePath)) { + return filePath; + } + currentDir = path.join(currentDir, ".."); + } + return null; + }; + + // 1) polyfill/aes.ts: writeUint32BE의 out 파라미터에 명시적 Uint8Array 타입 추가 + const polyfillAes = findNodeModulesFile( + "miscreant/src/providers/polyfill/aes.ts" + ); + if (polyfillAes) { + let data = await fs.readFile(polyfillAes, "utf8"); + const oldSig = + "function writeUint32BE(value: number, out = new Uint8Array(4), offset = 0): Uint8Array {"; + const newSig = + "function writeUint32BE(value: number, out: Uint8Array = new Uint8Array(4), offset = 0): Uint8Array {"; + if (data.includes(oldSig)) { + data = data.replace(oldSig, newSig); + await fs.writeFile(polyfillAes, data); + } + } + + // 2) webcrypto/aes.ts: importKey와 encrypt 호출에서 Uint8Array를 BufferSource로 캐스트 + const webcryptoAes = findNodeModulesFile( + "miscreant/src/providers/webcrypto/aes.ts" + ); + if (webcryptoAes) { + let data = await fs.readFile(webcryptoAes, "utf8"); + data = data.replace( + 'const key = await crypto.subtle.importKey("raw", keyData, "AES-CBC", false, ["encrypt"]);', + 'const key = await crypto.subtle.importKey("raw", keyData as unknown as BufferSource, "AES-CBC", false, ["encrypt"]);' + ); + data = data.replace( + "const ctBlock = await this._crypto.subtle.encrypt(params, this._key, block.data);", + "const ctBlock = await this._crypto.subtle.encrypt(params, this._key, block.data as unknown as BufferSource);" + ); + await fs.writeFile(webcryptoAes, data); + } + + // 3) webcrypto/aes_ctr.ts: importKey와 encrypt 호출에서 동일 수정 + const webcryptoAesCtr = findNodeModulesFile( + "miscreant/src/providers/webcrypto/aes_ctr.ts" + ); + if (webcryptoAesCtr) { + let data = await fs.readFile(webcryptoAesCtr, "utf8"); + data = data.replace( + 'const key = await crypto.subtle.importKey("raw", keyData, "AES-CTR", false, ["encrypt"]);', + 'const key = await crypto.subtle.importKey("raw", keyData as unknown as BufferSource, "AES-CTR", false, ["encrypt"]);' + ); + data = data.replace( + 'const ciphertext = await this.crypto.subtle.encrypt(\n { name: "AES-CTR", counter: iv, length: 16 },\n this.key,\n plaintext,\n );', + 'const ciphertext = await this.crypto.subtle.encrypt(\n { name: "AES-CTR", counter: iv as unknown as BufferSource, length: 16 },\n this.key,\n plaintext as unknown as BufferSource,\n );' + ); + await fs.writeFile(webcryptoAesCtr, data); + } +} + /* eslint-enable import/no-extraneous-dependencies, @typescript-eslint/no-var-requires */ diff --git a/yarn.lock b/yarn.lock index 2052c6da8a..84234deea0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4393,20 +4393,45 @@ __metadata: languageName: node linkType: hard -"@eslint/eslintrc@npm:^1.4.1": - version: 1.4.1 - resolution: "@eslint/eslintrc@npm:1.4.1" +"@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0": + version: 4.9.1 + resolution: "@eslint-community/eslint-utils@npm:4.9.1" + dependencies: + eslint-visitor-keys: ^3.4.3 + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + checksum: 0a27c2d676c4be6b329ebb5dd8f6c5ef5fae9a019ff575655306d72874bb26f3ab20e0b241a5f086464bb1f2511ca26a29ff6f80c1e2b0b02eca4686b4dfe1b5 + languageName: node + linkType: hard + +"@eslint-community/regexpp@npm:^4.10.0, @eslint-community/regexpp@npm:^4.6.1": + version: 4.12.2 + resolution: "@eslint-community/regexpp@npm:4.12.2" + checksum: 1770bc81f676a72f65c7200b5675ff7a349786521f30e66125faaf767fde1ba1c19c3790e16ba8508a62a3933afcfc806a893858b3b5906faf693d862b9e4120 + languageName: node + linkType: hard + +"@eslint/eslintrc@npm:^2.1.4": + version: 2.1.4 + resolution: "@eslint/eslintrc@npm:2.1.4" dependencies: ajv: ^6.12.4 debug: ^4.3.2 - espree: ^9.4.0 + 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: cd3e5a8683db604739938b1c1c8b77927dc04fce3e28e0c88e7f2cd4900b89466baf83dfbad76b2b9e4d2746abdd00dd3f9da544d3e311633d8693f327d04cd7 + checksum: 10957c7592b20ca0089262d8c2a8accbad14b4f6507e35416c32ee6b4dbf9cad67dfb77096bbd405405e9ada2b107f3797fe94362e1c55e0b09d6e90dd149127 + languageName: node + linkType: hard + +"@eslint/js@npm:8.57.1": + version: 8.57.1 + resolution: "@eslint/js@npm:8.57.1" + checksum: 2afb77454c06e8316793d2e8e79a0154854d35e6782a1217da274ca60b5044d2c69d6091155234ed0551a1e408f86f09dd4ece02752c59568fa403e60611e880 languageName: node linkType: hard @@ -5233,14 +5258,14 @@ __metadata: languageName: node linkType: hard -"@humanwhocodes/config-array@npm:^0.11.8": - version: 0.11.8 - resolution: "@humanwhocodes/config-array@npm:0.11.8" +"@humanwhocodes/config-array@npm:^0.13.0": + version: 0.13.0 + resolution: "@humanwhocodes/config-array@npm:0.13.0" dependencies: - "@humanwhocodes/object-schema": ^1.2.1 - debug: ^4.1.1 + "@humanwhocodes/object-schema": ^2.0.3 + debug: ^4.3.1 minimatch: ^3.0.5 - checksum: 0fd6b3c54f1674ce0a224df09b9c2f9846d20b9e54fabae1281ecfc04f2e6ad69bf19e1d6af6a28f88e8aa3990168b6cb9e1ef755868c3256a630605ec2cb1d3 + checksum: eae69ff9134025dd2924f0b430eb324981494be26f0fddd267a33c28711c4db643242cf9fddf7dadb9d16c96b54b2d2c073e60a56477df86e0173149313bd5d6 languageName: node linkType: hard @@ -5251,10 +5276,10 @@ __metadata: languageName: node linkType: hard -"@humanwhocodes/object-schema@npm:^1.2.1": - version: 1.2.1 - resolution: "@humanwhocodes/object-schema@npm:1.2.1" - checksum: a824a1ec31591231e4bad5787641f59e9633827d0a2eaae131a288d33c9ef0290bd16fda8da6f7c0fcb014147865d12118df10db57f27f41e20da92369fcb3f1 +"@humanwhocodes/object-schema@npm:^2.0.3": + version: 2.0.3 + resolution: "@humanwhocodes/object-schema@npm:2.0.3" + checksum: d3b78f6c5831888c6ecc899df0d03bcc25d46f3ad26a11d7ea52944dc36a35ef543fad965322174238d677a43d5c694434f6607532cff7077062513ad7022631 languageName: node linkType: hard @@ -11396,13 +11421,22 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^18.0.0, @types/node@npm:^18.13.0": +"@types/node@npm:^18.0.0": version: 18.13.0 resolution: "@types/node@npm:18.13.0" checksum: 4ea10f8802848b01672bce938f678b6774ca2cee0c9774f12275ab064ae07818419c3e2e41d6257ce7ba846d1ea26c63214aa1dfa4166fa3746291752b8c6416 languageName: node linkType: hard +"@types/node@npm:^22.0.0": + version: 22.19.15 + resolution: "@types/node@npm:22.19.15" + dependencies: + undici-types: ~6.21.0 + checksum: 6e78cdb6cec95770432aceff75b349610b2a8c6e3b661fa6643afa199e49b2ada081546b74c79f3b1bd922fbf5e1b52f7714f6d9d8d275d2083578821328d33c + languageName: node + linkType: hard + "@types/normalize-package-data@npm:^2.4.0": version: 2.4.0 resolution: "@types/normalize-package-data@npm:2.4.0" @@ -11566,13 +11600,6 @@ __metadata: languageName: node linkType: hard -"@types/semver@npm:^7.3.12": - version: 7.3.13 - resolution: "@types/semver@npm:7.3.13" - checksum: 00c0724d54757c2f4bc60b5032fe91cda6410e48689633d5f35ece8a0a66445e3e57fa1d6e07eb780f792e82ac542948ec4d0b76eb3484297b79bd18b8cf1cb0 - languageName: node - linkType: hard - "@types/semver@npm:^7.3.4": version: 7.5.8 resolution: "@types/semver@npm:7.5.8" @@ -11688,124 +11715,128 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/eslint-plugin@npm:^5.52.0": - version: 5.52.0 - resolution: "@typescript-eslint/eslint-plugin@npm:5.52.0" +"@typescript-eslint/eslint-plugin@npm:^7.0.0": + version: 7.18.0 + resolution: "@typescript-eslint/eslint-plugin@npm:7.18.0" dependencies: - "@typescript-eslint/scope-manager": 5.52.0 - "@typescript-eslint/type-utils": 5.52.0 - "@typescript-eslint/utils": 5.52.0 - debug: ^4.3.4 - grapheme-splitter: ^1.0.4 - ignore: ^5.2.0 - natural-compare-lite: ^1.4.0 - regexpp: ^3.2.0 - semver: ^7.3.7 - tsutils: ^3.21.0 + "@eslint-community/regexpp": ^4.10.0 + "@typescript-eslint/scope-manager": 7.18.0 + "@typescript-eslint/type-utils": 7.18.0 + "@typescript-eslint/utils": 7.18.0 + "@typescript-eslint/visitor-keys": 7.18.0 + graphemer: ^1.4.0 + ignore: ^5.3.1 + natural-compare: ^1.4.0 + ts-api-utils: ^1.3.0 peerDependencies: - "@typescript-eslint/parser": ^5.0.0 - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + "@typescript-eslint/parser": ^7.0.0 + eslint: ^8.56.0 peerDependenciesMeta: typescript: optional: true - checksum: cff07ee94d8ab2a1b6c33b5c5bf641eff2bf2bebc0f35a9d8b3f128fd610e27a4aaf620bc2ad23608ad161b1810b7e32e5a2e0f746cc5094c3f506f7a14daa34 + checksum: dfcf150628ca2d4ccdfc20b46b0eae075c2f16ef5e70d9d2f0d746acf4c69a09f962b93befee01a529f14bbeb3e817b5aba287d7dd0edc23396bc5ed1f448c3d languageName: node linkType: hard -"@typescript-eslint/parser@npm:^5.52.0": - version: 5.52.0 - resolution: "@typescript-eslint/parser@npm:5.52.0" +"@typescript-eslint/parser@npm:^7.0.0": + version: 7.18.0 + resolution: "@typescript-eslint/parser@npm:7.18.0" dependencies: - "@typescript-eslint/scope-manager": 5.52.0 - "@typescript-eslint/types": 5.52.0 - "@typescript-eslint/typescript-estree": 5.52.0 + "@typescript-eslint/scope-manager": 7.18.0 + "@typescript-eslint/types": 7.18.0 + "@typescript-eslint/typescript-estree": 7.18.0 + "@typescript-eslint/visitor-keys": 7.18.0 debug: ^4.3.4 peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + eslint: ^8.56.0 peerDependenciesMeta: typescript: optional: true - checksum: 1d8ff6e932f9c9db8d24b16ce89fd963f0982c38559e500aa1f8dc5cd66abd02f1659dd1a1361ce550def05331803caa69a69a039b54c94fc0f22919a2305c12 + checksum: 132b56ac3b2d90b588d61d005a70f6af322860974225b60201cbf45abf7304d67b7d8a6f0ade1c188ac4e339884e78d6dcd450417f1481998f9ddd155bab0801 languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:5.52.0": - version: 5.52.0 - resolution: "@typescript-eslint/scope-manager@npm:5.52.0" +"@typescript-eslint/scope-manager@npm:7.18.0": + version: 7.18.0 + resolution: "@typescript-eslint/scope-manager@npm:7.18.0" dependencies: - "@typescript-eslint/types": 5.52.0 - "@typescript-eslint/visitor-keys": 5.52.0 - checksum: 9a03fe30f8e90a5106c482478f213eefdd09f2f74e24d9dc59b453885466a758fe6d1cd24d706aed6188fb03c84b16ca6491cf20da6b16b8fc53cad8b8c327f2 + "@typescript-eslint/types": 7.18.0 + "@typescript-eslint/visitor-keys": 7.18.0 + checksum: b982c6ac13d8c86bb3b949c6b4e465f3f60557c2ccf4cc229799827d462df56b9e4d3eaed7711d79b875422fc3d71ec1ebcb5195db72134d07c619e3c5506b57 languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:5.52.0": - version: 5.52.0 - resolution: "@typescript-eslint/type-utils@npm:5.52.0" +"@typescript-eslint/type-utils@npm:7.18.0": + version: 7.18.0 + resolution: "@typescript-eslint/type-utils@npm:7.18.0" dependencies: - "@typescript-eslint/typescript-estree": 5.52.0 - "@typescript-eslint/utils": 5.52.0 + "@typescript-eslint/typescript-estree": 7.18.0 + "@typescript-eslint/utils": 7.18.0 debug: ^4.3.4 - tsutils: ^3.21.0 + ts-api-utils: ^1.3.0 peerDependencies: - eslint: "*" + eslint: ^8.56.0 peerDependenciesMeta: typescript: optional: true - checksum: ac5422040461febab8a2eeec76d969024ccff76203dec357f7220c9b5e0dde96e3e3a76fd4118d42b50bd5bfb3a194aaceeb63417a2ac4e1ebf5e687558a9a10 + checksum: 68fd5df5146c1a08cde20d59b4b919acab06a1b06194fe4f7ba1b928674880249890785fbbc97394142f2ef5cff5a7fba9b8a940449e7d5605306505348e38bc languageName: node linkType: hard -"@typescript-eslint/types@npm:5.52.0": - version: 5.52.0 - resolution: "@typescript-eslint/types@npm:5.52.0" - checksum: 018940d61aebf7cf3f7de1b9957446e2ea01f08fe950bef4788c716a3a88f7c42765fe7d80152b0d0428fcd4bd3ace2dfa8c459ba1c59d9a84e951642180f869 +"@typescript-eslint/types@npm:7.18.0": + version: 7.18.0 + resolution: "@typescript-eslint/types@npm:7.18.0" + checksum: 7df2750cd146a0acd2d843208d69f153b458e024bbe12aab9e441ad2c56f47de3ddfeb329c4d1ea0079e2577fea4b8c1c1ce15315a8d49044586b04fedfe7a4d languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:5.52.0": - version: 5.52.0 - resolution: "@typescript-eslint/typescript-estree@npm:5.52.0" +"@typescript-eslint/typescript-estree@npm:7.18.0": + version: 7.18.0 + resolution: "@typescript-eslint/typescript-estree@npm:7.18.0" dependencies: - "@typescript-eslint/types": 5.52.0 - "@typescript-eslint/visitor-keys": 5.52.0 + "@typescript-eslint/types": 7.18.0 + "@typescript-eslint/visitor-keys": 7.18.0 debug: ^4.3.4 globby: ^11.1.0 is-glob: ^4.0.3 - semver: ^7.3.7 - tsutils: ^3.21.0 + minimatch: ^9.0.4 + semver: ^7.6.0 + ts-api-utils: ^1.3.0 peerDependenciesMeta: typescript: optional: true - checksum: 67d396907fee3d6894e26411a5098a37f07e5d50343189e6361ff7db91c74a7ffe2abd630d11f14c2bda1f4af13edf52b80b11cbccb55b44079c7cec14c9e108 + checksum: c82d22ec9654973944f779eb4eb94c52f4a6eafaccce2f0231ff7757313f3a0d0256c3252f6dfe6d43f57171d09656478acb49a629a9d0c193fb959bc3f36116 languageName: node linkType: hard -"@typescript-eslint/utils@npm:5.52.0": - version: 5.52.0 - resolution: "@typescript-eslint/utils@npm:5.52.0" +"@typescript-eslint/utils@npm:7.18.0": + version: 7.18.0 + resolution: "@typescript-eslint/utils@npm:7.18.0" dependencies: - "@types/json-schema": ^7.0.9 - "@types/semver": ^7.3.12 - "@typescript-eslint/scope-manager": 5.52.0 - "@typescript-eslint/types": 5.52.0 - "@typescript-eslint/typescript-estree": 5.52.0 - eslint-scope: ^5.1.1 - eslint-utils: ^3.0.0 - semver: ^7.3.7 + "@eslint-community/eslint-utils": ^4.4.0 + "@typescript-eslint/scope-manager": 7.18.0 + "@typescript-eslint/types": 7.18.0 + "@typescript-eslint/typescript-estree": 7.18.0 peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - checksum: 01906be5262ece36537e9d586e4d2d4791e05752a9354bcb42b1f5bf965f53daa13309c61c3dff5e201ea28c298e4e01cf0c93738afa0099fea0da3b1d8cb3a5 + eslint: ^8.56.0 + checksum: 751dbc816dab8454b7dc6b26a56671dbec08e3f4ef94c2661ce1c0fc48fa2d05a64e03efe24cba2c22d03ba943cd3c5c7a5e1b7b03bbb446728aec1c640bd767 languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:5.52.0": - version: 5.52.0 - resolution: "@typescript-eslint/visitor-keys@npm:5.52.0" +"@typescript-eslint/visitor-keys@npm:7.18.0": + version: 7.18.0 + resolution: "@typescript-eslint/visitor-keys@npm:7.18.0" dependencies: - "@typescript-eslint/types": 5.52.0 - eslint-visitor-keys: ^3.3.0 - checksum: 33b44f0cd35b7b47f34e89d52e47b8d8200f55af306b22db4de104d79f65907458ea022e548f50d966e32fea150432ac9c1ae65b3001b0ad2ac8a17c0211f370 + "@typescript-eslint/types": 7.18.0 + eslint-visitor-keys: ^3.4.3 + checksum: 6e806a7cdb424c5498ea187a5a11d0fef7e4602a631be413e7d521e5aec1ab46ba00c76cfb18020adaa0a8c9802354a163bfa0deb74baa7d555526c7517bb158 + languageName: node + linkType: hard + +"@ungap/structured-clone@npm:^1.2.0": + version: 1.3.0 + resolution: "@ungap/structured-clone@npm:1.3.0" + checksum: 64ed518f49c2b31f5b50f8570a1e37bde3b62f2460042c50f132430b2d869c4a6586f13aa33a58a4722715b8158c68cae2827389d6752ac54da2893c83e480fc languageName: node linkType: hard @@ -12606,6 +12637,15 @@ __metadata: languageName: node linkType: hard +"acorn@npm:^8.9.0": + version: 8.16.0 + resolution: "acorn@npm:8.16.0" + bin: + acorn: bin/acorn + checksum: bbfa466cd0dbd18b4460a85e9d0fc2f35db999380892403c573261beda91f23836db2aa71fd3ae65e94424ad14ff8e2b7bd37c7a2624278fd89137cd6e448c41 + languageName: node + linkType: hard + "add-stream@npm:^1.0.0": version: 1.0.0 resolution: "add-stream@npm:1.0.0" @@ -12712,7 +12752,7 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^6.10.0, ajv@npm:^6.12.3, ajv@npm:^6.12.4, ajv@npm:^6.12.5": +"ajv@npm:^6.12.3, ajv@npm:^6.12.4, ajv@npm:^6.12.5": version: 6.12.6 resolution: "ajv@npm:6.12.6" dependencies: @@ -17866,29 +17906,20 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-unused-imports@npm:^2.0.0": - version: 2.0.0 - resolution: "eslint-plugin-unused-imports@npm:2.0.0" - dependencies: - eslint-rule-composer: ^0.3.0 +"eslint-plugin-unused-imports@npm:^4.0.0": + version: 4.4.1 + resolution: "eslint-plugin-unused-imports@npm:4.4.1" peerDependencies: - "@typescript-eslint/eslint-plugin": ^5.0.0 - eslint: ^8.0.0 + "@typescript-eslint/eslint-plugin": ^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0 + eslint: ^10.0.0 || ^9.0.0 || ^8.0.0 peerDependenciesMeta: "@typescript-eslint/eslint-plugin": optional: true - checksum: 8aa1e03e75da2a62a354065e0cb8fe370118c6f8d9720a32fe8c1da937de6adb81a4fed7d0d391d115ac9453b49029fb19f970d180a2cf3dba451fd4c20f0dc4 + checksum: b44557555462a0af1cd6b339fb3afdc30ad7e7ceb9a5b1f64737183b1b397acfd065e1f5838d0384dd239c8db84927feb0b289d0c4f453983065e215246fc6e6 languageName: node linkType: hard -"eslint-rule-composer@npm:^0.3.0": - version: 0.3.0 - resolution: "eslint-rule-composer@npm:0.3.0" - checksum: c2f57cded8d1c8f82483e0ce28861214347e24fd79fd4144667974cd334d718f4ba05080aaef2399e3bbe36f7d6632865110227e6b176ed6daa2d676df9281b1 - languageName: node - linkType: hard - -"eslint-scope@npm:5.1.1, eslint-scope@npm:^5.1.1": +"eslint-scope@npm:5.1.1": version: 5.1.1 resolution: "eslint-scope@npm:5.1.1" dependencies: @@ -17898,31 +17929,13 @@ __metadata: languageName: node linkType: hard -"eslint-scope@npm:^7.1.1": - version: 7.1.1 - resolution: "eslint-scope@npm:7.1.1" +"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: 9f6e974ab2db641ca8ab13508c405b7b859e72afe9f254e8131ff154d2f40c99ad4545ce326fd9fde3212ff29707102562a4834f1c48617b35d98c71a97fbf3e - languageName: node - linkType: hard - -"eslint-utils@npm:^3.0.0": - version: 3.0.0 - resolution: "eslint-utils@npm:3.0.0" - dependencies: - eslint-visitor-keys: ^2.0.0 - peerDependencies: - eslint: ">=5" - checksum: 0668fe02f5adab2e5a367eee5089f4c39033af20499df88fe4e6aba2015c20720404d8c3d6349b6f716b08fdf91b9da4e5d5481f265049278099c4c836ccb619 - languageName: node - linkType: hard - -"eslint-visitor-keys@npm:^2.0.0": - version: 2.0.0 - resolution: "eslint-visitor-keys@npm:2.0.0" - checksum: e07e9863fb8c9b1453f5ad1a26f3cc8dd6b349b26605cc06bc0c61215ac5b6f13a4d08c875218e6c0f8ac8fc06ca6e090df769e32c569f0fd2e6a848b8a76c75 + checksum: ec97dbf5fb04b94e8f4c5a91a7f0a6dd3c55e46bfc7bbcd0e3138c3a76977570e02ed89a1810c778dcd72072ff0e9621ba1379b4babe53921d71e2e4486fda3e languageName: node linkType: hard @@ -17933,52 +17946,58 @@ __metadata: languageName: node linkType: hard -"eslint@npm:^8.34.0": - version: 8.34.0 - resolution: "eslint@npm:8.34.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@npm:^8.56.0": + version: 8.57.1 + resolution: "eslint@npm:8.57.1" dependencies: - "@eslint/eslintrc": ^1.4.1 - "@humanwhocodes/config-array": ^0.11.8 + "@eslint-community/eslint-utils": ^4.2.0 + "@eslint-community/regexpp": ^4.6.1 + "@eslint/eslintrc": ^2.1.4 + "@eslint/js": 8.57.1 + "@humanwhocodes/config-array": ^0.13.0 "@humanwhocodes/module-importer": ^1.0.1 "@nodelib/fs.walk": ^1.2.8 - ajv: ^6.10.0 + "@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.1.1 - eslint-utils: ^3.0.0 - eslint-visitor-keys: ^3.3.0 - espree: ^9.4.0 - esquery: ^1.4.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 - grapheme-splitter: ^1.0.4 + graphemer: ^1.4.0 ignore: ^5.2.0 - import-fresh: ^3.0.0 imurmurhash: ^0.1.4 is-glob: ^4.0.0 is-path-inside: ^3.0.3 - js-sdsl: ^4.1.4 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.1 - regexpp: ^3.2.0 + optionator: ^0.9.3 strip-ansi: ^6.0.1 - strip-json-comments: ^3.1.0 text-table: ^0.2.0 bin: eslint: bin/eslint.js - checksum: 4e13e9eb05ac2248efbb6acae0b2325091235d5c47ba91a4775c7d6760778cbcd358a773ebd42f4629d2ad89e27c02f5d66eb1f737d75d9f5fc411454f83b2e5 + checksum: e2489bb7f86dd2011967759a09164e65744ef7688c310bc990612fc26953f34cc391872807486b15c06833bdff737726a23e9b4cdba5de144c311377dc41d91b languageName: node linkType: hard @@ -17993,6 +18012,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:^3.1.3": version: 3.1.3 resolution: "esprima@npm:3.1.3" @@ -18022,6 +18052,15 @@ __metadata: languageName: node linkType: hard +"esquery@npm:^1.4.2": + version: 1.7.0 + resolution: "esquery@npm:1.7.0" + dependencies: + estraverse: ^5.1.0 + checksum: 3239792b68cf39fe18966d0ca01549bb15556734f0144308fd213739b0f153671ae916013fce0bca032044a4dbcda98b43c1c667f20c20a54dec3597ac0d7c27 + languageName: node + linkType: hard + "esrecurse@npm:^4.3.0": version: 4.3.0 resolution: "esrecurse@npm:4.3.0" @@ -19725,10 +19764,10 @@ __metadata: languageName: node linkType: hard -"grapheme-splitter@npm:^1.0.4": - version: 1.0.4 - resolution: "grapheme-splitter@npm:1.0.4" - checksum: 0c22ec54dee1b05cd480f78cf14f732cb5b108edc073572c4ec205df4cd63f30f8db8025afc5debc8835a8ddeacf648a1c7992fe3dcd6ad38f9a476d84906620 +"graphemer@npm:^1.4.0": + version: 1.4.0 + resolution: "graphemer@npm:1.4.0" + checksum: bab8f0be9b568857c7bec9fda95a89f87b783546d02951c40c33f84d05bb7da3fd10f863a9beb901463669b6583173a8c8cc6d6b306ea2b9b9d5d3d943c3a673 languageName: node linkType: hard @@ -20489,6 +20528,13 @@ __metadata: languageName: node linkType: hard +"ignore@npm:^5.3.1": + version: 5.3.2 + resolution: "ignore@npm:5.3.2" + checksum: 2acfd32a573260ea522ea0bfeff880af426d68f6831f973129e2ba7363f422923cf53aab62f8369cbf4667c7b25b6f8a3761b34ecdb284ea18e87a5262a865be + languageName: node + linkType: hard + "ignore@npm:^7.0.5": version: 7.0.5 resolution: "ignore@npm:7.0.5" @@ -20506,7 +20552,7 @@ __metadata: languageName: node linkType: hard -"import-fresh@npm:^3.0.0, import-fresh@npm:^3.2.1": +"import-fresh@npm:^3.2.1": version: 3.3.0 resolution: "import-fresh@npm:3.3.0" dependencies: @@ -22368,13 +22414,6 @@ __metadata: languageName: node linkType: hard -"js-sdsl@npm:^4.1.4": - version: 4.3.0 - resolution: "js-sdsl@npm:4.3.0" - checksum: ce908257cf6909e213af580af3a691a736f5ee8b16315454768f917a682a4ea0c11bde1b241bbfaecedc0eb67b72101b2c2df2ffaed32aed5d539fca816f054e - languageName: node - linkType: hard - "js-sha3@npm:0.8.0": version: 0.8.0 resolution: "js-sha3@npm:0.8.0" @@ -22751,12 +22790,12 @@ __metadata: "keplr-wallet-private@file:./noop-keplr-wallet-private::locator=%40keplr-wallet%2Fextension%40workspace%3Aapps%2Fextension": version: 0.0.1 - resolution: "keplr-wallet-private@file:./noop-keplr-wallet-private#./noop-keplr-wallet-private::hash=28117a&locator=%40keplr-wallet%2Fextension%40workspace%3Aapps%2Fextension" + resolution: "keplr-wallet-private@file:./noop-keplr-wallet-private#./noop-keplr-wallet-private::hash=ae7095&locator=%40keplr-wallet%2Fextension%40workspace%3Aapps%2Fextension" peerDependencies: "@keplr-wallet/simple-fetch": "*" react: ^16.8.0 || ^17 || ^18 styled-components: "*" - checksum: 12e86c8412aa82b1446e73c24d3ad4cd3fbd22b12e06b7821595dc80ebaec0ee0f18f9a2d2548677c7e665a862fc4321f20155ca4ba697ee62bc70b76612d196 + checksum: f5edef51d6629fe7a83c9112e2387cf37b633bd8fdd5b1d6ddb73db4e1aebe81738410dd7a618b1e72477496b0dda0bd76d0fbddc179d07ea6d8aa928ca7b336 languageName: node linkType: hard @@ -22768,15 +22807,15 @@ __metadata: "@octokit/core": ^3.5.1 "@types/filesystem": ^0.0.32 "@types/jest": ^29.4.0 - "@types/node": ^18.13.0 + "@types/node": ^22.0.0 "@types/react": ^18.2.19 "@types/react-dom": ^18.2.7 "@types/react-is": ^18.2.1 - "@typescript-eslint/eslint-plugin": ^5.52.0 - "@typescript-eslint/parser": ^5.52.0 + "@typescript-eslint/eslint-plugin": ^7.0.0 + "@typescript-eslint/parser": ^7.0.0 bitcoinjs-lib: ^6.1.7 cross-env: ^5.2.0 - eslint: ^8.34.0 + eslint: ^8.56.0 eslint-config-prettier: ^8.6.0 eslint-plugin-import: ^2.27.5 eslint-plugin-mdx: ^2.0.5 @@ -22784,7 +22823,7 @@ __metadata: eslint-plugin-react: ^7.32.2 eslint-plugin-react-hooks: ^4.6.0 eslint-plugin-unicorn: ^45.0.2 - eslint-plugin-unused-imports: ^2.0.0 + eslint-plugin-unused-imports: ^4.0.0 folder-hash: ^4.0.2 husky: ^9.1.7 jest: ^29.4.3 @@ -22801,7 +22840,7 @@ __metadata: semver: ^7.6.0 starknet: ^8.9.1 ts-jest: ^29.0.5 - typescript: 5.0.4 + typescript: ^5.7.0 zx: ^4.2.0 languageName: unknown linkType: soft @@ -25247,13 +25286,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" @@ -26334,17 +26366,17 @@ __metadata: languageName: node linkType: hard -"optionator@npm:^0.9.1": - version: 0.9.1 - resolution: "optionator@npm:0.9.1" +"optionator@npm:^0.9.3": + version: 0.9.4 + resolution: "optionator@npm:0.9.4" dependencies: deep-is: ^0.1.3 fast-levenshtein: ^2.0.6 levn: ^0.4.1 prelude-ls: ^1.2.1 type-check: ^0.4.0 - word-wrap: ^1.2.3 - checksum: dbc6fa065604b24ea57d734261914e697bd73b69eff7f18e967e8912aa2a40a19a9f599a507fa805be6c13c24c4eae8c71306c239d517d42d4c041c942f508a0 + word-wrap: ^1.2.5 + checksum: ecbd010e3dc73e05d239976422d9ef54a82a13f37c11ca5911dff41c98a6c7f0f163b27f922c37e7f8340af9d36febd3b6e9cef508f3339d4c393d7276d716bb languageName: node linkType: hard @@ -29061,13 +29093,6 @@ __metadata: languageName: node linkType: hard -"regexpp@npm:^3.2.0": - version: 3.2.0 - resolution: "regexpp@npm:3.2.0" - checksum: a78dc5c7158ad9ddcfe01aa9144f46e192ddbfa7b263895a70a5c6c73edd9ce85faf7c0430e59ac38839e1734e275b9c3de5c57ee3ab6edc0e0b1bdebefccef8 - languageName: node - linkType: hard - "regexpu-core@npm:^4.7.1": version: 4.7.1 resolution: "regexpu-core@npm:4.7.1" @@ -32032,7 +32057,7 @@ __metadata: languageName: node linkType: hard -"strip-json-comments@npm:^3.0.1, strip-json-comments@npm:^3.1.0, strip-json-comments@npm:^3.1.1": +"strip-json-comments@npm:^3.0.1, strip-json-comments@npm:^3.1.1": version: 3.1.1 resolution: "strip-json-comments@npm:3.1.1" checksum: 492f73e27268f9b1c122733f28ecb0e7e8d8a531a6662efbd08e22cccb3f9475e90a1b82cab06a392f6afae6d2de636f977e231296400d0ec5304ba70f166443 @@ -32786,6 +32811,15 @@ __metadata: languageName: node linkType: hard +"ts-api-utils@npm:^1.3.0": + version: 1.4.3 + resolution: "ts-api-utils@npm:1.4.3" + peerDependencies: + typescript: ">=4.2.0" + checksum: ea00dee382d19066b2a3d8929f1089888b05fec797e32e7a7004938eda1dccf2e77274ee2afcd4166f53fab9b8d7ee90ebb225a3183f9ba8817d636f688a148d + languageName: node + linkType: hard + "ts-custom-error@npm:^3.0.0": version: 3.3.0 resolution: "ts-custom-error@npm:3.3.0" @@ -32914,7 +32948,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:1.14.1, tslib@npm:^1.13.0, tslib@npm:^1.8.1, tslib@npm:^1.9.0": +"tslib@npm:1.14.1, tslib@npm:^1.13.0, tslib@npm:^1.9.0": version: 1.14.1 resolution: "tslib@npm:1.14.1" checksum: dbe628ef87f66691d5d2959b3e41b9ca0045c3ee3c7c7b906cc1e328b39f199bb1ad9e671c39025bd56122ac57dfbf7385a94843b1cc07c60a4db74795829acd @@ -32963,17 +32997,6 @@ __metadata: languageName: node linkType: hard -"tsutils@npm:^3.21.0": - version: 3.21.0 - resolution: "tsutils@npm:3.21.0" - dependencies: - tslib: ^1.8.1 - peerDependencies: - typescript: ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - checksum: 1843f4c1b2e0f975e08c4c21caa4af4f7f65a12ac1b81b3b8489366826259323feb3fc7a243123453d2d1a02314205a7634e048d4a8009921da19f99755cdc48 - languageName: node - linkType: hard - "tty-browserify@npm:0.0.0": version: 0.0.0 resolution: "tty-browserify@npm:0.0.0" @@ -33131,17 +33154,7 @@ __metadata: languageName: node linkType: hard -"typescript@npm:5.0.4": - version: 5.0.4 - resolution: "typescript@npm:5.0.4" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 82b94da3f4604a8946da585f7d6c3025fff8410779e5bde2855ab130d05e4fd08938b9e593b6ebed165bda6ad9292b230984f10952cf82f0a0ca07bbeaa08172 - languageName: node - linkType: hard - -"typescript@npm:>=3 < 6": +"typescript@npm:>=3 < 6, typescript@npm:^5.7.0": version: 5.9.3 resolution: "typescript@npm:5.9.3" bin: @@ -33151,17 +33164,7 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@5.0.4#~builtin": - version: 5.0.4 - resolution: "typescript@patch:typescript@npm%3A5.0.4#~builtin::version=5.0.4&hash=1f5320" - bin: - tsc: bin/tsc - tsserver: bin/tsserver - checksum: 6a1fe9a77bb9c5176ead919cc4a1499ee63e46b4e05bf667079f11bf3a8f7887f135aa72460a4c3b016e6e6bb65a822cb8689a6d86cbfe92d22cc9f501f09213 - languageName: node - linkType: hard - -"typescript@patch:typescript@>=3 < 6#~builtin": +"typescript@patch:typescript@>=3 < 6#~builtin, typescript@patch:typescript@^5.7.0#~builtin": version: 5.9.3 resolution: "typescript@patch:typescript@npm%3A5.9.3#~builtin::version=5.9.3&hash=1f5320" bin: @@ -33241,6 +33244,13 @@ __metadata: languageName: node linkType: hard +"undici-types@npm:~6.21.0": + version: 6.21.0 + resolution: "undici-types@npm:6.21.0" + checksum: 46331c7d6016bf85b3e8f20c159d62f5ae471aba1eb3dc52fff35a0259d58dcc7d592d4cc4f00c5f9243fa738a11cfa48bd20203040d4a9e6bc25e807fab7ab3 + languageName: node + linkType: hard + "unenv@npm:^1.7.4": version: 1.8.0 resolution: "unenv@npm:1.8.0" @@ -34669,7 +34679,14 @@ __metadata: languageName: node linkType: hard -"word-wrap@npm:^1.2.3, word-wrap@npm:~1.2.3": +"word-wrap@npm:^1.2.5": + version: 1.2.5 + resolution: "word-wrap@npm:1.2.5" + checksum: f93ba3586fc181f94afdaff3a6fef27920b4b6d9eaefed0f428f8e07adea2a7f54a5f2830ce59406c8416f033f86902b91eb824072354645eea687dff3691ccb + languageName: node + linkType: hard + +"word-wrap@npm:~1.2.3": version: 1.2.3 resolution: "word-wrap@npm:1.2.3" checksum: 30b48f91fcf12106ed3186ae4fa86a6a1842416df425be7b60485de14bec665a54a68e4b5156647dec3a70f25e84d270ca8bc8cd23182ed095f5c7206a938c1f From 89fb6ed1b30de489a177e7c49390a2211e6da543 Mon Sep 17 00:00:00 2001 From: Thunnini Date: Fri, 20 Mar 2026 17:57:49 +0900 Subject: [PATCH 02/64] refactor: replace ethers.js low-level utilities with ox MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce ox library and replace @ethersproject utility functions: - getAddress() → Address.from() (cosmos, stores-eth) - id() → Hash.keccak256(Hex.fromString()) (background) - hexValue() → Hex.fromNumber() (stores-eth) - parseUnits() → Value.from() (stores-eth) - parseBytes32String() → Hex.toBytes() + TextDecoder (stores-eth) Remove unused @ethersproject dependencies: - @keplr-wallet/cosmos: remove @ethersproject/address - @keplr-wallet/stores-eth: remove address, bytes, units, strings - @keplr-wallet/background: remove @ethersproject/address Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/background/package.json | 2 +- .../src/recent-send-history/service.ts | 14 ++- packages/cosmos/package.json | 2 +- packages/cosmos/src/bech32/index.ts | 4 +- packages/stores-eth/package.json | 5 +- packages/stores-eth/src/account/base.ts | 36 +++--- .../src/queries/coingecko-token-info.ts | 7 +- yarn.lock | 114 ++++++++++++++++-- 8 files changed, 143 insertions(+), 41 deletions(-) diff --git a/packages/background/package.json b/packages/background/package.json index b6bcaf0d32..4d295c1f1a 100644 --- a/packages/background/package.json +++ b/packages/background/package.json @@ -29,7 +29,6 @@ "dependencies": { "@ethereumjs/common": "^2.6.5", "@ethereumjs/tx": "^3.5.2", - "@ethersproject/address": "^5.7.0", "@ethersproject/hash": "^5.7.0", "@ethersproject/transactions": "^5.7.0", "@ethersproject/wallet": "^5.7.0", @@ -61,6 +60,7 @@ "ledger-bitcoin": "^0.2.3", "long": "^4.0.0", "miscreant": "0.3.2", + "ox": "^0.14.6", "pbkdf2": "^3.1.2", "utility-types": "^3.10.0" }, diff --git a/packages/background/src/recent-send-history/service.ts b/packages/background/src/recent-send-history/service.ts index ce622a9461..1e3902252a 100644 --- a/packages/background/src/recent-send-history/service.ts +++ b/packages/background/src/recent-send-history/service.ts @@ -36,7 +36,7 @@ import { ModularChainInfo, } from "@keplr-wallet/types"; import { CoinPretty } from "@keplr-wallet/unit"; -import { id } from "@ethersproject/hash"; +import { Hash, Hex } from "ox"; import { EventBusPublisher } from "@keplr-wallet/common"; import { TxExecutionEvent } from "../tx-executor/types"; import { @@ -1267,10 +1267,14 @@ export class RecentSendHistoryService { params; const logs = txReceipt.logs; - const transferTopic = id("Transfer(address,address,uint256)"); - const withdrawTopic = id("Withdrawal(address,uint256)"); - const hyperlaneReceiveTopic = id( - "ReceivedTransferRemote(uint32,bytes32,uint256)" + const transferTopic = Hash.keccak256( + Hex.fromString("Transfer(address,address,uint256)") + ); + const withdrawTopic = Hash.keccak256( + Hex.fromString("Withdrawal(address,uint256)") + ); + const hyperlaneReceiveTopic = Hash.keccak256( + Hex.fromString("ReceivedTransferRemote(uint32,bytes32,uint256)") ); for (const log of logs) { diff --git a/packages/cosmos/package.json b/packages/cosmos/package.json index 170f22ca06..4f00720ab2 100644 --- a/packages/cosmos/package.json +++ b/packages/cosmos/package.json @@ -21,7 +21,6 @@ "typecheck": "npx tsc -p tsconfig.check.json" }, "dependencies": { - "@ethersproject/address": "^5.6.0", "@keplr-wallet/common": "0.13.15", "@keplr-wallet/crypto": "0.13.15", "@keplr-wallet/proto-types": "0.13.15", @@ -31,6 +30,7 @@ "bech32": "^1.1.4", "buffer": "^6.0.3", "long": "^4.0.0", + "ox": "^0.14.6", "protobufjs": "^6.11.2", "utility-types": "^3.10.0" } diff --git a/packages/cosmos/src/bech32/index.ts b/packages/cosmos/src/bech32/index.ts index a213c85dd9..8f57ffc1a7 100644 --- a/packages/cosmos/src/bech32/index.ts +++ b/packages/cosmos/src/bech32/index.ts @@ -1,7 +1,7 @@ import bech32, { fromWords } from "bech32"; import { Bech32Config } from "@keplr-wallet/types"; import { Buffer } from "buffer/"; -import { getAddress as getEthAddress } from "@ethersproject/address"; +import { Address } from "ox"; export class Bech32Address { static shortenAddress(bech32: string, maxCharacters: number): string { @@ -92,7 +92,7 @@ export class Bech32Address { } if (mixedCaseChecksum) { - return getEthAddress("0x" + hex); + return Address.from(`0x${hex}`); } else { return "0x" + hex; } diff --git a/packages/stores-eth/package.json b/packages/stores-eth/package.json index b65dc692b8..bc464a3856 100644 --- a/packages/stores-eth/package.json +++ b/packages/stores-eth/package.json @@ -22,11 +22,7 @@ }, "dependencies": { "@ethersproject/abi": "^5.7.0", - "@ethersproject/address": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/strings": "^5.7.0", "@ethersproject/transactions": "^5.7.0", - "@ethersproject/units": "^5.7.0", "@keplr-wallet/common": "0.13.15", "@keplr-wallet/simple-fetch": "0.13.15", "@keplr-wallet/stores": "0.13.15", @@ -35,6 +31,7 @@ "@keplr-wallet/unit": "0.13.15", "big-integer": "^1.6.48", "lodash.debounce": "^4.0.8", + "ox": "^0.14.6", "utility-types": "^3.10.0" }, "peerDependencies": { diff --git a/packages/stores-eth/src/account/base.ts b/packages/stores-eth/src/account/base.ts index 63ba3a640d..8ca10c85fa 100644 --- a/packages/stores-eth/src/account/base.ts +++ b/packages/stores-eth/src/account/base.ts @@ -10,16 +10,14 @@ import { } from "@keplr-wallet/types"; import { DenomHelper, retry } from "@keplr-wallet/common"; import { erc20ContractInterface } from "../constants"; -import { hexValue } from "@ethersproject/bytes"; -import { parseUnits } from "@ethersproject/units"; import { TransactionTypes, UnsignedTransaction, serialize, } from "@ethersproject/transactions"; -import { getAddress as getEthAddress } from "@ethersproject/address"; import { action, makeObservable, observable } from "mobx"; import { Interface } from "@ethersproject/abi"; +import { Address, Hex, Value } from "ox"; import { AccountStateDiffTracerResult, SimulateGasWithPendingErc20ApprovalResult, @@ -197,7 +195,7 @@ export class EthereumAccountBase { ? recipient : sender; - const parsedAmount = parseUnits(amount, currency.coinDecimals); + const parsedAmount = Value.from(amount, currency.coinDecimals); const denomHelper = new DenomHelper(currency.coinMinimalDenom); const unsignedTx: UnsignedTransaction = (() => { @@ -208,13 +206,13 @@ export class EthereumAccountBase { value: "0x0", data: erc20ContractInterface.encodeFunctionData("transfer", [ tempRecipient, - hexValue(parsedAmount), + Hex.fromNumber(parsedAmount), ]), }; default: return { to: tempRecipient, - value: hexValue(parsedAmount), + value: Hex.fromNumber(parsedAmount), }; } })(); @@ -240,7 +238,7 @@ export class EthereumAccountBase { value: "0x0", data: erc20ContractInterface.encodeFunctionData("approve", [ approval.spender, - hexValue(BigInt(approval.amount)), + Hex.fromNumber(BigInt(approval.amount)), ]), }; }); @@ -374,7 +372,7 @@ export class EthereumAccountBase { to: erc20Approval.tokenAddress, data: erc20ContractInterface.encodeFunctionData("approve", [ erc20Approval.spender, - hexValue(BigInt(erc20Approval.amount)), + Hex.fromNumber(BigInt(erc20Approval.amount)), ]), chainId: unsignedTx.chainId, }); @@ -431,18 +429,18 @@ export class EthereumAccountBase { maxPriorityFeePerGas?: string; gasPrice?: string; }): UnsignedTransaction { - const parsedAmount = parseUnits(amount, currency.coinDecimals); + const parsedAmount = Value.from(amount, currency.coinDecimals); const denomHelper = new DenomHelper(currency.coinMinimalDenom); const feeObject = maxFeePerGas && maxPriorityFeePerGas ? { - maxFeePerGas: hexValue(Number(maxFeePerGas)), - maxPriorityFeePerGas: hexValue(Number(maxPriorityFeePerGas)), - gasLimit: hexValue(gasLimit), + maxFeePerGas: Hex.fromNumber(Number(maxFeePerGas)), + maxPriorityFeePerGas: Hex.fromNumber(Number(maxPriorityFeePerGas)), + gasLimit: Hex.fromNumber(gasLimit), } : { - gasPrice: hexValue(Number(gasPrice ?? "0")), - gasLimit: hexValue(gasLimit), + gasPrice: Hex.fromNumber(Number(gasPrice ?? "0")), + gasLimit: Hex.fromNumber(gasLimit), }; // Support EIP-1559 transaction only. @@ -455,14 +453,14 @@ export class EthereumAccountBase { "0x0", erc20ContractInterface.encodeFunctionData("transfer", [ to, - hexValue(parsedAmount), + Hex.fromNumber(parsedAmount), ]) ), ...feeObject, }; default: return { - ...this.makeTx(to, hexValue(parsedAmount)), + ...this.makeTx(to, Hex.fromNumber(parsedAmount)), ...feeObject, }; } @@ -476,14 +474,14 @@ export class EthereumAccountBase { spender: string, amount: string ): UnsignedTransaction { - const parsedAmount = parseUnits(amount, currency.coinDecimals); + const parsedAmount = Value.from(amount, currency.coinDecimals); return this.makeTx( currency.contractAddress, "0x0", erc20ContractInterface.encodeFunctionData("approve", [ spender, - hexValue(parsedAmount), + Hex.fromNumber(parsedAmount), ]) ); } @@ -666,7 +664,7 @@ export class EthereumAccountBase { return false; } - const checksumHexAddress = getEthAddress(hexAddress.toLowerCase()); + const checksumHexAddress = Address.from(hexAddress.toLowerCase()); if (isChecksumAddress && checksumHexAddress !== hexAddress) { return false; } diff --git a/packages/stores-eth/src/queries/coingecko-token-info.ts b/packages/stores-eth/src/queries/coingecko-token-info.ts index 796ceb9c30..65e90f17a4 100644 --- a/packages/stores-eth/src/queries/coingecko-token-info.ts +++ b/packages/stores-eth/src/queries/coingecko-token-info.ts @@ -4,7 +4,7 @@ import { ObservableQuery, QuerySharedContext, } from "@keplr-wallet/stores"; -import { parseBytes32String } from "@ethersproject/strings"; +import { Hex } from "ox"; import { makeObservable } from "mobx"; /** @@ -16,7 +16,10 @@ function decodeBytes32IfNeeded(value: string | undefined): string | undefined { if (!value) return value; if (value.length === 64 && /^[0-9a-fA-F]+$/.test(value)) { try { - return parseBytes32String("0x" + value); + const bytes = Hex.toBytes(`0x${value}` as `0x${string}`); + const end = bytes.indexOf(0); + const trimmed = bytes.slice(0, end === -1 ? bytes.length : end); + return new TextDecoder().decode(trimmed); } catch { // noop } diff --git a/yarn.lock b/yarn.lock index 84234deea0..df2c4d5757 100644 --- a/yarn.lock +++ b/yarn.lock @@ -23,6 +23,13 @@ __metadata: languageName: node linkType: hard +"@adraffy/ens-normalize@npm:^1.11.0": + version: 1.11.1 + resolution: "@adraffy/ens-normalize@npm:1.11.1" + checksum: e8b17fcc730ccc45a956e1fbb09edfe42be41c291079512082e9964f8ef4287e67913183cdd02fff71d2e215340d5b98a9bbbd9be32c5d36fad4ba2c1ec33ff2 + languageName: node + linkType: hard + "@amplitude/analytics-browser@npm:^1.10.3": version: 1.10.3 resolution: "@amplitude/analytics-browser@npm:1.10.3" @@ -6092,7 +6099,6 @@ __metadata: dependencies: "@ethereumjs/common": ^2.6.5 "@ethereumjs/tx": ^3.5.2 - "@ethersproject/address": ^5.7.0 "@ethersproject/hash": ^5.7.0 "@ethersproject/transactions": ^5.7.0 "@ethersproject/wallet": ^5.7.0 @@ -6128,6 +6134,7 @@ __metadata: ledger-bitcoin: ^0.2.3 long: ^4.0.0 miscreant: 0.3.2 + ox: ^0.14.6 pbkdf2: ^3.1.2 utility-types: ^3.10.0 peerDependencies: @@ -6167,7 +6174,6 @@ __metadata: version: 0.0.0-use.local resolution: "@keplr-wallet/cosmos@workspace:packages/cosmos" dependencies: - "@ethersproject/address": ^5.6.0 "@keplr-wallet/common": 0.13.15 "@keplr-wallet/crypto": 0.13.15 "@keplr-wallet/proto-types": 0.13.15 @@ -6177,6 +6183,7 @@ __metadata: bech32: ^1.1.4 buffer: ^6.0.3 long: ^4.0.0 + ox: ^0.14.6 protobufjs: ^6.11.2 utility-types: ^3.10.0 languageName: unknown @@ -6637,11 +6644,7 @@ __metadata: resolution: "@keplr-wallet/stores-eth@workspace:packages/stores-eth" dependencies: "@ethersproject/abi": ^5.7.0 - "@ethersproject/address": ^5.7.0 - "@ethersproject/bytes": ^5.7.0 - "@ethersproject/strings": ^5.7.0 "@ethersproject/transactions": ^5.7.0 - "@ethersproject/units": ^5.7.0 "@keplr-wallet/common": 0.13.15 "@keplr-wallet/simple-fetch": 0.13.15 "@keplr-wallet/stores": 0.13.15 @@ -6651,6 +6654,7 @@ __metadata: "@types/lodash.debounce": ^4 big-integer: ^1.6.48 lodash.debounce: ^4.0.8 + ox: ^0.14.6 utility-types: ^3.10.0 peerDependencies: mobx: ^6 @@ -7442,6 +7446,13 @@ __metadata: languageName: node linkType: hard +"@noble/ciphers@npm:^1.3.0": + version: 1.3.0 + resolution: "@noble/ciphers@npm:1.3.0" + checksum: 19722c35475df9bc78db60d261d0b5ef8a6d722561efc2135453f943eaa421b492195dc666e3e4df2b755bca3739e04f04b9c660198559f5dd05d3cfbf1b9e92 + languageName: node + linkType: hard + "@noble/curves@npm:1.0.0, @noble/curves@npm:~1.0.0": version: 1.0.0 resolution: "@noble/curves@npm:1.0.0" @@ -7460,6 +7471,15 @@ __metadata: languageName: node linkType: hard +"@noble/curves@npm:1.9.1": + version: 1.9.1 + resolution: "@noble/curves@npm:1.9.1" + dependencies: + "@noble/hashes": 1.8.0 + checksum: 4f3483a1001538d2f55516cdcb19319d1eaef79550633f670e7d570b989cdbc0129952868b72bb67643329746b8ffefe8e4cd791c8cc35574e05a37f873eef42 + languageName: node + linkType: hard + "@noble/curves@npm:^1.7.0": version: 1.8.1 resolution: "@noble/curves@npm:1.8.1" @@ -7478,6 +7498,15 @@ __metadata: languageName: node linkType: hard +"@noble/curves@npm:~1.9.0": + version: 1.9.7 + resolution: "@noble/curves@npm:1.9.7" + dependencies: + "@noble/hashes": 1.8.0 + checksum: 65acad44ac6944ab96471109087d6cfcbcaa251faad6295961be9a5ace220634f4b7c74a96d1ee2274ad3880ea953d8e8259893ed8c906c831ef29f5c04ec9cc + languageName: node + linkType: hard + "@noble/hashes@npm:1.0.0": version: 1.0.0 resolution: "@noble/hashes@npm:1.0.0" @@ -7513,6 +7542,13 @@ __metadata: languageName: node linkType: hard +"@noble/hashes@npm:1.8.0, @noble/hashes@npm:^1.8.0, @noble/hashes@npm:~1.8.0": + version: 1.8.0 + resolution: "@noble/hashes@npm:1.8.0" + checksum: c94e98b941963676feaba62475b1ccfa8341e3f572adbb3b684ee38b658df44100187fa0ef4220da580b13f8d27e87d5492623c8a02ecc61f23fb9960c7918f5 + languageName: node + linkType: hard + "@noble/hashes@npm:^1, @noble/hashes@npm:^1.0.0": version: 1.1.3 resolution: "@noble/hashes@npm:1.1.3" @@ -9282,7 +9318,7 @@ __metadata: languageName: node linkType: hard -"@scure/base@npm:~1.2.1": +"@scure/base@npm:~1.2.1, @scure/base@npm:~1.2.5": version: 1.2.6 resolution: "@scure/base@npm:1.2.6" checksum: 1058cb26d5e4c1c46c9cc0ae0b67cc66d306733baf35d6ebdd8ddaba242b80c3807b726e3b48cb0411bb95ec10d37764969063ea62188f86ae9315df8ea6b325 @@ -9311,6 +9347,17 @@ __metadata: languageName: node linkType: hard +"@scure/bip32@npm:^1.7.0": + version: 1.7.0 + resolution: "@scure/bip32@npm:1.7.0" + dependencies: + "@noble/curves": ~1.9.0 + "@noble/hashes": ~1.8.0 + "@scure/base": ~1.2.5 + checksum: c83adca5a74ec5c4ded8ba93900d0065e4767c4759cf24c2674923aef01d45ba56f171574e3519f2341be99f53a333f01b674eb6cfeb6fa8379607c6d1bc90b5 + languageName: node + linkType: hard + "@scure/bip39@npm:1.2.0": version: 1.2.0 resolution: "@scure/bip39@npm:1.2.0" @@ -9331,6 +9378,16 @@ __metadata: languageName: node linkType: hard +"@scure/bip39@npm:^1.6.0": + version: 1.6.0 + resolution: "@scure/bip39@npm:1.6.0" + dependencies: + "@noble/hashes": ~1.8.0 + "@scure/base": ~1.2.5 + checksum: 96d46420780473d6c6c9700254a0eceec60302f61d7f9d7f29024e90c7acff3e8e40a5ee52dfaf104db539a10462e531996aaf9e69f082b8540b0a25870545fc + languageName: node + linkType: hard + "@scure/starknet@npm:1.1.0": version: 1.1.0 resolution: "@scure/starknet@npm:1.1.0" @@ -12524,6 +12581,21 @@ __metadata: languageName: node linkType: hard +"abitype@npm:^1.2.3": + version: 1.2.3 + resolution: "abitype@npm:1.2.3" + peerDependencies: + typescript: ">=5.0.4" + zod: ^3.22.0 || ^4.0.0 + peerDependenciesMeta: + typescript: + optional: true + zod: + optional: true + checksum: b5b5620f8e55a6dd7ae829630c0ded02b30f589f0f8f5ca931cdfcf6d7daa8154e30e3fe3593b3f6c4872a955ac55d447ccc2f801fd6a6aa698bdad966e3fe2e + languageName: node + linkType: hard + "accepts@npm:~1.3.5, accepts@npm:~1.3.8": version: 1.3.8 resolution: "accepts@npm:1.3.8" @@ -18197,6 +18269,13 @@ __metadata: languageName: node linkType: hard +"eventemitter3@npm:5.0.1": + version: 5.0.1 + resolution: "eventemitter3@npm:5.0.1" + checksum: 543d6c858ab699303c3c32e0f0f47fc64d360bf73c3daf0ac0b5079710e340d6fe9f15487f94e66c629f5f82cd1a8678d692f3dbb6f6fcd1190e1b97fcad36f8 + languageName: node + linkType: hard + "eventemitter3@npm:^4.0.4, eventemitter3@npm:^4.0.7": version: 4.0.7 resolution: "eventemitter3@npm:4.0.7" @@ -26434,6 +26513,27 @@ __metadata: languageName: node linkType: hard +"ox@npm:^0.14.6": + version: 0.14.8 + resolution: "ox@npm:0.14.8" + dependencies: + "@adraffy/ens-normalize": ^1.11.0 + "@noble/ciphers": ^1.3.0 + "@noble/curves": 1.9.1 + "@noble/hashes": ^1.8.0 + "@scure/bip32": ^1.7.0 + "@scure/bip39": ^1.6.0 + abitype: ^1.2.3 + eventemitter3: 5.0.1 + peerDependencies: + typescript: ">=5.4.0" + peerDependenciesMeta: + typescript: + optional: true + checksum: 9684de6e40ad4fc978f41e35c1a282d91592c8935b755cd89dc86d199a1367462c9ea88d5b079f8ae6698a6dd80cfda67cfb20789df0163e4437559da39ff2a9 + languageName: node + linkType: hard + "p-finally@npm:^1.0.0": version: 1.0.0 resolution: "p-finally@npm:1.0.0" From ae5c8bd3a2798c99e3cbbfa1870d05cfa135c95c Mon Sep 17 00:00:00 2001 From: Thunnini Date: Fri, 20 Mar 2026 18:06:24 +0900 Subject: [PATCH 03/64] refactor: remove unused @ethersproject dependencies Remove @ethersproject packages that are not imported in source code: - @keplr-wallet/background: remove @ethersproject/wallet - @keplr-wallet/hooks: remove @ethersproject/address - @keplr-wallet/hooks-evm: remove @ethersproject/address - @keplr-wallet/hooks-starknet: remove @ethersproject/address, providers - @keplr-wallet/stores-starknet: remove abi, address, bytes, transactions, units - @keplr-wallet/chain-validator: remove @ethersproject/abi Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/background/package.json | 1 - packages/chain-validator/package.json | 1 - packages/hooks-evm/package.json | 1 - packages/hooks-starknet/package.json | 2 - packages/hooks/package.json | 1 - packages/stores-starknet/package.json | 5 -- yarn.lock | 118 +------------------------- 7 files changed, 1 insertion(+), 128 deletions(-) diff --git a/packages/background/package.json b/packages/background/package.json index 4d295c1f1a..7839aafd76 100644 --- a/packages/background/package.json +++ b/packages/background/package.json @@ -31,7 +31,6 @@ "@ethereumjs/tx": "^3.5.2", "@ethersproject/hash": "^5.7.0", "@ethersproject/transactions": "^5.7.0", - "@ethersproject/wallet": "^5.7.0", "@keplr-wallet/chain-validator": "0.13.15", "@keplr-wallet/common": "0.13.15", "@keplr-wallet/cosmos": "0.13.15", diff --git a/packages/chain-validator/package.json b/packages/chain-validator/package.json index 8fe8121bcb..034fb405c7 100644 --- a/packages/chain-validator/package.json +++ b/packages/chain-validator/package.json @@ -21,7 +21,6 @@ "typecheck": "npx tsc -p tsconfig.check.json" }, "dependencies": { - "@ethersproject/abi": "^5.7.0", "@keplr-wallet/cosmos": "0.13.15", "@keplr-wallet/simple-fetch": "0.13.15", "@keplr-wallet/types": "0.13.15", diff --git a/packages/hooks-evm/package.json b/packages/hooks-evm/package.json index 907ba2d684..667f5c3276 100644 --- a/packages/hooks-evm/package.json +++ b/packages/hooks-evm/package.json @@ -21,7 +21,6 @@ "typecheck": "npx tsc -p tsconfig.check.json" }, "dependencies": { - "@ethersproject/address": "^5.7.0", "@ethersproject/providers": "^5.7.0", "@keplr-wallet/common": "0.13.15", "@keplr-wallet/cosmos": "0.13.15", diff --git a/packages/hooks-starknet/package.json b/packages/hooks-starknet/package.json index 5028e621cf..eb22c630ce 100644 --- a/packages/hooks-starknet/package.json +++ b/packages/hooks-starknet/package.json @@ -21,8 +21,6 @@ "typecheck": "npx tsc -p tsconfig.check.json" }, "dependencies": { - "@ethersproject/address": "^5.7.0", - "@ethersproject/providers": "^5.7.0", "@keplr-wallet/background": "0.13.15", "@keplr-wallet/common": "0.13.15", "@keplr-wallet/cosmos": "0.13.15", diff --git a/packages/hooks/package.json b/packages/hooks/package.json index 68e9658ca2..cfceb3cdc5 100644 --- a/packages/hooks/package.json +++ b/packages/hooks/package.json @@ -21,7 +21,6 @@ "typecheck": "npx tsc -p tsconfig.check.json" }, "dependencies": { - "@ethersproject/address": "^5.7.0", "@ethersproject/providers": "^5.7.0", "@keplr-wallet/background": "0.13.15", "@keplr-wallet/common": "0.13.15", diff --git a/packages/stores-starknet/package.json b/packages/stores-starknet/package.json index d11d24fad5..8f38db8b0f 100644 --- a/packages/stores-starknet/package.json +++ b/packages/stores-starknet/package.json @@ -21,11 +21,6 @@ "typecheck": "npx tsc -p tsconfig.check.json" }, "dependencies": { - "@ethersproject/abi": "^5.7.0", - "@ethersproject/address": "^5.7.0", - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/transactions": "^5.7.0", - "@ethersproject/units": "^5.7.0", "@keplr-wallet/common": "0.13.15", "@keplr-wallet/simple-fetch": "0.13.15", "@keplr-wallet/stores": "0.13.15", diff --git a/yarn.lock b/yarn.lock index df2c4d5757..872d52cd80 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4717,47 +4717,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/hdnode@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/hdnode@npm:5.7.0" - dependencies: - "@ethersproject/abstract-signer": ^5.7.0 - "@ethersproject/basex": ^5.7.0 - "@ethersproject/bignumber": ^5.7.0 - "@ethersproject/bytes": ^5.7.0 - "@ethersproject/logger": ^5.7.0 - "@ethersproject/pbkdf2": ^5.7.0 - "@ethersproject/properties": ^5.7.0 - "@ethersproject/sha2": ^5.7.0 - "@ethersproject/signing-key": ^5.7.0 - "@ethersproject/strings": ^5.7.0 - "@ethersproject/transactions": ^5.7.0 - "@ethersproject/wordlists": ^5.7.0 - checksum: bfe5ca2d89a42de73655f853170ef4766b933c5f481cddad709b3aca18823275b096e572f92d1602a052f80b426edde44ad6b9d028799775a7dad4a5bbed2133 - languageName: node - linkType: hard - -"@ethersproject/json-wallets@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/json-wallets@npm:5.7.0" - dependencies: - "@ethersproject/abstract-signer": ^5.7.0 - "@ethersproject/address": ^5.7.0 - "@ethersproject/bytes": ^5.7.0 - "@ethersproject/hdnode": ^5.7.0 - "@ethersproject/keccak256": ^5.7.0 - "@ethersproject/logger": ^5.7.0 - "@ethersproject/pbkdf2": ^5.7.0 - "@ethersproject/properties": ^5.7.0 - "@ethersproject/random": ^5.7.0 - "@ethersproject/strings": ^5.7.0 - "@ethersproject/transactions": ^5.7.0 - aes-js: 3.0.0 - scrypt-js: 3.0.1 - checksum: f583458d22db62efaaf94d38dd243482776a45bf90f9f3882fbad5aa0b8fd288b41eb7c1ff8ec0b99c9b751088e43d6173530db64dd33c59f9d8daa8d7ad5aa2 - languageName: node - linkType: hard - "@ethersproject/keccak256@npm:^5.6.0": version: 5.6.0 resolution: "@ethersproject/keccak256@npm:5.6.0" @@ -4810,16 +4769,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/pbkdf2@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/pbkdf2@npm:5.7.0" - dependencies: - "@ethersproject/bytes": ^5.7.0 - "@ethersproject/sha2": ^5.7.0 - checksum: b895adb9e35a8a127e794f7aadc31a2424ef355a70e51cde10d457e3e888bb8102373199a540cf61f2d6b9a32e47358f9c65b47d559f42bf8e596b5fd67901e9 - languageName: node - linkType: hard - "@ethersproject/properties@npm:^5.6.0": version: 5.6.0 resolution: "@ethersproject/properties@npm:5.6.0" @@ -4991,40 +4940,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/units@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/units@npm:5.7.0" - dependencies: - "@ethersproject/bignumber": ^5.7.0 - "@ethersproject/constants": ^5.7.0 - "@ethersproject/logger": ^5.7.0 - checksum: 304714f848cd32e57df31bf545f7ad35c2a72adae957198b28cbc62166daa929322a07bff6e9c9ac4577ab6aa0de0546b065ed1b2d20b19e25748b7d475cb0fc - languageName: node - linkType: hard - -"@ethersproject/wallet@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/wallet@npm:5.7.0" - dependencies: - "@ethersproject/abstract-provider": ^5.7.0 - "@ethersproject/abstract-signer": ^5.7.0 - "@ethersproject/address": ^5.7.0 - "@ethersproject/bignumber": ^5.7.0 - "@ethersproject/bytes": ^5.7.0 - "@ethersproject/hash": ^5.7.0 - "@ethersproject/hdnode": ^5.7.0 - "@ethersproject/json-wallets": ^5.7.0 - "@ethersproject/keccak256": ^5.7.0 - "@ethersproject/logger": ^5.7.0 - "@ethersproject/properties": ^5.7.0 - "@ethersproject/random": ^5.7.0 - "@ethersproject/signing-key": ^5.7.0 - "@ethersproject/transactions": ^5.7.0 - "@ethersproject/wordlists": ^5.7.0 - checksum: a4009bf7331eddab38e3015b5e9101ef92de7f705b00a6196b997db0e5635b6d83561674d46c90c6f77b87c0500fe4a6b0183ba13749efc22db59c99deb82fbd - languageName: node - linkType: hard - "@ethersproject/web@npm:^5.6.0": version: 5.6.0 resolution: "@ethersproject/web@npm:5.6.0" @@ -5051,19 +4966,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/wordlists@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/wordlists@npm:5.7.0" - dependencies: - "@ethersproject/bytes": ^5.7.0 - "@ethersproject/hash": ^5.7.0 - "@ethersproject/logger": ^5.7.0 - "@ethersproject/properties": ^5.7.0 - "@ethersproject/strings": ^5.7.0 - checksum: 30eb6eb0731f9ef5faa44bf9c0c6e950bcaaef61e4d2d9ce0ae6d341f4e2d6d1f4ab4f8880bfce03b7aac4b862fb740e1421170cfbf8e2aafc359277d49e6e97 - languageName: node - linkType: hard - "@fal-works/esbuild-plugin-global-externals@npm:^2.1.2": version: 2.1.2 resolution: "@fal-works/esbuild-plugin-global-externals@npm:2.1.2" @@ -6101,7 +6003,6 @@ __metadata: "@ethereumjs/tx": ^3.5.2 "@ethersproject/hash": ^5.7.0 "@ethersproject/transactions": ^5.7.0 - "@ethersproject/wallet": ^5.7.0 "@keplr-wallet/chain-validator": 0.13.15 "@keplr-wallet/common": 0.13.15 "@keplr-wallet/cosmos": 0.13.15 @@ -6149,7 +6050,6 @@ __metadata: version: 0.0.0-use.local resolution: "@keplr-wallet/chain-validator@workspace:packages/chain-validator" dependencies: - "@ethersproject/abi": ^5.7.0 "@keplr-wallet/cosmos": 0.13.15 "@keplr-wallet/simple-fetch": 0.13.15 "@keplr-wallet/types": 0.13.15 @@ -6384,7 +6284,6 @@ __metadata: version: 0.0.0-use.local resolution: "@keplr-wallet/hooks-evm@workspace:packages/hooks-evm" dependencies: - "@ethersproject/address": ^5.7.0 "@ethersproject/providers": ^5.7.0 "@keplr-wallet/common": 0.13.15 "@keplr-wallet/cosmos": 0.13.15 @@ -6423,8 +6322,6 @@ __metadata: version: 0.0.0-use.local resolution: "@keplr-wallet/hooks-starknet@workspace:packages/hooks-starknet" dependencies: - "@ethersproject/address": ^5.7.0 - "@ethersproject/providers": ^5.7.0 "@keplr-wallet/background": 0.13.15 "@keplr-wallet/common": 0.13.15 "@keplr-wallet/cosmos": 0.13.15 @@ -6452,7 +6349,6 @@ __metadata: version: 0.0.0-use.local resolution: "@keplr-wallet/hooks@workspace:packages/hooks" dependencies: - "@ethersproject/address": ^5.7.0 "@ethersproject/providers": ^5.7.0 "@keplr-wallet/background": 0.13.15 "@keplr-wallet/common": 0.13.15 @@ -6703,11 +6599,6 @@ __metadata: version: 0.0.0-use.local resolution: "@keplr-wallet/stores-starknet@workspace:packages/stores-starknet" dependencies: - "@ethersproject/abi": ^5.7.0 - "@ethersproject/address": ^5.7.0 - "@ethersproject/bytes": ^5.7.0 - "@ethersproject/transactions": ^5.7.0 - "@ethersproject/units": ^5.7.0 "@keplr-wallet/common": 0.13.15 "@keplr-wallet/simple-fetch": 0.13.15 "@keplr-wallet/stores": 0.13.15 @@ -12732,13 +12623,6 @@ __metadata: languageName: node linkType: hard -"aes-js@npm:3.0.0": - version: 3.0.0 - resolution: "aes-js@npm:3.0.0" - checksum: 251e26d533cd1a915b44896b17d5ed68c24a02484cfdd2e74ec700a309267db96651ea4eb657bf20aac32a3baa61f6e34edf8e2fec2de440a655da9942d334b8 - languageName: node - linkType: hard - "aes-js@npm:^3.1.2": version: 3.1.2 resolution: "aes-js@npm:3.1.2" @@ -30789,7 +30673,7 @@ __metadata: languageName: node linkType: hard -"scrypt-js@npm:3.0.1, scrypt-js@npm:^3.0.0, scrypt-js@npm:^3.0.1": +"scrypt-js@npm:^3.0.0, scrypt-js@npm:^3.0.1": version: 3.0.1 resolution: "scrypt-js@npm:3.0.1" checksum: b7c7d1a68d6ca946f2fbb0778e0c4ec63c65501b54023b2af7d7e9f48fdb6c6580d6f7675cd53bda5944c5ebc057560d5a6365079752546865defb3b79dea454 From e2f26ee2a6bcb9f10be85791cd8559aac553303f Mon Sep 17 00:00:00 2001 From: Thunnini Date: Fri, 20 Mar 2026 18:25:15 +0900 Subject: [PATCH 04/64] refactor: replace @ethersproject/abi with ox AbiFunction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Migrate all ABI encoding/decoding from ethers Interface to ox AbiFunction: - constants.ts: replace Interface with individual AbiFunction definitions - account/base.ts: encodeFunctionData → AbiFunction.encodeData - erc20-balance.ts: encodeFunctionData → AbiFunction.encodeData - erc20-metadata.ts: encode/decode → AbiFunction.encodeData/decodeResult - stores-etc erc20 query: same migration as above - send-token.tsx: BigNumber instanceof → typeof bigint check, amount.toHexString() → template literal with toString(16) Remove @ethersproject/abi from stores-eth, stores-etc Remove @ethersproject/bignumber from extension Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/extension/package.json | 2 +- .../components/eth-tx/render/send-token.tsx | 15 +- packages/stores-etc/package.json | 2 +- packages/stores-etc/src/erc20/query/index.ts | 85 ++---- packages/stores-eth/package.json | 1 - packages/stores-eth/src/account/base.ts | 61 ++-- packages/stores-eth/src/constants.ts | 270 ++---------------- .../stores-eth/src/queries/erc20-balance.ts | 7 +- .../stores-eth/src/queries/erc20-metadata.ts | 29 +- yarn.lock | 217 +------------- 10 files changed, 106 insertions(+), 583 deletions(-) diff --git a/apps/extension/package.json b/apps/extension/package.json index 516e07f92e..a3ba51d2bb 100644 --- a/apps/extension/package.json +++ b/apps/extension/package.json @@ -23,7 +23,6 @@ }, "dependencies": { "@amplitude/analytics-browser": "^1.10.3", - "@ethersproject/bignumber": "^5.7.0", "@ethersproject/transactions": "^5.7.0", "@floating-ui/react": "^0.23.0", "@floating-ui/react-dom": "^1.3.0", @@ -82,6 +81,7 @@ "long": "^4.0.0", "lottie-web": "^5.10.2", "os-browserify": "^0.3.0", + "ox": "^0.14.6", "p-queue": "^6.6.2", "process": "^0.11.10", "qrcode.react": "^3.1.0", diff --git a/apps/extension/src/pages/sign/components/eth-tx/render/send-token.tsx b/apps/extension/src/pages/sign/components/eth-tx/render/send-token.tsx index 95d5ce8987..6c9eb9a814 100644 --- a/apps/extension/src/pages/sign/components/eth-tx/render/send-token.tsx +++ b/apps/extension/src/pages/sign/components/eth-tx/render/send-token.tsx @@ -2,8 +2,8 @@ import React from "react"; import { observer } from "mobx-react-lite"; import { useStore } from "../../../../../stores"; import { CoinPretty, Dec } from "@keplr-wallet/unit"; -import { erc20ContractInterface } from "@keplr-wallet/stores-eth"; -import { BigNumber } from "@ethersproject/bignumber"; +import { erc20TransferFunction } from "@keplr-wallet/stores-eth"; +import { AbiFunction } from "ox"; import { IEthTxRenderer } from "../types"; import { ItemLogo } from "../../../../main/token-detail/msg-items/logo"; import { ColorPalette } from "../../../../../styles"; @@ -19,9 +19,9 @@ export const EthSendTokenTx: IEthTxRenderer = { process(chainId, unsignedTx) { if (unsignedTx.data && unsignedTx.data !== "0x" && unsignedTx.to) { try { - const dataDecodedValues = erc20ContractInterface.decodeFunctionData( - "transfer", - unsignedTx.data + const dataDecodedValues = AbiFunction.decodeData( + erc20TransferFunction, + unsignedTx.data as `0x${string}` ); const erc20ContractAddress = unsignedTx.to; const recipient = dataDecodedValues[0]; @@ -32,8 +32,7 @@ export const EthSendTokenTx: IEthTxRenderer = { typeof erc20ContractAddress !== "string" || !recipient || typeof recipient !== "string" || - !amount || - amount instanceof BigNumber === false + typeof amount !== "bigint" ) { return undefined; } @@ -69,7 +68,7 @@ export const EthSendTokenTx: IEthTxRenderer = { ), diff --git a/packages/stores-etc/package.json b/packages/stores-etc/package.json index b2025248fe..991803d46b 100644 --- a/packages/stores-etc/package.json +++ b/packages/stores-etc/package.json @@ -21,12 +21,12 @@ "typecheck": "npx tsc -p tsconfig.check.json" }, "dependencies": { - "@ethersproject/abi": "^5.6.0", "@keplr-wallet/common": "0.13.15", "@keplr-wallet/simple-fetch": "0.13.15", "@keplr-wallet/stores": "0.13.15", "@keplr-wallet/types": "0.13.15", "@keplr-wallet/unit": "0.13.15", + "ox": "^0.14.6", "utility-types": "^3.10.0" }, "peerDependencies": { diff --git a/packages/stores-etc/src/erc20/query/index.ts b/packages/stores-etc/src/erc20/query/index.ts index 4a5926f2bd..8474819265 100644 --- a/packages/stores-etc/src/erc20/query/index.ts +++ b/packages/stores-etc/src/erc20/query/index.ts @@ -3,53 +3,18 @@ import { ObservableJsonRPCQuery, QuerySharedContext, } from "@keplr-wallet/stores"; -import { Interface } from "@ethersproject/abi"; +import { AbiFunction } from "ox"; import { computed, makeObservable } from "mobx"; -const erc20MetadataInterface: Interface = new Interface([ - { - constant: true, - inputs: [], - name: "name", - outputs: [ - { - name: "", - type: "string", - }, - ], - payable: false, - stateMutability: "view", - type: "function", - }, - { - constant: true, - inputs: [], - name: "symbol", - outputs: [ - { - name: "", - type: "string", - }, - ], - payable: false, - stateMutability: "view", - type: "function", - }, - { - constant: true, - inputs: [], - name: "decimals", - outputs: [ - { - name: "", - type: "uint8", - }, - ], - payable: false, - stateMutability: "view", - type: "function", - }, -]); +const erc20NameFunction = AbiFunction.from( + "function name() view returns (string)" +); +const erc20SymbolFunction = AbiFunction.from( + "function symbol() view returns (string)" +); +const erc20DecimalsFunction = AbiFunction.from( + "function decimals() view returns (uint8)" +); export class ObservableQueryERC20MetadataName extends ObservableJsonRPCQuery { constructor( @@ -60,7 +25,7 @@ export class ObservableQueryERC20MetadataName extends ObservableJsonRPCQuery { constructor( @@ -17,7 +24,7 @@ export class ObservableQueryEVMChainERC20MetadataSymbol extends ObservableEvmCha super(sharedContext, chainId, chainGetter, "eth_call", [ { to: contractAddress, - data: erc20ContractInterface.encodeFunctionData("symbol"), + data: AbiFunction.encodeData(erc20SymbolFunction), }, "latest", ]); @@ -36,10 +43,10 @@ export class ObservableQueryEVMChainERC20MetadataSymbol extends ObservableEvmCha } try { - return erc20ContractInterface.decodeFunctionResult( - "symbol", - this.response.data - )[0]; + return AbiFunction.decodeResult( + erc20SymbolFunction, + this.response.data as `0x${string}` + ); } catch (e) { console.log(e); } @@ -57,7 +64,7 @@ export class ObservableQueryEVMChainERC20MetadataDecimals extends ObservableEvmC super(sharedContext, chainId, chainGetter, "eth_call", [ { to: contractAddress, - data: erc20ContractInterface.encodeFunctionData("decimals"), + data: AbiFunction.encodeData(erc20DecimalsFunction), }, "latest", ]); @@ -76,10 +83,10 @@ export class ObservableQueryEVMChainERC20MetadataDecimals extends ObservableEvmC } try { - return erc20ContractInterface.decodeFunctionResult( - "decimals", - this.response.data - )[0]; + return AbiFunction.decodeResult( + erc20DecimalsFunction, + this.response.data as `0x${string}` + ); } catch (e) { console.log(e); } diff --git a/yarn.lock b/yarn.lock index 872d52cd80..fe3a5a72fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4482,23 +4482,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/abi@npm:^5.6.0": - version: 5.6.0 - resolution: "@ethersproject/abi@npm:5.6.0" - dependencies: - "@ethersproject/address": ^5.6.0 - "@ethersproject/bignumber": ^5.6.0 - "@ethersproject/bytes": ^5.6.0 - "@ethersproject/constants": ^5.6.0 - "@ethersproject/hash": ^5.6.0 - "@ethersproject/keccak256": ^5.6.0 - "@ethersproject/logger": ^5.6.0 - "@ethersproject/properties": ^5.6.0 - "@ethersproject/strings": ^5.6.0 - checksum: d0cf0450d21ff5beff5593ac4b1749eb64d67fdd7d0e2d489ab1996218ec7242b9dadcd7a9896687e84784c83bf3e39cba6e77b3e813165d6b400e584a96f2fc - languageName: node - linkType: hard - "@ethersproject/abi@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/abi@npm:5.7.0" @@ -4516,21 +4499,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/abstract-provider@npm:^5.6.0": - version: 5.6.0 - resolution: "@ethersproject/abstract-provider@npm:5.6.0" - dependencies: - "@ethersproject/bignumber": ^5.6.0 - "@ethersproject/bytes": ^5.6.0 - "@ethersproject/logger": ^5.6.0 - "@ethersproject/networks": ^5.6.0 - "@ethersproject/properties": ^5.6.0 - "@ethersproject/transactions": ^5.6.0 - "@ethersproject/web": ^5.6.0 - checksum: 42ec4148217f7643f667f46235266100a1b31b8e87b6d540b6e8667703f56f633d25ec2e5d9b0f95556de0d0620189488e9d77dafc058c61e45872fef620ac5a - languageName: node - linkType: hard - "@ethersproject/abstract-provider@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/abstract-provider@npm:5.7.0" @@ -4546,19 +4514,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/abstract-signer@npm:^5.6.0": - version: 5.6.0 - resolution: "@ethersproject/abstract-signer@npm:5.6.0" - dependencies: - "@ethersproject/abstract-provider": ^5.6.0 - "@ethersproject/bignumber": ^5.6.0 - "@ethersproject/bytes": ^5.6.0 - "@ethersproject/logger": ^5.6.0 - "@ethersproject/properties": ^5.6.0 - checksum: 91722f3ad449da1a26898132b53e0130deac19ab8dbef55c5fd3c6d2b9ddb0428f539021c9b7085f3fc5e8615bdf1fddcbe4f6c5365f6b6cfd5d3952816d27b7 - languageName: node - linkType: hard - "@ethersproject/abstract-signer@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/abstract-signer@npm:5.7.0" @@ -4572,19 +4527,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/address@npm:^5.6.0": - version: 5.6.0 - resolution: "@ethersproject/address@npm:5.6.0" - dependencies: - "@ethersproject/bignumber": ^5.6.0 - "@ethersproject/bytes": ^5.6.0 - "@ethersproject/keccak256": ^5.6.0 - "@ethersproject/logger": ^5.6.0 - "@ethersproject/rlp": ^5.6.0 - checksum: 504cddd422ec9890eda61da0421991ace7c5cd9f365cbc9761305013621915dc5ff5247f4b04699b7060fc272a7a8c9dc88f993bc6fa6e0f6e9e4fa30d6f3c0f - languageName: node - linkType: hard - "@ethersproject/address@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/address@npm:5.7.0" @@ -4598,15 +4540,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/base64@npm:^5.6.0": - version: 5.6.0 - resolution: "@ethersproject/base64@npm:5.6.0" - dependencies: - "@ethersproject/bytes": ^5.6.0 - checksum: 5f316367acf18fdba82d50868171251f75218740a1c9bad8b11c6c3372c86ae323f91bc6727e78e527866357974d19fcced12f666fb067ffba2be638d54d36f7 - languageName: node - linkType: hard - "@ethersproject/base64@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/base64@npm:5.7.0" @@ -4626,17 +4559,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/bignumber@npm:^5.6.0": - version: 5.6.0 - resolution: "@ethersproject/bignumber@npm:5.6.0" - dependencies: - "@ethersproject/bytes": ^5.6.0 - "@ethersproject/logger": ^5.6.0 - bn.js: ^4.11.9 - checksum: cb1e0d712a1d991d7c74c66d34522413a2fd832e10bc15158b24e07f61e80a221689947936790334137c11c582f3f4d184d3ccf3036e09e4df1b2026923962b4 - languageName: node - linkType: hard - "@ethersproject/bignumber@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/bignumber@npm:5.7.0" @@ -4648,15 +4570,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/bytes@npm:^5.6.0": - version: 5.6.1 - resolution: "@ethersproject/bytes@npm:5.6.1" - dependencies: - "@ethersproject/logger": ^5.6.0 - checksum: d06ffe3bf12aa8a6588d99b82e40b46a2cbb8b057fc650aad836e3e8c95d4559773254eeeb8fed652066dcf8082e527e37cd2b9fff7ac8cabc4de7c49459a7eb - languageName: node - linkType: hard - "@ethersproject/bytes@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/bytes@npm:5.7.0" @@ -4666,15 +4579,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/constants@npm:^5.6.0": - version: 5.6.0 - resolution: "@ethersproject/constants@npm:5.6.0" - dependencies: - "@ethersproject/bignumber": ^5.6.0 - checksum: da54458a0133b64c02052b86fefa6118ed88c449b02a61ba57745bf08029658214291935b0500461bde3f734ea98e6d8edc586eed9ce9fa7e6a16d9397716ff7 - languageName: node - linkType: hard - "@ethersproject/constants@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/constants@npm:5.7.0" @@ -4684,22 +4588,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/hash@npm:^5.6.0": - version: 5.6.0 - resolution: "@ethersproject/hash@npm:5.6.0" - dependencies: - "@ethersproject/abstract-signer": ^5.6.0 - "@ethersproject/address": ^5.6.0 - "@ethersproject/bignumber": ^5.6.0 - "@ethersproject/bytes": ^5.6.0 - "@ethersproject/keccak256": ^5.6.0 - "@ethersproject/logger": ^5.6.0 - "@ethersproject/properties": ^5.6.0 - "@ethersproject/strings": ^5.6.0 - checksum: 7a3b9180963765fff1a307adeb2138219d1585fc979ee2d14888739102ae2b223759cf456a88da554b4043475ec459d3a8dd67a844e39a896f0584c5a9556a06 - languageName: node - linkType: hard - "@ethersproject/hash@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/hash@npm:5.7.0" @@ -4717,16 +4605,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/keccak256@npm:^5.6.0": - version: 5.6.0 - resolution: "@ethersproject/keccak256@npm:5.6.0" - dependencies: - "@ethersproject/bytes": ^5.6.0 - js-sha3: 0.8.0 - checksum: 8683ee5c665ae23c9e1a46be4efb9f208f256abc1885844ec653452ad6dd58d08e5df0d78fc01eef33dc10bca38e27a94390b71a86fae666ef7eddf49860e047 - languageName: node - linkType: hard - "@ethersproject/keccak256@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/keccak256@npm:5.7.0" @@ -4737,13 +4615,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/logger@npm:^5.6.0": - version: 5.6.0 - resolution: "@ethersproject/logger@npm:5.6.0" - checksum: 6eee38a973c7a458552278971c109a3e5df3c257e433cb959da9a287ea04628d1f510d41b83bd5f9da5ddc05d97d307ed2162a9ba1b4fcc50664e4f60061636c - languageName: node - linkType: hard - "@ethersproject/logger@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/logger@npm:5.7.0" @@ -4751,15 +4622,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/networks@npm:^5.6.0": - version: 5.6.1 - resolution: "@ethersproject/networks@npm:5.6.1" - dependencies: - "@ethersproject/logger": ^5.6.0 - checksum: e894369e58b45563653155df6f6db9a919448fc077a74a8fcc3fa10423335250372243868bcc2cc08857f081af6320a3a62d322340d2e5364fb25c258c978b9e - languageName: node - linkType: hard - "@ethersproject/networks@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/networks@npm:5.7.0" @@ -4769,15 +4631,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/properties@npm:^5.6.0": - version: 5.6.0 - resolution: "@ethersproject/properties@npm:5.6.0" - dependencies: - "@ethersproject/logger": ^5.6.0 - checksum: adcb6a843dcdf809262d77d6fbe52acdd48703327b298f78e698b76784e89564fb81791d27eaee72b1a6aaaf5688ea2ae7a95faabdef8b4aecc99989fec55901 - languageName: node - linkType: hard - "@ethersproject/properties@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/properties@npm:5.7.0" @@ -4825,16 +4678,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/rlp@npm:^5.6.0": - version: 5.6.0 - resolution: "@ethersproject/rlp@npm:5.6.0" - dependencies: - "@ethersproject/bytes": ^5.6.0 - "@ethersproject/logger": ^5.6.0 - checksum: 3697871cec540e3bf3fd7a6a65ef3e5ca223f684ac928ecf028619eee251c6c5427b02493c152f057f5e9b07ea216d24f807ec84e5df80414511f8aff5505359 - languageName: node - linkType: hard - "@ethersproject/rlp@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/rlp@npm:5.7.0" @@ -4856,20 +4699,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/signing-key@npm:^5.6.0": - version: 5.6.0 - resolution: "@ethersproject/signing-key@npm:5.6.0" - dependencies: - "@ethersproject/bytes": ^5.6.0 - "@ethersproject/logger": ^5.6.0 - "@ethersproject/properties": ^5.6.0 - bn.js: ^4.11.9 - elliptic: 6.5.4 - hash.js: 1.1.7 - checksum: c61118ff1ff89560da59cf77ca6553762e94b9d2757542be15dcf7eb4962327faede941d8461272afb8550cc2e28d38e7e89d199650ebae07a596df3c2059e08 - languageName: node - linkType: hard - "@ethersproject/signing-key@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/signing-key@npm:5.7.0" @@ -4884,17 +4713,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/strings@npm:^5.6.0": - version: 5.6.0 - resolution: "@ethersproject/strings@npm:5.6.0" - dependencies: - "@ethersproject/bytes": ^5.6.0 - "@ethersproject/constants": ^5.6.0 - "@ethersproject/logger": ^5.6.0 - checksum: 0b69bdd2c2767049599e1b6bbf34782166a1b901fd00a09b2dab0f4a92a6a1e85bb28d498f40f138a68baf62714831b6398e170358c861b4b1e54bfac375b655 - languageName: node - linkType: hard - "@ethersproject/strings@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/strings@npm:5.7.0" @@ -4906,23 +4724,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/transactions@npm:^5.6.0": - version: 5.6.0 - resolution: "@ethersproject/transactions@npm:5.6.0" - dependencies: - "@ethersproject/address": ^5.6.0 - "@ethersproject/bignumber": ^5.6.0 - "@ethersproject/bytes": ^5.6.0 - "@ethersproject/constants": ^5.6.0 - "@ethersproject/keccak256": ^5.6.0 - "@ethersproject/logger": ^5.6.0 - "@ethersproject/properties": ^5.6.0 - "@ethersproject/rlp": ^5.6.0 - "@ethersproject/signing-key": ^5.6.0 - checksum: b01a3a9ce1e2d945825adbecfc71b992293e0274b77caf2c0b3c45bb76919ce64aa888a5705e6745a84448c50fc4eb58ff5e0ad11c37b1aae33c7a7d3b8af883 - languageName: node - linkType: hard - "@ethersproject/transactions@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/transactions@npm:5.7.0" @@ -4940,19 +4741,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/web@npm:^5.6.0": - version: 5.6.0 - resolution: "@ethersproject/web@npm:5.6.0" - dependencies: - "@ethersproject/base64": ^5.6.0 - "@ethersproject/bytes": ^5.6.0 - "@ethersproject/logger": ^5.6.0 - "@ethersproject/properties": ^5.6.0 - "@ethersproject/strings": ^5.6.0 - checksum: 42b6e71658393e5abf8341c6bea0deb22cc744b4d22b3a979ed876bc772723b800c18e5180e223f77c47fb045828ef16ba5a01e8dd346474cf430533d1f053bc - languageName: node - linkType: hard - "@ethersproject/web@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/web@npm:5.7.0" @@ -6115,7 +5903,6 @@ __metadata: "@babel/preset-env": ^7.24.5 "@babel/preset-react": ^7.24.1 "@babel/preset-typescript": ^7.24.1 - "@ethersproject/bignumber": ^5.7.0 "@ethersproject/transactions": ^5.7.0 "@floating-ui/react": ^0.23.0 "@floating-ui/react-dom": ^1.3.0 @@ -6204,6 +5991,7 @@ __metadata: long: ^4.0.0 lottie-web: ^5.10.2 os-browserify: ^0.3.0 + ox: ^0.14.6 p-queue: ^6.6.2 process: ^0.11.10 qrcode.react: ^3.1.0 @@ -6522,12 +6310,12 @@ __metadata: version: 0.0.0-use.local resolution: "@keplr-wallet/stores-etc@workspace:packages/stores-etc" dependencies: - "@ethersproject/abi": ^5.6.0 "@keplr-wallet/common": 0.13.15 "@keplr-wallet/simple-fetch": 0.13.15 "@keplr-wallet/stores": 0.13.15 "@keplr-wallet/types": 0.13.15 "@keplr-wallet/unit": 0.13.15 + ox: ^0.14.6 utility-types: ^3.10.0 peerDependencies: mobx: ^6 @@ -6539,7 +6327,6 @@ __metadata: version: 0.0.0-use.local resolution: "@keplr-wallet/stores-eth@workspace:packages/stores-eth" dependencies: - "@ethersproject/abi": ^5.7.0 "@ethersproject/transactions": ^5.7.0 "@keplr-wallet/common": 0.13.15 "@keplr-wallet/simple-fetch": 0.13.15 From ce138b177cc2d6e4ce4dc949866d0832490bce3a Mon Sep 17 00:00:00 2001 From: Thunnini Date: Fri, 20 Mar 2026 19:45:14 +0900 Subject: [PATCH 05/64] =?UTF-8?q?refactor:=20complete=20ethers.js=20?= =?UTF-8?q?=E2=86=92=20ox=20migration,=20remove=20all=20@ethersproject=20d?= =?UTF-8?q?eps?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 4 — Transaction serialization: - Define UnsignedEVMTransaction type replacing ethers UnsignedTransaction - Add serializeEVMTransaction() helper using ox TxEnvelope (verified byte-identical RLP output) - Replace serialize()/TransactionTypes across 11 files (stores-eth, background, extension sign utils, hardware wallet flows) Phase 5 — EIP-712 and ENS: - Replace _TypedDataEncoder with ox TypedData.hashDomain/hashStruct (verified byte-identical hash output) - Replace JsonRpcProvider ENS resolution with manual RPC calls using ox Ens.namehash + AbiFunction + simpleFetch Remove all @ethersproject dependencies: - @ethersproject/transactions from stores-eth, background, extension - @ethersproject/hash from background - @ethersproject/providers from hooks, hooks-evm Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/extension/package.json | 1 - .../pages/sign/components/eth-tx/registry.tsx | 4 +- .../src/pages/sign/components/eth-tx/types.ts | 6 +- .../sign/ethereum/views/sign-tx-view.tsx | 6 +- .../src/pages/sign/utils/handle-eth-sign.ts | 6 +- .../src/pages/sign/utils/keystone.ts | 8 +- packages/background/package.json | 3 +- .../background/src/keyring-cosmos/eip712.ts | 41 +++---- .../src/keyring-ethereum/service.ts | 33 +++--- .../background/src/tx-executor/service.ts | 6 +- packages/background/src/tx-executor/types.ts | 4 +- .../background/src/tx-executor/utils/evm.ts | 10 +- packages/hooks-evm/package.json | 4 +- packages/hooks-evm/src/tx/name-service-ens.ts | 78 +++++++++++-- packages/hooks/package.json | 2 +- packages/hooks/src/tx/name-service-ens.ts | 78 +++++++++++-- packages/stores-eth/package.json | 1 - packages/stores-eth/src/account/base.ts | 39 ++++--- packages/stores-eth/src/index.ts | 1 + packages/stores-eth/src/serialize.ts | 107 ++++++++++++++++++ packages/stores-eth/src/types.ts | 16 ++- yarn.lock | 97 ++-------------- 22 files changed, 352 insertions(+), 199 deletions(-) create mode 100644 packages/stores-eth/src/serialize.ts diff --git a/apps/extension/package.json b/apps/extension/package.json index a3ba51d2bb..f978dbbd24 100644 --- a/apps/extension/package.json +++ b/apps/extension/package.json @@ -23,7 +23,6 @@ }, "dependencies": { "@amplitude/analytics-browser": "^1.10.3", - "@ethersproject/transactions": "^5.7.0", "@floating-ui/react": "^0.23.0", "@floating-ui/react-dom": "^1.3.0", "@keplr-wallet/analytics": "0.13.15", diff --git a/apps/extension/src/pages/sign/components/eth-tx/registry.tsx b/apps/extension/src/pages/sign/components/eth-tx/registry.tsx index 443135a977..797cd173ec 100644 --- a/apps/extension/src/pages/sign/components/eth-tx/registry.tsx +++ b/apps/extension/src/pages/sign/components/eth-tx/registry.tsx @@ -1,8 +1,8 @@ import React, { FunctionComponent, PropsWithChildren } from "react"; import { useTheme } from "styled-components"; import { ColorPalette } from "../../../../styles"; +import { UnsignedEVMTransaction } from "@keplr-wallet/stores-eth"; import { IEthTxRenderRegistry, IEthTxRenderer } from "./types"; -import { UnsignedTransaction } from "@ethersproject/transactions"; import { EthSendTokenTx } from "./render"; import { EthExecuteContractTx } from "./render/execute-contract"; @@ -15,7 +15,7 @@ export class EthTxRenderRegistry implements IEthTxRenderRegistry { render( chainId: string, - unsignedTx: UnsignedTransaction + unsignedTx: UnsignedEVMTransaction ): { icon?: React.ReactElement; title?: string | React.ReactElement; diff --git a/apps/extension/src/pages/sign/components/eth-tx/types.ts b/apps/extension/src/pages/sign/components/eth-tx/types.ts index fd2debf282..2cdc3a36b4 100644 --- a/apps/extension/src/pages/sign/components/eth-tx/types.ts +++ b/apps/extension/src/pages/sign/components/eth-tx/types.ts @@ -1,10 +1,10 @@ import React from "react"; -import { UnsignedTransaction } from "@ethersproject/transactions"; +import { UnsignedEVMTransaction } from "@keplr-wallet/stores-eth"; export interface IEthTxRenderer { process( chainId: string, - unsignedTx: UnsignedTransaction + unsignedTx: UnsignedEVMTransaction ): | { icon?: React.ReactElement; @@ -19,7 +19,7 @@ export interface IEthTxRenderRegistry { render( chainId: string, - unsignedTx: UnsignedTransaction + unsignedTx: UnsignedEVMTransaction ): { icon?: React.ReactElement; title?: string | React.ReactElement; diff --git a/apps/extension/src/pages/sign/ethereum/views/sign-tx-view.tsx b/apps/extension/src/pages/sign/ethereum/views/sign-tx-view.tsx index 2591f17e41..b9864d722b 100644 --- a/apps/extension/src/pages/sign/ethereum/views/sign-tx-view.tsx +++ b/apps/extension/src/pages/sign/ethereum/views/sign-tx-view.tsx @@ -30,7 +30,7 @@ import { KeystoneSign } from "../../components/keystone"; import styled, { useTheme } from "styled-components"; import SimpleBar from "simplebar-react"; import { ViewDataButton } from "../../components/view-data-button"; -import { UnsignedTransaction } from "@ethersproject/transactions"; +import { UnsignedEVMTransaction } from "@keplr-wallet/stores-eth"; import { defaultRegistry } from "../../components/eth-tx/registry"; import { ChainImageFallback } from "../../../../components/image"; import { Gutter } from "../../../../components/gutter"; @@ -373,7 +373,7 @@ export const EthereumSignTxView: FunctionComponent<{ useEffect(() => { (async () => { if (chainInfo.hasFeature("op-stack-l1-data-fee")) { - const { to, gasLimit, value, data, chainId }: UnsignedTransaction = + const { to, gasLimit, value, data, chainId }: UnsignedEVMTransaction = JSON.parse(Buffer.from(message).toString("utf8")); const l1DataFee = await ethereumAccount.simulateOpStackL1Fee({ @@ -829,7 +829,7 @@ export const EthereumSignTxView: FunctionComponent<{ interactionData.data.chainId, JSON.parse( Buffer.from(interactionData.data.message).toString() - ) as UnsignedTransaction + ) as UnsignedEVMTransaction ); if (icon !== undefined && title !== undefined) { diff --git a/apps/extension/src/pages/sign/utils/handle-eth-sign.ts b/apps/extension/src/pages/sign/utils/handle-eth-sign.ts index 0ee6c27290..236116ac67 100644 --- a/apps/extension/src/pages/sign/utils/handle-eth-sign.ts +++ b/apps/extension/src/pages/sign/utils/handle-eth-sign.ts @@ -23,7 +23,7 @@ import { EIP712MessageValidator, messageHash, } from "@keplr-wallet/background"; -import { serialize, TransactionTypes } from "@ethersproject/transactions"; +import { serializeEVMTransaction } from "@keplr-wallet/stores-eth"; import TransportWebHID from "@ledgerhq/hw-transport-webhid"; import { KeystoneKeys, @@ -277,9 +277,9 @@ export const connectAndSignEthWithLedger = async ( const isEIP1559 = !!tx.maxFeePerGas || !!tx.maxPriorityFeePerGas; if (isEIP1559) { - tx.type = TransactionTypes.eip1559; + tx.type = 2; } - const rlpArray = serialize(tx).replace("0x", ""); + const rlpArray = serializeEVMTransaction(tx).replace("0x", ""); return ethSignatureToBytes( await ethApp.signTransaction( diff --git a/apps/extension/src/pages/sign/utils/keystone.ts b/apps/extension/src/pages/sign/utils/keystone.ts index 1821f0f7df..ffcf5b64f3 100644 --- a/apps/extension/src/pages/sign/utils/keystone.ts +++ b/apps/extension/src/pages/sign/utils/keystone.ts @@ -1,4 +1,4 @@ -import { serialize, TransactionTypes } from "@ethersproject/transactions"; +import { serializeEVMTransaction } from "@keplr-wallet/stores-eth"; import { EthSignType } from "@keplr-wallet/types"; import { KeystoneEthereumSDK } from "@keystonehq/keystone-sdk"; @@ -40,7 +40,7 @@ export function getEthDataTypeFromSignType( const msg = JSON.parse(Buffer.from(message).toString()); const isEIP1559 = !!msg.maxFeePerGas || !!msg.maxPriorityFeePerGas; if (isEIP1559) { - msg.type = TransactionTypes.eip1559; + msg.type = 2; } if (!msg.type) { return KeystoneEthereumSDK.DataType.transaction; @@ -64,12 +64,12 @@ export function encodeEthMessage( const tx = JSON.parse(Buffer.from(message).toString()); const isEIP1559 = !!tx.maxFeePerGas || !!tx.maxPriorityFeePerGas; if (isEIP1559) { - tx.type = TransactionTypes.eip1559; + tx.type = 2; } if (typeof tx.type === "string") { tx.type = +tx.type.replace(/^0x/, ""); } - return Buffer.from(serialize(tx).replace(/^0x/, ""), "hex"); + return Buffer.from(serializeEVMTransaction(tx).replace(/^0x/, ""), "hex"); case EthSignType.MESSAGE: case EthSignType.EIP712: return Buffer.from(message); diff --git a/packages/background/package.json b/packages/background/package.json index 7839aafd76..2c46414177 100644 --- a/packages/background/package.json +++ b/packages/background/package.json @@ -29,8 +29,6 @@ "dependencies": { "@ethereumjs/common": "^2.6.5", "@ethereumjs/tx": "^3.5.2", - "@ethersproject/hash": "^5.7.0", - "@ethersproject/transactions": "^5.7.0", "@keplr-wallet/chain-validator": "0.13.15", "@keplr-wallet/common": "0.13.15", "@keplr-wallet/cosmos": "0.13.15", @@ -40,6 +38,7 @@ "@keplr-wallet/proto-types": "0.13.15", "@keplr-wallet/router": "0.13.15", "@keplr-wallet/simple-fetch": "0.13.15", + "@keplr-wallet/stores-eth": "0.13.15", "@keplr-wallet/types": "0.13.15", "@keplr-wallet/unit": "0.13.15", "@ledgerhq/hw-app-eth": "6.42.8", diff --git a/packages/background/src/keyring-cosmos/eip712.ts b/packages/background/src/keyring-cosmos/eip712.ts index d7c12d23dc..30ead91c1e 100644 --- a/packages/background/src/keyring-cosmos/eip712.ts +++ b/packages/background/src/keyring-cosmos/eip712.ts @@ -1,5 +1,5 @@ import Joi from "joi"; -import { _TypedDataEncoder as TypedDataEncoder } from "@ethersproject/hash"; +import { TypedData } from "ox"; // https://eips.ethereum.org/EIPS/eip-712 @@ -96,35 +96,30 @@ export const domainHash = (message: { types: Record; domain: Record; }): string => - TypedDataEncoder.hashStruct( - "EIP712Domain", - { EIP712Domain: message.types["EIP712Domain"] }, - message.domain - ); + TypedData.hashDomain({ + domain: message.domain, + types: { EIP712Domain: message.types["EIP712Domain"] }, + } as any); // Seems that there is no way to set primary type and the first type becomes primary type. export const messageHash = (message: { types: Record; primaryType: string; message: Record; -}): string => - TypedDataEncoder.from( - (() => { - const types = { ...message.types }; - - delete types["EIP712Domain"]; +}): string => { + const types = { ...message.types }; - const primary = types[message.primaryType]; + delete types["EIP712Domain"]; - if (!primary) { - throw new Error(`No matched primary type: ${message.primaryType}`); - } + const primary = types[message.primaryType]; - delete types[message.primaryType]; + if (!primary) { + throw new Error(`No matched primary type: ${message.primaryType}`); + } - return { - [message.primaryType]: primary, - ...types, - }; - })() - ).hash(message.message); + return TypedData.hashStruct({ + data: message.message, + primaryType: message.primaryType, + types, + } as any); +}; diff --git a/packages/background/src/keyring-ethereum/service.ts b/packages/background/src/keyring-ethereum/service.ts index 91bdbc9fa0..84f24ffc7d 100644 --- a/packages/background/src/keyring-ethereum/service.ts +++ b/packages/background/src/keyring-ethereum/service.ts @@ -22,11 +22,11 @@ import { KeyRingCosmosService, messageHash, } from "../keyring-cosmos"; + import { - serialize, - TransactionTypes, - UnsignedTransaction, -} from "@ethersproject/transactions"; + UnsignedEVMTransaction, + serializeEVMTransaction, +} from "@keplr-wallet/stores-eth"; import { simpleFetch } from "@keplr-wallet/simple-fetch"; import { getBasicAccessPermissionType, PermissionService } from "../permission"; import { BackgroundTxEthereumService } from "../tx-ethereum"; @@ -216,7 +216,7 @@ export class KeyRingEthereumService { !!unsignedTx.maxFeePerGas || !!unsignedTx.maxPriorityFeePerGas; if (isEIP1559) { - unsignedTx.type = TransactionTypes.eip1559; + unsignedTx.type = 2; } delete unsignedTx.from; @@ -224,7 +224,10 @@ export class KeyRingEthereumService { const signature = await this.keyRingService.sign( chainId, vaultId, - Buffer.from(serialize(unsignedTx).replace("0x", ""), "hex"), + Buffer.from( + serializeEVMTransaction(unsignedTx).replace("0x", ""), + "hex" + ), "keccak256" ); @@ -400,19 +403,19 @@ export class KeyRingEthereumService { ); } - const unsignedTx: UnsignedTransaction = JSON.parse( + const unsignedTx: UnsignedEVMTransaction = JSON.parse( Buffer.from(message).toString() ); const isEIP1559 = !!unsignedTx.maxFeePerGas || !!unsignedTx.maxPriorityFeePerGas; if (isEIP1559) { - unsignedTx.type = TransactionTypes.eip1559; + unsignedTx.type = 2; } const signature = await this.keyRingService.sign( chainId, vaultId, - Buffer.from(serialize(unsignedTx).replace("0x", ""), "hex"), + Buffer.from(serializeEVMTransaction(unsignedTx).replace("0x", ""), "hex"), "keccak256" ); @@ -621,11 +624,11 @@ export class KeyRingEthereumService { const isEIP1559 = !!signingTx.maxFeePerGas || !!signingTx.maxPriorityFeePerGas; if (isEIP1559) { - signingTx.type = TransactionTypes.eip1559; + signingTx.type = 2; } const signedTx = Buffer.from( - serialize(signingTx, signature).replace("0x", ""), + serializeEVMTransaction(signingTx, signature).replace("0x", ""), "hex" ); @@ -741,10 +744,10 @@ export class KeyRingEthereumService { const isEIP1559 = !!signingTx.maxFeePerGas || !!signingTx.maxPriorityFeePerGas; if (isEIP1559) { - signingTx.type = TransactionTypes.eip1559; + signingTx.type = 2; } - const signedTx = serialize(signingTx, signature); + const signedTx = serializeEVMTransaction(signingTx, signature); return signedTx; } catch (e) { @@ -1424,7 +1427,7 @@ export class KeyRingEthereumService { tx: any, chainId: string, nonce: number - ): UnsignedTransaction { + ): UnsignedEVMTransaction { const { value, gas, @@ -1436,7 +1439,7 @@ export class KeyRingEthereumService { ...restTx } = tx; - const unsignedTx: UnsignedTransaction = { + const unsignedTx: UnsignedEVMTransaction = { ...restTx, value: this.toHexQty(value), gasLimit: this.toHexQty(gasLimit ?? gas), diff --git a/packages/background/src/tx-executor/service.ts b/packages/background/src/tx-executor/service.ts index 48a2dd3eba..3bfa31c468 100644 --- a/packages/background/src/tx-executor/service.ts +++ b/packages/background/src/tx-executor/service.ts @@ -34,7 +34,7 @@ import { EthSignType, EthTxStatus, } from "@keplr-wallet/types"; -import { TransactionTypes, serialize } from "@ethersproject/transactions"; +import { serializeEVMTransaction } from "@keplr-wallet/stores-eth"; import { BaseAccount } from "@keplr-wallet/cosmos"; import { Any } from "@keplr-wallet/proto-types/google/protobuf/any"; import { TxRaw } from "@keplr-wallet/proto-types/cosmos/tx/v1beta1/tx"; @@ -610,12 +610,12 @@ export class BackgroundTxExecutorService { const isEIP1559 = !!signedTxData.maxFeePerGas || !!signedTxData.maxPriorityFeePerGas; if (isEIP1559) { - signedTxData.type = TransactionTypes.eip1559; + signedTxData.type = 2; } delete signedTxData.from; - return serialize(signedTxData, result.signature); + return serializeEVMTransaction(signedTxData, result.signature); } private async signCosmosTx( diff --git a/packages/background/src/tx-executor/types.ts b/packages/background/src/tx-executor/types.ts index dae13821ca..95b1284710 100644 --- a/packages/background/src/tx-executor/types.ts +++ b/packages/background/src/tx-executor/types.ts @@ -1,4 +1,4 @@ -import { UnsignedTransaction } from "@ethersproject/transactions"; +import { UnsignedEVMTransaction } from "@keplr-wallet/stores-eth"; import { StdFee } from "@keplr-wallet/types"; import { Any } from "@keplr-wallet/proto-types/google/protobuf/any"; import { Msg } from "@keplr-wallet/types"; @@ -50,7 +50,7 @@ interface EVMBackgroundTxBase extends Omit { export interface EVMBackgroundTx extends EVMBackgroundTxBase { readonly type: BackgroundTxType.EVM; - txData: UnsignedTransaction; + txData: UnsignedEVMTransaction; customPriorityFee?: string; customGasPrice?: string; } diff --git a/packages/background/src/tx-executor/utils/evm.ts b/packages/background/src/tx-executor/utils/evm.ts index 7c91e36992..9c9c7032d5 100644 --- a/packages/background/src/tx-executor/utils/evm.ts +++ b/packages/background/src/tx-executor/utils/evm.ts @@ -1,6 +1,6 @@ import { EVMInfo } from "@keplr-wallet/types"; import { simpleFetch } from "@keplr-wallet/simple-fetch"; -import { UnsignedTransaction } from "@ethersproject/transactions"; +import { UnsignedEVMTransaction } from "@keplr-wallet/stores-eth"; import { Dec } from "@keplr-wallet/unit"; import { BackgroundTxFeeType, EVMBackgroundTxFeeType } from "../types"; import { JsonRpcResponse } from "@keplr-wallet/types"; @@ -60,11 +60,11 @@ export async function fillUnsignedEVMTx( origin: string, evmInfo: EVMInfo, signer: string, - tx: UnsignedTransaction, + tx: UnsignedEVMTransaction, feeType: EVMBackgroundTxFeeType = "average", customPriorityFee?: string, customGasPrice?: string -): Promise { +): Promise { const hasProvidedPriorityFee = tx.maxPriorityFeePerGas != null; const hasProvidedGasLimit = tx.gasLimit != null; const innerFeeType: BackgroundTxFeeType = @@ -193,7 +193,7 @@ export async function fillUnsignedEVMTx( ? undefined : getResult(ESTIMATE_GAS_ID, true); - let finalGasLimit: UnsignedTransaction["gasLimit"]; + let finalGasLimit: UnsignedEVMTransaction["gasLimit"]; if (tx.gasLimit != null) { finalGasLimit = tx.gasLimit; } else if (gasLimitHex) { @@ -358,7 +358,7 @@ export async function fillUnsignedEVMTx( .toBigNumber() .toString(16)}`; - const newUnsignedTx: UnsignedTransaction = { + const newUnsignedTx: UnsignedEVMTransaction = { ...tx, nonce: finalNonce, maxFeePerGas: maxFeePerGasHex, diff --git a/packages/hooks-evm/package.json b/packages/hooks-evm/package.json index 667f5c3276..9a5db5f7ac 100644 --- a/packages/hooks-evm/package.json +++ b/packages/hooks-evm/package.json @@ -21,7 +21,6 @@ "typecheck": "npx tsc -p tsconfig.check.json" }, "dependencies": { - "@ethersproject/providers": "^5.7.0", "@keplr-wallet/common": "0.13.15", "@keplr-wallet/cosmos": "0.13.15", "@keplr-wallet/simple-fetch": "0.13.15", @@ -29,7 +28,8 @@ "@keplr-wallet/stores-eth": "0.13.15", "@keplr-wallet/types": "0.13.15", "@keplr-wallet/unit": "0.13.15", - "buffer": "^6.0.3" + "buffer": "^6.0.3", + "ox": "^0.14.6" }, "peerDependencies": { "mobx": "^6", diff --git a/packages/hooks-evm/src/tx/name-service-ens.ts b/packages/hooks-evm/src/tx/name-service-ens.ts index f31e17ffc4..d3c9e135b8 100644 --- a/packages/hooks-evm/src/tx/name-service-ens.ts +++ b/packages/hooks-evm/src/tx/name-service-ens.ts @@ -1,9 +1,75 @@ import { action, autorun, makeObservable, observable, runInAction } from "mobx"; import { ChainGetter } from "@keplr-wallet/stores"; import { FetchDebounce, NameService } from "./name-service"; -import { JsonRpcProvider } from "@ethersproject/providers"; +import { simpleFetch } from "@keplr-wallet/simple-fetch"; +import { AbiFunction, Ens, Hex } from "ox"; import { ITxChainSetter } from "./types"; +const ENS_REGISTRY: Hex.Hex = "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"; +const resolverAbi = AbiFunction.from( + "function resolver(bytes32 node) view returns (address)" +); +const addrAbi = AbiFunction.from( + "function addr(bytes32 node) view returns (address)" +); + +async function resolveENS( + rpcUrl: string, + name: string +): Promise { + const node = Ens.namehash(name); + + const resolverCalldata = AbiFunction.encodeData(resolverAbi, [node]); + const resolverResult = await ethCall(rpcUrl, ENS_REGISTRY, resolverCalldata); + const resolverAddress = AbiFunction.decodeResult(resolverAbi, resolverResult); + + if ( + !resolverAddress || + resolverAddress === "0x0000000000000000000000000000000000000000" + ) { + return undefined; + } + + const addrCalldata = AbiFunction.encodeData(addrAbi, [node]); + const addrResult = await ethCall(rpcUrl, resolverAddress, addrCalldata); + const addr = AbiFunction.decodeResult(addrAbi, addrResult); + + if (!addr || addr === "0x0000000000000000000000000000000000000000") { + return undefined; + } + + return addr; +} + +async function ethCall( + rpcUrl: string, + to: string, + data: string +): Promise { + const res = await simpleFetch<{ + result?: string; + error?: { message: string }; + }>(rpcUrl, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + jsonrpc: "2.0", + id: 1, + method: "eth_call", + params: [{ to, data }, "latest"], + }), + }); + + if (res.data.error) { + throw new Error(res.data.error.message); + } + if (!res.data.result) { + throw new Error("No result from eth_call"); + } + + return res.data.result as Hex.Hex; +} + export class ENSNameService implements NameService { readonly type = "ens"; @@ -162,15 +228,7 @@ export class ENSNameService implements NameService { const domain = this.value; const username = domain + "." + suffix; - const resolver = await new JsonRpcProvider(ensU.evm.rpc).getResolver( - username - ); - - if (!resolver) { - throw new Error("Can't find resolver"); - } - - const res = await resolver.getAddress(60); + const res = await resolveENS(ensU.evm.rpc, username); if (this.value === prevValue) { if (res) { diff --git a/packages/hooks/package.json b/packages/hooks/package.json index cfceb3cdc5..945f10ef63 100644 --- a/packages/hooks/package.json +++ b/packages/hooks/package.json @@ -21,7 +21,6 @@ "typecheck": "npx tsc -p tsconfig.check.json" }, "dependencies": { - "@ethersproject/providers": "^5.7.0", "@keplr-wallet/background": "0.13.15", "@keplr-wallet/common": "0.13.15", "@keplr-wallet/cosmos": "0.13.15", @@ -35,6 +34,7 @@ "@keplr-wallet/unit": "0.13.15", "buffer": "^6.0.3", "long": "^4.0.0", + "ox": "^0.14.6", "utility-types": "^3.10.0" }, "peerDependencies": { diff --git a/packages/hooks/src/tx/name-service-ens.ts b/packages/hooks/src/tx/name-service-ens.ts index dcafa474dc..e89281ccb4 100644 --- a/packages/hooks/src/tx/name-service-ens.ts +++ b/packages/hooks/src/tx/name-service-ens.ts @@ -1,9 +1,75 @@ import { action, autorun, makeObservable, observable, runInAction } from "mobx"; import { ChainGetter } from "@keplr-wallet/stores"; import { FetchDebounce, NameService } from "./name-service"; -import { JsonRpcProvider } from "@ethersproject/providers"; +import { simpleFetch } from "@keplr-wallet/simple-fetch"; +import { AbiFunction, Ens, Hex } from "ox"; import { ITxChainSetter } from "./types"; +const ENS_REGISTRY: Hex.Hex = "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"; +const resolverAbi = AbiFunction.from( + "function resolver(bytes32 node) view returns (address)" +); +const addrAbi = AbiFunction.from( + "function addr(bytes32 node) view returns (address)" +); + +async function resolveENS( + rpcUrl: string, + name: string +): Promise { + const node = Ens.namehash(name); + + const resolverCalldata = AbiFunction.encodeData(resolverAbi, [node]); + const resolverResult = await ethCall(rpcUrl, ENS_REGISTRY, resolverCalldata); + const resolverAddress = AbiFunction.decodeResult(resolverAbi, resolverResult); + + if ( + !resolverAddress || + resolverAddress === "0x0000000000000000000000000000000000000000" + ) { + return undefined; + } + + const addrCalldata = AbiFunction.encodeData(addrAbi, [node]); + const addrResult = await ethCall(rpcUrl, resolverAddress, addrCalldata); + const addr = AbiFunction.decodeResult(addrAbi, addrResult); + + if (!addr || addr === "0x0000000000000000000000000000000000000000") { + return undefined; + } + + return addr; +} + +async function ethCall( + rpcUrl: string, + to: string, + data: string +): Promise { + const res = await simpleFetch<{ + result?: string; + error?: { message: string }; + }>(rpcUrl, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + jsonrpc: "2.0", + id: 1, + method: "eth_call", + params: [{ to, data }, "latest"], + }), + }); + + if (res.data.error) { + throw new Error(res.data.error.message); + } + if (!res.data.result) { + throw new Error("No result from eth_call"); + } + + return res.data.result as Hex.Hex; +} + export class ENSNameService implements NameService { readonly type = "ens"; @@ -164,15 +230,7 @@ export class ENSNameService implements NameService { throw new Error("ENS chain must be an EVM chain"); } - const resolver = await new JsonRpcProvider(ensU.evm.rpc).getResolver( - username - ); - - if (!resolver) { - throw new Error("Can't find resolver"); - } - - const res = await resolver.getAddress(60); + const res = await resolveENS(ensU.evm.rpc, username); if (this.value === prevValue) { if (res) { diff --git a/packages/stores-eth/package.json b/packages/stores-eth/package.json index 754f76d067..8ebfedb6a6 100644 --- a/packages/stores-eth/package.json +++ b/packages/stores-eth/package.json @@ -21,7 +21,6 @@ "typecheck": "npx tsc -p tsconfig.check.json" }, "dependencies": { - "@ethersproject/transactions": "^5.7.0", "@keplr-wallet/common": "0.13.15", "@keplr-wallet/simple-fetch": "0.13.15", "@keplr-wallet/stores": "0.13.15", diff --git a/packages/stores-eth/src/account/base.ts b/packages/stores-eth/src/account/base.ts index 688702eb66..fed414c509 100644 --- a/packages/stores-eth/src/account/base.ts +++ b/packages/stores-eth/src/account/base.ts @@ -10,11 +10,6 @@ import { } from "@keplr-wallet/types"; import { DenomHelper, retry } from "@keplr-wallet/common"; -import { - TransactionTypes, - UnsignedTransaction, - serialize, -} from "@ethersproject/transactions"; import { action, makeObservable, observable } from "mobx"; import { AbiFunction, Address, Hex, Value } from "ox"; import { erc20TransferFunction, erc20ApproveFunction } from "../constants"; @@ -23,8 +18,10 @@ import { AccountStateDiffTracerResult, SimulateGasWithPendingErc20ApprovalResult, StateOverride, + UnsignedEVMTransaction, UnsignedEVMTransactionWithErc20Approvals, } from "../types"; +import { serializeEVMTransaction } from "../serialize"; import { simpleFetch } from "@keplr-wallet/simple-fetch"; const opStackGasPriceOracleProxyAddress = @@ -135,7 +132,7 @@ export class EthereumAccountBase { async simulateGas( sender: string, - unsignedTx: UnsignedTransaction, + unsignedTx: UnsignedEVMTransaction, stateOverride?: StateOverride ) { const u = this.chainGetter.getModularChain(this.chainId).unwrapped; @@ -197,7 +194,7 @@ export class EthereumAccountBase { const parsedAmount = Value.from(amount, currency.coinDecimals); const denomHelper = new DenomHelper(currency.coinMinimalDenom); - const unsignedTx: UnsignedTransaction = (() => { + const unsignedTx: UnsignedEVMTransaction = (() => { switch (denomHelper.type) { case "erc20": return { @@ -314,7 +311,9 @@ export class EthereumAccountBase { } } - async simulateOpStackL1Fee(unsignedTx: UnsignedTransaction): Promise { + async simulateOpStackL1Fee( + unsignedTx: UnsignedEVMTransaction + ): Promise { const mcInfo = this.chainGetter.getModularChain(this.chainId); if (!mcInfo.hasFeature("op-stack-l1-data-fee")) { throw new Error("The chain isn't built with OP Stack"); @@ -329,7 +328,7 @@ export class EthereumAccountBase { { to: opStackGasPriceOracleProxyAddress, data: AbiFunction.encodeData(opStackGetL1FeeFunction, [ - serialize(unsignedTx) as `0x${string}`, + serializeEVMTransaction(unsignedTx) as `0x${string}`, ]), }, "latest", @@ -354,7 +353,7 @@ export class EthereumAccountBase { } const evmInfo = u.evm; - const txsToSimulate: UnsignedTransaction[] = [ + const txsToSimulate: UnsignedEVMTransaction[] = [ { to: unsignedTx.to, gasLimit: unsignedTx.gasLimit, @@ -384,7 +383,7 @@ export class EthereumAccountBase { { to: opStackGasPriceOracleProxyAddress, data: AbiFunction.encodeData(opStackGetL1FeeFunction, [ - serialize(tx) as `0x${string}`, + serializeEVMTransaction(tx) as `0x${string}`, ]), }, "latest", @@ -427,7 +426,7 @@ export class EthereumAccountBase { maxFeePerGas?: string; maxPriorityFeePerGas?: string; gasPrice?: string; - }): UnsignedTransaction { + }): UnsignedEVMTransaction { const parsedAmount = Value.from(amount, currency.coinDecimals); const denomHelper = new DenomHelper(currency.coinMinimalDenom); const feeObject = @@ -443,7 +442,7 @@ export class EthereumAccountBase { }; // Support EIP-1559 transaction only. - const unsignedTx: UnsignedTransaction = (() => { + const unsignedTx: UnsignedEVMTransaction = (() => { switch (denomHelper.type) { case "erc20": return { @@ -472,7 +471,7 @@ export class EthereumAccountBase { currency: ERC20Currency, spender: string, amount: string - ): UnsignedTransaction { + ): UnsignedEVMTransaction { const parsedAmount = Value.from(amount, currency.coinDecimals); return this.makeTx( @@ -485,7 +484,7 @@ export class EthereumAccountBase { ); } - makeTx(to: string, value: string, data?: string): UnsignedTransaction { + makeTx(to: string, value: string, data?: string): UnsignedEVMTransaction { const u = this.chainGetter.getModularChain(this.chainId).unwrapped; if (u.type !== "evm" && u.type !== "ethermint") { throw new Error("No EVM chain info provided"); @@ -550,15 +549,15 @@ export class EthereumAccountBase { const isEIP1559 = !!unsignedTx.maxFeePerGas || !!unsignedTx.maxPriorityFeePerGas; if (isEIP1559) { - unsignedTx.type = TransactionTypes.eip1559; + unsignedTx.type = 2; } - return serialize(unsignedTx, signature); + return serializeEVMTransaction(unsignedTx, signature); } async sendEthereumTx( sender: string, - unsignedTx: UnsignedTransaction, + unsignedTx: UnsignedEVMTransaction, onTxEvents?: { onBroadcastFailed?: (e?: Error) => void; onBroadcasted?: (txHash: string) => void; @@ -600,11 +599,11 @@ export class EthereumAccountBase { const isEIP1559 = !!unsignedTx.maxFeePerGas || !!unsignedTx.maxPriorityFeePerGas; if (isEIP1559) { - unsignedTx.type = TransactionTypes.eip1559; + unsignedTx.type = 2; } const signedTx = Buffer.from( - serialize(unsignedTx, signature).replace("0x", ""), + serializeEVMTransaction(unsignedTx, signature).replace("0x", ""), "hex" ); diff --git a/packages/stores-eth/src/index.ts b/packages/stores-eth/src/index.ts index 059a3ffdea..9f41b56298 100644 --- a/packages/stores-eth/src/index.ts +++ b/packages/stores-eth/src/index.ts @@ -3,3 +3,4 @@ export * from "./account"; export * from "./constants"; export * from "./currency-registrar"; export * from "./types"; +export * from "./serialize"; diff --git a/packages/stores-eth/src/serialize.ts b/packages/stores-eth/src/serialize.ts new file mode 100644 index 0000000000..6813141bdd --- /dev/null +++ b/packages/stores-eth/src/serialize.ts @@ -0,0 +1,107 @@ +import { TxEnvelopeEip1559, TxEnvelopeLegacy } from "ox"; +import { UnsignedEVMTransaction } from "./types"; + +/** + * Convert a string/number field to bigint. + * Handles hex strings ("0x..."), decimal strings, and numbers. + */ +function toBigInt(value: string | number | undefined): bigint | undefined { + if (value == null) { + return undefined; + } + return BigInt(value); +} + +/** + * Signature format used by the existing codebase. + * This is the ethers-compatible signature format (r, s as hex strings, v as number). + * Can also be raw bytes (Uint8Array/Buffer of 65 bytes: r[32] + s[32] + v[1]). + */ +export type EVMSignatureLike = + | { + r: string; + s: string; + v: number; + } + | Uint8Array; + +function parseSignature( + sig: EVMSignatureLike +): { r: bigint; s: bigint; yParity: number } | undefined { + if (sig instanceof Uint8Array || Buffer.isBuffer(sig)) { + if (sig.length !== 65) { + throw new Error( + `Invalid signature length: expected 65 bytes, got ${sig.length}` + ); + } + const r = BigInt("0x" + Buffer.from(sig.slice(0, 32)).toString("hex")); + const s = BigInt("0x" + Buffer.from(sig.slice(32, 64)).toString("hex")); + const v = sig[64]; + // v can be 0/1 or 27/28 + const yParity = v >= 27 ? v - 27 : v; + return { r, s, yParity }; + } + + const rHex = sig.r.startsWith("0x") ? sig.r : `0x${sig.r}`; + const sHex = sig.s.startsWith("0x") ? sig.s : `0x${sig.s}`; + const v = sig.v; + // v can be 0/1 or 27/28 + const yParity = v >= 27 ? v - 27 : v; + return { r: BigInt(rHex), s: BigInt(sHex), yParity }; +} + +/** + * Serialize an unsigned EVM transaction (and optionally sign it) using ox. + * + * This replaces `serialize()` from `@ethersproject/transactions`. + * The output is identical RLP encoding with "0x" prefix. + */ +export function serializeEVMTransaction( + tx: UnsignedEVMTransaction, + signature?: EVMSignatureLike +): string { + const sig = signature ? parseSignature(signature) : undefined; + + const isEIP1559 = + tx.type === 2 || tx.maxFeePerGas != null || tx.maxPriorityFeePerGas != null; + + if (isEIP1559) { + const envelope: Parameters[0] = { + chainId: tx.chainId ?? 1, + nonce: toBigInt(tx.nonce), + gas: toBigInt(tx.gasLimit), + maxFeePerGas: toBigInt(tx.maxFeePerGas), + maxPriorityFeePerGas: toBigInt(tx.maxPriorityFeePerGas), + to: tx.to as `0x${string}` | undefined, + value: toBigInt(tx.value), + data: tx.data as `0x${string}` | undefined, + accessList: tx.accessList as + | readonly { + address: `0x${string}`; + storageKeys: readonly `0x${string}`[]; + }[] + | undefined, + }; + + return TxEnvelopeEip1559.serialize( + envelope, + sig ? { signature: sig } : undefined + ); + } + + // Legacy transaction + const envelope: Parameters[0] = { + chainId: tx.chainId, + nonce: toBigInt(tx.nonce), + gas: toBigInt(tx.gasLimit), + gasPrice: toBigInt(tx.gasPrice), + to: tx.to as `0x${string}` | undefined, + value: toBigInt(tx.value), + data: tx.data as `0x${string}` | undefined, + }; + + return TxEnvelopeLegacy.serialize( + envelope, + sig ? { signature: sig } : undefined + ); +} diff --git a/packages/stores-eth/src/types.ts b/packages/stores-eth/src/types.ts index b5b0f2fdfc..d217962ee5 100644 --- a/packages/stores-eth/src/types.ts +++ b/packages/stores-eth/src/types.ts @@ -1,7 +1,19 @@ -import { UnsignedTransaction } from "@ethersproject/transactions"; import { EvmGasSimulationOutcome } from "@keplr-wallet/types"; -export type UnsignedEVMTransaction = UnsignedTransaction; +export type UnsignedEVMTransaction = { + to?: string; + from?: string; + nonce?: number; + gasLimit?: string | number; + gasPrice?: string | number; + data?: string; + value?: string | number; + chainId?: number; + type?: number | null; + accessList?: Array<{ address: string; storageKeys: Array }>; + maxPriorityFeePerGas?: string | number; + maxFeePerGas?: string | number; +}; export type UnsignedEVMTransactionWithErc20Approvals = UnsignedEVMTransaction & { diff --git a/yarn.lock b/yarn.lock index fe3a5a72fc..968a6c515d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4549,16 +4549,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/basex@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/basex@npm:5.7.0" - dependencies: - "@ethersproject/bytes": ^5.7.0 - "@ethersproject/properties": ^5.7.0 - checksum: 326087b7e1f3787b5fe6cd1cf2b4b5abfafbc355a45e88e22e5e9d6c845b613ffc5301d629b28d5c4d5e2bfe9ec424e6782c804956dff79be05f0098cb5817de - languageName: node - linkType: hard - "@ethersproject/bignumber@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/bignumber@npm:5.7.0" @@ -4640,44 +4630,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/providers@npm:^5.7.0": - version: 5.7.2 - resolution: "@ethersproject/providers@npm:5.7.2" - dependencies: - "@ethersproject/abstract-provider": ^5.7.0 - "@ethersproject/abstract-signer": ^5.7.0 - "@ethersproject/address": ^5.7.0 - "@ethersproject/base64": ^5.7.0 - "@ethersproject/basex": ^5.7.0 - "@ethersproject/bignumber": ^5.7.0 - "@ethersproject/bytes": ^5.7.0 - "@ethersproject/constants": ^5.7.0 - "@ethersproject/hash": ^5.7.0 - "@ethersproject/logger": ^5.7.0 - "@ethersproject/networks": ^5.7.0 - "@ethersproject/properties": ^5.7.0 - "@ethersproject/random": ^5.7.0 - "@ethersproject/rlp": ^5.7.0 - "@ethersproject/sha2": ^5.7.0 - "@ethersproject/strings": ^5.7.0 - "@ethersproject/transactions": ^5.7.0 - "@ethersproject/web": ^5.7.0 - bech32: 1.1.4 - ws: 7.4.6 - checksum: 1754c731a5ca6782ae9677f4a9cd8b6246c4ef21a966c9a01b133750f3c578431ec43ec254e699969c4a0f87e84463ded50f96b415600aabd37d2056aee58c19 - languageName: node - linkType: hard - -"@ethersproject/random@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/random@npm:5.7.0" - dependencies: - "@ethersproject/bytes": ^5.7.0 - "@ethersproject/logger": ^5.7.0 - checksum: 017829c91cff6c76470852855108115b0b52c611b6be817ed1948d56ba42d6677803ec2012aa5ae298a7660024156a64c11fcf544e235e239ab3f89f0fff7345 - languageName: node - linkType: hard - "@ethersproject/rlp@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/rlp@npm:5.7.0" @@ -4688,17 +4640,6 @@ __metadata: languageName: node linkType: hard -"@ethersproject/sha2@npm:^5.7.0": - version: 5.7.0 - resolution: "@ethersproject/sha2@npm:5.7.0" - dependencies: - "@ethersproject/bytes": ^5.7.0 - "@ethersproject/logger": ^5.7.0 - hash.js: 1.1.7 - checksum: 09321057c022effbff4cc2d9b9558228690b5dd916329d75c4b1ffe32ba3d24b480a367a7cc92d0f0c0b1c896814d03351ae4630e2f1f7160be2bcfbde435dbc - languageName: node - linkType: hard - "@ethersproject/signing-key@npm:^5.7.0": version: 5.7.0 resolution: "@ethersproject/signing-key@npm:5.7.0" @@ -5789,8 +5730,6 @@ __metadata: dependencies: "@ethereumjs/common": ^2.6.5 "@ethereumjs/tx": ^3.5.2 - "@ethersproject/hash": ^5.7.0 - "@ethersproject/transactions": ^5.7.0 "@keplr-wallet/chain-validator": 0.13.15 "@keplr-wallet/common": 0.13.15 "@keplr-wallet/cosmos": 0.13.15 @@ -5800,6 +5739,7 @@ __metadata: "@keplr-wallet/proto-types": 0.13.15 "@keplr-wallet/router": 0.13.15 "@keplr-wallet/simple-fetch": 0.13.15 + "@keplr-wallet/stores-eth": 0.13.15 "@keplr-wallet/types": 0.13.15 "@keplr-wallet/unit": 0.13.15 "@ledgerhq/hw-app-eth": 6.42.8 @@ -5903,7 +5843,6 @@ __metadata: "@babel/preset-env": ^7.24.5 "@babel/preset-react": ^7.24.1 "@babel/preset-typescript": ^7.24.1 - "@ethersproject/transactions": ^5.7.0 "@floating-ui/react": ^0.23.0 "@floating-ui/react-dom": ^1.3.0 "@keplr-wallet/analytics": 0.13.15 @@ -6072,7 +6011,6 @@ __metadata: version: 0.0.0-use.local resolution: "@keplr-wallet/hooks-evm@workspace:packages/hooks-evm" dependencies: - "@ethersproject/providers": ^5.7.0 "@keplr-wallet/common": 0.13.15 "@keplr-wallet/cosmos": 0.13.15 "@keplr-wallet/simple-fetch": 0.13.15 @@ -6081,6 +6019,7 @@ __metadata: "@keplr-wallet/types": 0.13.15 "@keplr-wallet/unit": 0.13.15 buffer: ^6.0.3 + ox: ^0.14.6 peerDependencies: mobx: ^6 mobx-utils: ^6 @@ -6137,7 +6076,6 @@ __metadata: version: 0.0.0-use.local resolution: "@keplr-wallet/hooks@workspace:packages/hooks" dependencies: - "@ethersproject/providers": ^5.7.0 "@keplr-wallet/background": 0.13.15 "@keplr-wallet/common": 0.13.15 "@keplr-wallet/cosmos": 0.13.15 @@ -6151,6 +6089,7 @@ __metadata: "@keplr-wallet/unit": 0.13.15 buffer: ^6.0.3 long: ^4.0.0 + ox: ^0.14.6 utility-types: ^3.10.0 peerDependencies: mobx: ^6 @@ -6327,7 +6266,6 @@ __metadata: version: 0.0.0-use.local resolution: "@keplr-wallet/stores-eth@workspace:packages/stores-eth" dependencies: - "@ethersproject/transactions": ^5.7.0 "@keplr-wallet/common": 0.13.15 "@keplr-wallet/simple-fetch": 0.13.15 "@keplr-wallet/stores": 0.13.15 @@ -13441,13 +13379,6 @@ __metadata: languageName: node linkType: hard -"bech32@npm:1.1.4, bech32@npm:^1.1.4": - version: 1.1.4 - resolution: "bech32@npm:1.1.4" - checksum: 0e98db619191548390d6f09ff68b0253ba7ae6a55db93dfdbb070ba234c1fd3308c0606fbcc95fad50437227b10011e2698b89f0181f6e7f845c499bd14d0f4b - languageName: node - linkType: hard - "bech32@npm:2.0.0, bech32@npm:^2.0.0": version: 2.0.0 resolution: "bech32@npm:2.0.0" @@ -13455,6 +13386,13 @@ __metadata: languageName: node linkType: hard +"bech32@npm:^1.1.4": + version: 1.1.4 + resolution: "bech32@npm:1.1.4" + checksum: 0e98db619191548390d6f09ff68b0253ba7ae6a55db93dfdbb070ba234c1fd3308c0606fbcc95fad50437227b10011e2698b89f0181f6e7f845c499bd14d0f4b + languageName: node + linkType: hard + "before-after-hook@npm:^2.2.0": version: 2.2.2 resolution: "before-after-hook@npm:2.2.2" @@ -34577,21 +34515,6 @@ __metadata: languageName: node linkType: hard -"ws@npm:7.4.6": - version: 7.4.6 - resolution: "ws@npm:7.4.6" - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - checksum: 3a990b32ed08c72070d5e8913e14dfcd831919205be52a3ff0b4cdd998c8d554f167c9df3841605cde8b11d607768cacab3e823c58c96a5c08c987e093eb767a - languageName: node - linkType: hard - "ws@npm:^5.1.1": version: 5.2.3 resolution: "ws@npm:5.2.3" From 8f6606d65f35334c4c36b5ad548f7ac202e51c30 Mon Sep 17 00:00:00 2001 From: Thunnini Date: Mon, 23 Mar 2026 18:01:57 +0900 Subject: [PATCH 06/64] fix: validate ERC20 transfer selector before decoding tx data ox AbiFunction.decodeData does not verify the function selector, unlike ethers decodeFunctionData. This caused approve transactions to be incorrectly decoded as transfers, rendering the wrong sign view. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/pages/sign/components/eth-tx/render/send-token.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/extension/src/pages/sign/components/eth-tx/render/send-token.tsx b/apps/extension/src/pages/sign/components/eth-tx/render/send-token.tsx index 6c9eb9a814..f3a3100fe4 100644 --- a/apps/extension/src/pages/sign/components/eth-tx/render/send-token.tsx +++ b/apps/extension/src/pages/sign/components/eth-tx/render/send-token.tsx @@ -19,6 +19,11 @@ export const EthSendTokenTx: IEthTxRenderer = { process(chainId, unsignedTx) { if (unsignedTx.data && unsignedTx.data !== "0x" && unsignedTx.to) { try { + const selector = unsignedTx.data.slice(0, 10); + if (selector !== AbiFunction.getSelector(erc20TransferFunction)) { + return undefined; + } + const dataDecodedValues = AbiFunction.decodeData( erc20TransferFunction, unsignedTx.data as `0x${string}` From 9db299837c108db998652bd02192cb3b9c431a34 Mon Sep 17 00:00:00 2001 From: Thunnini Date: Mon, 23 Mar 2026 18:12:46 +0900 Subject: [PATCH 07/64] fix: use ENSIP-9 addr(bytes32,uint256) for ENS resolution The previous migration used ENSIP-1 addr(bytes32) which only returns ETH addresses. ethers getAddress(60) used ENSIP-9 addr(bytes32,uint256) with coinType 60. Restore the same behavior to support resolvers that only implement the multi-coin address interface. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/hooks-evm/src/tx/name-service-ens.ts | 12 ++++++------ packages/hooks/src/tx/name-service-ens.ts | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/hooks-evm/src/tx/name-service-ens.ts b/packages/hooks-evm/src/tx/name-service-ens.ts index d3c9e135b8..7199035ccf 100644 --- a/packages/hooks-evm/src/tx/name-service-ens.ts +++ b/packages/hooks-evm/src/tx/name-service-ens.ts @@ -2,7 +2,7 @@ import { action, autorun, makeObservable, observable, runInAction } from "mobx"; import { ChainGetter } from "@keplr-wallet/stores"; import { FetchDebounce, NameService } from "./name-service"; import { simpleFetch } from "@keplr-wallet/simple-fetch"; -import { AbiFunction, Ens, Hex } from "ox"; +import { AbiFunction, Address, Ens, Hex } from "ox"; import { ITxChainSetter } from "./types"; const ENS_REGISTRY: Hex.Hex = "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"; @@ -10,7 +10,7 @@ const resolverAbi = AbiFunction.from( "function resolver(bytes32 node) view returns (address)" ); const addrAbi = AbiFunction.from( - "function addr(bytes32 node) view returns (address)" + "function addr(bytes32 node, uint256 coinType) view returns (bytes)" ); async function resolveENS( @@ -30,15 +30,15 @@ async function resolveENS( return undefined; } - const addrCalldata = AbiFunction.encodeData(addrAbi, [node]); + const addrCalldata = AbiFunction.encodeData(addrAbi, [node, BigInt(60)]); const addrResult = await ethCall(rpcUrl, resolverAddress, addrCalldata); - const addr = AbiFunction.decodeResult(addrAbi, addrResult); + const addrBytes = AbiFunction.decodeResult(addrAbi, addrResult); - if (!addr || addr === "0x0000000000000000000000000000000000000000") { + if (!addrBytes || Hex.size(addrBytes as Hex.Hex) !== 20) { return undefined; } - return addr; + return Address.from(addrBytes as `0x${string}`); } async function ethCall( diff --git a/packages/hooks/src/tx/name-service-ens.ts b/packages/hooks/src/tx/name-service-ens.ts index e89281ccb4..11bf73181a 100644 --- a/packages/hooks/src/tx/name-service-ens.ts +++ b/packages/hooks/src/tx/name-service-ens.ts @@ -2,7 +2,7 @@ import { action, autorun, makeObservable, observable, runInAction } from "mobx"; import { ChainGetter } from "@keplr-wallet/stores"; import { FetchDebounce, NameService } from "./name-service"; import { simpleFetch } from "@keplr-wallet/simple-fetch"; -import { AbiFunction, Ens, Hex } from "ox"; +import { AbiFunction, Address, Ens, Hex } from "ox"; import { ITxChainSetter } from "./types"; const ENS_REGISTRY: Hex.Hex = "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"; @@ -10,7 +10,7 @@ const resolverAbi = AbiFunction.from( "function resolver(bytes32 node) view returns (address)" ); const addrAbi = AbiFunction.from( - "function addr(bytes32 node) view returns (address)" + "function addr(bytes32 node, uint256 coinType) view returns (bytes)" ); async function resolveENS( @@ -30,15 +30,15 @@ async function resolveENS( return undefined; } - const addrCalldata = AbiFunction.encodeData(addrAbi, [node]); + const addrCalldata = AbiFunction.encodeData(addrAbi, [node, BigInt(60)]); const addrResult = await ethCall(rpcUrl, resolverAddress, addrCalldata); - const addr = AbiFunction.decodeResult(addrAbi, addrResult); + const addrBytes = AbiFunction.decodeResult(addrAbi, addrResult); - if (!addr || addr === "0x0000000000000000000000000000000000000000") { + if (!addrBytes || Hex.size(addrBytes as Hex.Hex) !== 20) { return undefined; } - return addr; + return Address.from(addrBytes as `0x${string}`); } async function ethCall( From 5cf33b77aca859f85c7721cc363a53ccadeb171a Mon Sep 17 00:00:00 2001 From: Thunnini Date: Mon, 23 Mar 2026 18:34:08 +0900 Subject: [PATCH 08/64] refactor: align EIP-712 validator types with ox TypedData MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace hand-written {name: string, type: string} types with TypedData.Parameter and TypedData.TypedData from ox. This removes both `as any` casts that were added during the ethers→ox migration. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../background/src/keyring-cosmos/eip712.ts | 42 ++++++------------- 1 file changed, 12 insertions(+), 30 deletions(-) diff --git a/packages/background/src/keyring-cosmos/eip712.ts b/packages/background/src/keyring-cosmos/eip712.ts index 30ead91c1e..28b8efc47e 100644 --- a/packages/background/src/keyring-cosmos/eip712.ts +++ b/packages/background/src/keyring-cosmos/eip712.ts @@ -6,10 +6,7 @@ import { TypedData } from "ox"; // XXX: ledger의 서명을 frontend에서 다루게 되면서 밑의 함수들은 사실 frontend에서 사용된다. // 뭔가 이상해진 부분임 -export const EIP712PropertyFieldValidator = Joi.object<{ - name: string; - type: string; -}>({ +export const EIP712PropertyFieldValidator = Joi.object({ name: Joi.string().min(1).required(), // TODO: Check valid types (string, bool, address, uint256...) type: Joi.string().min(1).required(), @@ -17,40 +14,25 @@ export const EIP712PropertyFieldValidator = Joi.object<{ export const EIP712DomainTypeValidator = Joi.array() .items( - Joi.object<{ - name: string; - type: string; - }>({ + Joi.object({ name: Joi.string().valid("name").required(), type: Joi.string().valid("string").required(), }), - Joi.object<{ - name: string; - type: string; - }>({ + Joi.object({ name: Joi.string().valid("version").required(), type: Joi.string().valid("string").required(), }), - Joi.object<{ - name: string; - type: string; - }>({ + Joi.object({ name: Joi.string().valid("chainId").required(), type: Joi.string().valid("uint256").required(), }), - Joi.object<{ - name: string; - type: string; - }>({ + Joi.object({ name: Joi.string().valid("verifyingContract").required(), // From https://eips.ethereum.org/EIPS/eip-712, (string) may be non-standard? // But, ethermint set this type as string. type: Joi.string().valid("address", "string").required(), }), - Joi.object<{ - name: string; - type: string; - }>({ + Joi.object({ name: Joi.string().valid("salt").required(), // From https://eips.ethereum.org/EIPS/eip-712, (string) may be non-standard? // But, ethermint set this type as string. @@ -77,7 +59,7 @@ export const EIP712DomainTypeValidator = Joi.array() }); export const EIP712MessageValidator = Joi.object<{ - types: Record; + types: TypedData.TypedData; primaryType: string; domain: Record; message: Record; @@ -93,17 +75,17 @@ export const EIP712MessageValidator = Joi.object<{ }); export const domainHash = (message: { - types: Record; - domain: Record; + types: TypedData.TypedData; + domain: Record; }): string => TypedData.hashDomain({ domain: message.domain, types: { EIP712Domain: message.types["EIP712Domain"] }, - } as any); + }); // Seems that there is no way to set primary type and the first type becomes primary type. export const messageHash = (message: { - types: Record; + types: TypedData.TypedData; primaryType: string; message: Record; }): string => { @@ -121,5 +103,5 @@ export const messageHash = (message: { data: message.message, primaryType: message.primaryType, types, - } as any); + }); }; From 415dce66143c6ad9bb858c15f6e8b46aad8d8d43 Mon Sep 17 00:00:00 2001 From: Thunnini Date: Mon, 23 Mar 2026 18:52:34 +0900 Subject: [PATCH 09/64] fix: use chainId 0 as fallback to match ethers serialize behavior ethers used `chainId || 0` when chainId was missing. The ox migration incorrectly defaulted to 1 (mainnet), which could silently produce wrong signatures if chainId were ever omitted. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/stores-eth/src/serialize.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stores-eth/src/serialize.ts b/packages/stores-eth/src/serialize.ts index 6813141bdd..cad7de6c4a 100644 --- a/packages/stores-eth/src/serialize.ts +++ b/packages/stores-eth/src/serialize.ts @@ -67,7 +67,7 @@ export function serializeEVMTransaction( if (isEIP1559) { const envelope: Parameters[0] = { - chainId: tx.chainId ?? 1, + chainId: tx.chainId ?? 0, nonce: toBigInt(tx.nonce), gas: toBigInt(tx.gasLimit), maxFeePerGas: toBigInt(tx.maxFeePerGas), From 9bf88b4cea3742748f2951c0662d4502b0e319e0 Mon Sep 17 00:00:00 2001 From: Thunnini Date: Mon, 23 Mar 2026 19:21:28 +0900 Subject: [PATCH 10/64] build: upgrade ts-loader and fork-ts-checker-webpack-plugin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ts-loader: 9.4.2 → 9.5.4 fork-ts-checker-webpack-plugin: 7.2.13 → 9.1.0 The old versions were not tested with TypeScript 5.9.3 and caused the extension to break on watch-mode rebuilds during development. Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/extension/package.json | 4 +- yarn.lock | 77 +++++++++++++++++++++++++++---------- 2 files changed, 59 insertions(+), 22 deletions(-) diff --git a/apps/extension/package.json b/apps/extension/package.json index f978dbbd24..f894b5c5e5 100644 --- a/apps/extension/package.json +++ b/apps/extension/package.json @@ -140,13 +140,13 @@ "copy-webpack-plugin": "^11.0.0", "css-loader": "^6.7.4", "deepmerge": "^4.2.2", - "fork-ts-checker-webpack-plugin": "^7.2.13", + "fork-ts-checker-webpack-plugin": "^9.1.0", "html-webpack-plugin": "^5.5.0", "identity-obj-proxy": "^3.0.0", "serialize-javascript": ">=3.1.0", "storybook": "^7.6.18", "style-loader": "^3.3.3", - "ts-loader": "^9.4.2", + "ts-loader": "^9.5.4", "webpack": "^5.74.0", "webpack-bundle-analyzer": "^4.5.0", "webpack-cli": "^5.0.1" diff --git a/yarn.lock b/yarn.lock index 968a6c515d..5f5bf96b9d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5918,7 +5918,7 @@ __metadata: css-loader: ^6.7.4 deepmerge: ^4.2.2 delay: ^4.4.0 - fork-ts-checker-webpack-plugin: ^7.2.13 + fork-ts-checker-webpack-plugin: ^9.1.0 html-webpack-plugin: ^5.5.0 https-browserify: ^1.0.0 identity-obj-proxy: ^3.0.0 @@ -5957,7 +5957,7 @@ __metadata: styled-components: ^5.3.5 styled-normalize: ^8.0.7 swiper: ^8.4.4 - ts-loader: ^9.4.2 + ts-loader: ^9.5.4 utility-types: ^3.10.0 uuid: ^11.1.0 webextension-polyfill: ^0.12.0 @@ -14525,6 +14525,15 @@ __metadata: languageName: node linkType: hard +"chokidar@npm:^4.0.1": + version: 4.0.3 + resolution: "chokidar@npm:4.0.3" + dependencies: + readdirp: ^4.0.1 + checksum: a8765e452bbafd04f3f2fad79f04222dd65f43161488bb6014a41099e6ca18d166af613d59a90771908c1c823efa3f46ba36b86ac50b701c20c1b9908c5fe36e + languageName: node + linkType: hard + "chownr@npm:^1.1.1": version: 1.1.4 resolution: "chownr@npm:1.1.4" @@ -15451,6 +15460,23 @@ __metadata: languageName: node linkType: hard +"cosmiconfig@npm:^8.2.0": + version: 8.3.6 + resolution: "cosmiconfig@npm:8.3.6" + dependencies: + import-fresh: ^3.3.0 + js-yaml: ^4.1.0 + parse-json: ^5.2.0 + path-type: ^4.0.0 + peerDependencies: + typescript: ">=4.9.5" + peerDependenciesMeta: + typescript: + optional: true + checksum: dc339ebea427898c9e03bf01b56ba7afbac07fc7d2a2d5a15d6e9c14de98275a9565da949375aee1809591c152c0a3877bb86dbeaf74d5bd5aaa79955ad9e7a0 + languageName: node + linkType: hard + "cosmjs-test@workspace:packages/cosmjs-test": version: 0.0.0-use.local resolution: "cosmjs-test@workspace:packages/cosmjs-test" @@ -18604,9 +18630,9 @@ __metadata: languageName: node linkType: hard -"fork-ts-checker-webpack-plugin@npm:^7.2.13": - version: 7.2.13 - resolution: "fork-ts-checker-webpack-plugin@npm:7.2.13" +"fork-ts-checker-webpack-plugin@npm:^8.0.0": + version: 8.0.0 + resolution: "fork-ts-checker-webpack-plugin@npm:8.0.0" dependencies: "@babel/code-frame": ^7.16.7 chalk: ^4.1.2 @@ -18622,23 +18648,19 @@ __metadata: tapable: ^2.2.1 peerDependencies: typescript: ">3.6.0" - vue-template-compiler: "*" webpack: ^5.11.0 - peerDependenciesMeta: - vue-template-compiler: - optional: true - checksum: 3d4694c6fee4b8b2f213d0d10a3f40da770ca0ed3aa2a3dc8d1e701ad1ecaed3a1507f77a1b0cea6ef80539b04d8e5f5f02560e688d310bcb9e8c81f684d2950 + checksum: aad4cbc5b802e6281a2700a379837697c93ad95288468f9595219d91d9c26674736d37852bb4c4341e9122f26181e9e05fc1a362e8d029fdd88e99de7816037b languageName: node linkType: hard -"fork-ts-checker-webpack-plugin@npm:^8.0.0": - version: 8.0.0 - resolution: "fork-ts-checker-webpack-plugin@npm:8.0.0" +"fork-ts-checker-webpack-plugin@npm:^9.1.0": + version: 9.1.0 + resolution: "fork-ts-checker-webpack-plugin@npm:9.1.0" dependencies: "@babel/code-frame": ^7.16.7 chalk: ^4.1.2 - chokidar: ^3.5.3 - cosmiconfig: ^7.0.1 + chokidar: ^4.0.1 + cosmiconfig: ^8.2.0 deepmerge: ^4.2.2 fs-extra: ^10.0.0 memfs: ^3.4.1 @@ -18650,7 +18672,7 @@ __metadata: peerDependencies: typescript: ">3.6.0" webpack: ^5.11.0 - checksum: aad4cbc5b802e6281a2700a379837697c93ad95288468f9595219d91d9c26674736d37852bb4c4341e9122f26181e9e05fc1a362e8d029fdd88e99de7816037b + checksum: 8c3e011cce7ad2b45adce8aea8226db7d79e6ea0900030e86d24ea689152127a7a420f16ac09b9634e461fcd317e32b922a1f3106fe5107b1e8b74b83ed8ae82 languageName: node linkType: hard @@ -28600,6 +28622,13 @@ __metadata: languageName: node linkType: hard +"readdirp@npm:^4.0.1": + version: 4.1.2 + resolution: "readdirp@npm:4.1.2" + checksum: 3242ee125422cb7c0e12d51452e993f507e6ed3d8c490bc8bf3366c5cdd09167562224e429b13e9cb2b98d4b8b2b11dc100d3c73883aa92d657ade5a21ded004 + languageName: node + linkType: hard + "readdirp@npm:~3.6.0": version: 3.6.0 resolution: "readdirp@npm:3.6.0" @@ -31122,6 +31151,13 @@ __metadata: languageName: node linkType: hard +"source-map@npm:^0.7.4": + version: 0.7.6 + resolution: "source-map@npm:0.7.6" + checksum: 932f4a2390aa7100e91357d88cc272de984ad29139ac09eedfde8cc78d46da35f389065d0c5343c5d71d054a6ebd4939a8c0f2c98d5df64fe97bb8a730596c2d + languageName: node + linkType: hard + "space-separated-tokens@npm:^1.0.0": version: 1.1.5 resolution: "space-separated-tokens@npm:1.1.5" @@ -32576,18 +32612,19 @@ __metadata: languageName: node linkType: hard -"ts-loader@npm:^9.4.2": - version: 9.4.2 - resolution: "ts-loader@npm:9.4.2" +"ts-loader@npm:^9.5.4": + version: 9.5.4 + resolution: "ts-loader@npm:9.5.4" dependencies: chalk: ^4.1.0 enhanced-resolve: ^5.0.0 micromatch: ^4.0.0 semver: ^7.3.4 + source-map: ^0.7.4 peerDependencies: typescript: "*" webpack: ^5.0.0 - checksum: 6f306ee4c615c2a159fb177561e3fb86ca2cbd6c641e710d408a64b4978e1ff3f2c9733df07bff27d3f82efbfa7c287523d4306049510c7485ac2669a9c37eb0 + checksum: ee8a6883d0f8c0db0ea254fe4e9247ab0f6747cbbbee987af80e69822b840bdc2b67cee267ed3a93af803aeed47790d64dbaf07c001322b9091671f51cbf12b2 languageName: node linkType: hard From 35ab0027448adfdbe50d767ed0116e1561089763 Mon Sep 17 00:00:00 2001 From: Thunnini Date: Mon, 23 Mar 2026 19:43:59 +0900 Subject: [PATCH 11/64] fix: use Address.checksum instead of Address.from for EIP-55 ox Address.from() returns lowercase addresses as-is without applying EIP-55 checksum, unlike ethers getAddress(). Address.checksum() matches the original ethers behavior of always returning checksummed addresses. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/cosmos/src/bech32/index.ts | 2 +- packages/hooks-evm/src/tx/name-service-ens.ts | 2 +- packages/hooks/src/tx/name-service-ens.ts | 2 +- packages/stores-eth/src/account/base.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/cosmos/src/bech32/index.ts b/packages/cosmos/src/bech32/index.ts index 8f57ffc1a7..925dfe72b7 100644 --- a/packages/cosmos/src/bech32/index.ts +++ b/packages/cosmos/src/bech32/index.ts @@ -92,7 +92,7 @@ export class Bech32Address { } if (mixedCaseChecksum) { - return Address.from(`0x${hex}`); + return Address.checksum(`0x${hex}`); } else { return "0x" + hex; } diff --git a/packages/hooks-evm/src/tx/name-service-ens.ts b/packages/hooks-evm/src/tx/name-service-ens.ts index 7199035ccf..b56580779b 100644 --- a/packages/hooks-evm/src/tx/name-service-ens.ts +++ b/packages/hooks-evm/src/tx/name-service-ens.ts @@ -38,7 +38,7 @@ async function resolveENS( return undefined; } - return Address.from(addrBytes as `0x${string}`); + return Address.checksum(addrBytes as `0x${string}`); } async function ethCall( diff --git a/packages/hooks/src/tx/name-service-ens.ts b/packages/hooks/src/tx/name-service-ens.ts index 11bf73181a..6286976f47 100644 --- a/packages/hooks/src/tx/name-service-ens.ts +++ b/packages/hooks/src/tx/name-service-ens.ts @@ -38,7 +38,7 @@ async function resolveENS( return undefined; } - return Address.from(addrBytes as `0x${string}`); + return Address.checksum(addrBytes as `0x${string}`); } async function ethCall( diff --git a/packages/stores-eth/src/account/base.ts b/packages/stores-eth/src/account/base.ts index fed414c509..740f21953d 100644 --- a/packages/stores-eth/src/account/base.ts +++ b/packages/stores-eth/src/account/base.ts @@ -662,7 +662,7 @@ export class EthereumAccountBase { return false; } - const checksumHexAddress = Address.from(hexAddress.toLowerCase()); + const checksumHexAddress = Address.checksum(hexAddress.toLowerCase()); if (isChecksumAddress && checksumHexAddress !== hexAddress) { return false; } From 9fdc93e3d05fca9353b177abc6285eae6de01267 Mon Sep 17 00:00:00 2001 From: Thunnini Date: Mon, 23 Mar 2026 20:26:48 +0900 Subject: [PATCH 12/64] fix: clean up stale comments, typo, and add decode warning - Remove stale comment about primaryType inference (eip712.ts) - Rewrite ethersjs reference to be library-agnostic (service.ts) - Remove "ethers-compatible" from EVMSignatureLike JSDoc (serialize.ts) - Add console.warn when ERC20 selector matches but decoding fails - Fix "ENS or is not set" typo (name-service-ens.ts) Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/pages/sign/components/eth-tx/render/send-token.tsx | 5 ++++- packages/background/src/keyring-cosmos/eip712.ts | 1 - packages/background/src/keyring-ethereum/service.ts | 4 ++-- packages/hooks/src/tx/name-service-ens.ts | 2 +- packages/stores-eth/src/serialize.ts | 4 ++-- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/apps/extension/src/pages/sign/components/eth-tx/render/send-token.tsx b/apps/extension/src/pages/sign/components/eth-tx/render/send-token.tsx index f3a3100fe4..c5d25b22a2 100644 --- a/apps/extension/src/pages/sign/components/eth-tx/render/send-token.tsx +++ b/apps/extension/src/pages/sign/components/eth-tx/render/send-token.tsx @@ -79,7 +79,10 @@ export const EthSendTokenTx: IEthTxRenderer = { ), }; } catch (e) { - // Fallback to other renderers if unsingedTx.data can't be decoded with ERC20 transfer method. + console.warn( + "ERC20 transfer selector matched but data decoding failed:", + e + ); return undefined; } } else { diff --git a/packages/background/src/keyring-cosmos/eip712.ts b/packages/background/src/keyring-cosmos/eip712.ts index 28b8efc47e..818f186a12 100644 --- a/packages/background/src/keyring-cosmos/eip712.ts +++ b/packages/background/src/keyring-cosmos/eip712.ts @@ -83,7 +83,6 @@ export const domainHash = (message: { types: { EIP712Domain: message.types["EIP712Domain"] }, }); -// Seems that there is no way to set primary type and the first type becomes primary type. export const messageHash = (message: { types: TypedData.TypedData; primaryType: string; diff --git a/packages/background/src/keyring-ethereum/service.ts b/packages/background/src/keyring-ethereum/service.ts index 84f24ffc7d..26918da128 100644 --- a/packages/background/src/keyring-ethereum/service.ts +++ b/packages/background/src/keyring-ethereum/service.ts @@ -247,8 +247,8 @@ export class KeyRingEthereumService { const data = await EIP712MessageValidator.validateAsync( JSON.parse(Buffer.from(res.signingData).toString()) ); - // Since ethermint eip712 tx uses non-standard format, it cannot pass validation of ethersjs. - // Therefore, it should be handled at a slightly lower level. + // Since ethermint eip712 tx uses non-standard format, we construct + // the signing hash manually rather than using a typed data signing helper. const signature = await this.keyRingService.sign( chainId, vaultId, diff --git a/packages/hooks/src/tx/name-service-ens.ts b/packages/hooks/src/tx/name-service-ens.ts index 6286976f47..39cac4a127 100644 --- a/packages/hooks/src/tx/name-service-ens.ts +++ b/packages/hooks/src/tx/name-service-ens.ts @@ -208,7 +208,7 @@ export class ENSNameService implements NameService { const prevValue = this.value; try { if (!this._ens) { - throw new Error("ENS or is not set"); + throw new Error("ENS is not set"); } runInAction(() => { diff --git a/packages/stores-eth/src/serialize.ts b/packages/stores-eth/src/serialize.ts index cad7de6c4a..bf8c0092c7 100644 --- a/packages/stores-eth/src/serialize.ts +++ b/packages/stores-eth/src/serialize.ts @@ -14,8 +14,8 @@ function toBigInt(value: string | number | undefined): bigint | undefined { /** * Signature format used by the existing codebase. - * This is the ethers-compatible signature format (r, s as hex strings, v as number). - * Can also be raw bytes (Uint8Array/Buffer of 65 bytes: r[32] + s[32] + v[1]). + * Accepts either {r, s, v} (r, s as hex strings, v as number) + * or raw bytes (Uint8Array/Buffer of 65 bytes: r[32] + s[32] + v[1]). */ export type EVMSignatureLike = | { From 507771ac2386bdf2385b3d94779dfa6fd530e63d Mon Sep 17 00:00:00 2001 From: Thunnini Date: Mon, 23 Mar 2026 20:41:41 +0900 Subject: [PATCH 13/64] build: disable no-loss-of-precision in unit test files Large numeric literals in test assertions trigger the rule under TypeScript 5.9.3's stricter eslint integration. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/unit/.eslintrc.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/unit/.eslintrc.json b/packages/unit/.eslintrc.json index 2788a227ef..8f56043e04 100644 --- a/packages/unit/.eslintrc.json +++ b/packages/unit/.eslintrc.json @@ -6,5 +6,13 @@ "patterns": ["src/*", "@keplr-wallet/unit", "@keplr-wallet/unit/*"] } ] - } + }, + "overrides": [ + { + "files": ["**/*.spec.ts", "**/*.spec.js"], + "rules": { + "@typescript-eslint/no-loss-of-precision": "off" + } + } + ] } From e519873b7a07e12cbf0a4a639e4215e12d4f7685 Mon Sep 17 00:00:00 2001 From: Thunnini Date: Mon, 23 Mar 2026 20:43:23 +0900 Subject: [PATCH 14/64] =?UTF-8?q?build:=20upgrade=20eslint-plugin-mdx=202.?= =?UTF-8?q?0.5=20=E2=86=92=203.7.0,=20remove=20@1stg/remark-preset?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit eslint-mdx v2 failed to parse .mdx files because @1stg/remark-preset uses ESM top-level await which is incompatible with require(). v3 no longer depends on @1stg/remark-preset. Co-Authored-By: Claude Opus 4.6 (1M context) --- package.json | 8 +- yarn.lock | 2742 ++++++++++++++------------------------------------ 2 files changed, 745 insertions(+), 2005 deletions(-) diff --git a/package.json b/package.json index 8cb4fcd9c8..3430ca1cb5 100644 --- a/package.json +++ b/package.json @@ -51,16 +51,10 @@ "prettier --check" ] }, - "remarkConfig": { - "plugins": [ - "@1stg/remark-preset" - ] - }, "keywords": [], "author": "chainapsis", "license": "Apache-2.0", "devDependencies": { - "@1stg/remark-preset": "^2.0.0", "@octokit/core": "^3.5.1", "@types/filesystem": "^0.0.32", "@types/jest": "^29.4.0", @@ -75,7 +69,7 @@ "eslint": "^8.56.0", "eslint-config-prettier": "^8.6.0", "eslint-plugin-import": "^2.27.5", - "eslint-plugin-mdx": "^2.0.5", + "eslint-plugin-mdx": "^3.7.0", "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^4.6.0", diff --git a/yarn.lock b/yarn.lock index 5f5bf96b9d..8d8e5afb4a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,24 +5,6 @@ __metadata: version: 6 cacheKey: 8 -"@1stg/remark-preset@npm:^2.0.0": - version: 2.0.0 - resolution: "@1stg/remark-preset@npm:2.0.0" - dependencies: - remark-frontmatter: ^4.0.1 - remark-gfm: ^3.0.1 - remark-lint: ^9.1.1 - remark-lint-no-duplicate-headings: ^3.1.1 - remark-lint-no-duplicate-headings-in-section: ^3.1.1 - remark-preset-lint-consistent: ^5.1.1 - remark-preset-lint-markdown-style-guide: ^5.1.2 - remark-preset-lint-recommended: ^6.1.2 - remark-preset-prettier: ^2.0.1 - remark-validate-links: ^12.1.0 - checksum: 0de9d6dc5a197ab77ee7716506fe5227b57550be7ae4d9e173f783cd3424a48395a7eb6ec77465439ede2ea2a2b5f78cc0e6c51a5e3a068e2da958df2f574b80 - languageName: node - linkType: hard - "@adraffy/ens-normalize@npm:^1.11.0": version: 1.11.1 resolution: "@adraffy/ens-normalize@npm:1.11.1" @@ -175,7 +157,7 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.16.0, @babel/code-frame@npm:^7.16.7, @babel/code-frame@npm:^7.18.6": +"@babel/code-frame@npm:^7.16.7, @babel/code-frame@npm:^7.18.6": version: 7.18.6 resolution: "@babel/code-frame@npm:7.18.6" dependencies: @@ -184,6 +166,17 @@ __metadata: languageName: node linkType: hard +"@babel/code-frame@npm:^7.21.4": + version: 7.29.0 + resolution: "@babel/code-frame@npm:7.29.0" + dependencies: + "@babel/helper-validator-identifier": ^7.28.5 + js-tokens: ^4.0.0 + picocolors: ^1.1.1 + checksum: 39f5b303757e4d63bbff8133e251094cd4f952b46e3fa9febc7368d907583911d6a1eded6090876dc1feeff5cf6e134fb19b706f8d58d26c5402cd50e5e1aeb2 + languageName: node + linkType: hard + "@babel/code-frame@npm:^7.22.5": version: 7.22.10 resolution: "@babel/code-frame@npm:7.22.10" @@ -1354,6 +1347,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-identifier@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/helper-validator-identifier@npm:7.28.5" + checksum: 5a251a6848e9712aea0338f659a1a3bd334d26219d5511164544ca8ec20774f098c3a6661e9da65a0d085c745c00bb62c8fada38a62f08fa1f8053bc0aeb57e4 + languageName: node + linkType: hard + "@babel/helper-validator-option@npm:^7.12.17": version: 7.12.17 resolution: "@babel/helper-validator-option@npm:7.12.17" @@ -7323,18 +7323,19 @@ __metadata: languageName: node linkType: hard -"@npmcli/config@npm:^6.0.0": - version: 6.1.3 - resolution: "@npmcli/config@npm:6.1.3" +"@npmcli/config@npm:^8.0.0": + version: 8.3.4 + resolution: "@npmcli/config@npm:8.3.4" dependencies: "@npmcli/map-workspaces": ^3.0.2 - ini: ^3.0.0 - nopt: ^7.0.0 - proc-log: ^3.0.0 - read-package-json-fast: ^3.0.2 + "@npmcli/package-json": ^5.1.1 + ci-info: ^4.0.0 + ini: ^4.1.2 + nopt: ^7.2.1 + proc-log: ^4.2.0 semver: ^7.3.5 - walk-up-path: ^1.0.0 - checksum: 3ca1d1301354af50ecbfbfd1d5a34459b0b68f6364758a188cc65f04dd517e260d6c21f631fcc3840e8c25826ba5756e114166881ec4227d540919280e6cb0f4 + walk-up-path: ^3.0.1 + checksum: 9a8cc40fa577b37f85ef82f7cfe6a3b65be893cae0e00997f5f2a51ca89928ce56b3cf3cffc6cf8c74a615dd0102a230718ff937138caa3f4737dd34160f90b7 languageName: node linkType: hard @@ -7366,6 +7367,23 @@ __metadata: languageName: node linkType: hard +"@npmcli/git@npm:^5.0.0": + version: 5.0.8 + resolution: "@npmcli/git@npm:5.0.8" + dependencies: + "@npmcli/promise-spawn": ^7.0.0 + ini: ^4.1.3 + lru-cache: ^10.0.1 + npm-pick-manifest: ^9.0.0 + proc-log: ^4.0.0 + promise-inflight: ^1.0.1 + promise-retry: ^2.0.1 + semver: ^7.3.5 + which: ^4.0.0 + checksum: 8c1733b591e428719c60fceaca74b3355967f6ddbce851c0d163a3c2e8123aaa717361b8226f8f8e606685f14721ea97d8f99c4b5831bc9251007bb1a20663cd + languageName: node + linkType: hard + "@npmcli/git@npm:^6.0.0": version: 6.0.3 resolution: "@npmcli/git@npm:6.0.3" @@ -7519,6 +7537,21 @@ __metadata: languageName: node linkType: hard +"@npmcli/package-json@npm:^5.1.1": + version: 5.2.1 + resolution: "@npmcli/package-json@npm:5.2.1" + dependencies: + "@npmcli/git": ^5.0.0 + glob: ^10.2.2 + hosted-git-info: ^7.0.0 + json-parse-even-better-errors: ^3.0.0 + normalize-package-data: ^6.0.0 + proc-log: ^4.0.0 + semver: ^7.5.3 + checksum: f9f76428fb3b3350fe840f1fa49854d18ff1ecb82b426c9cf53a62a37389c357a89d64a07497f50b7fbf1c742f5a0cd349d8efdddef0bb6982497f8356c1f98a + languageName: node + linkType: hard + "@npmcli/package-json@npm:^7.0.0": version: 7.0.4 resolution: "@npmcli/package-json@npm:7.0.4" @@ -7534,6 +7567,15 @@ __metadata: languageName: node linkType: hard +"@npmcli/promise-spawn@npm:^7.0.0": + version: 7.0.2 + resolution: "@npmcli/promise-spawn@npm:7.0.2" + dependencies: + which: ^4.0.0 + checksum: 728256506ecbafb53064036e28c2815b9a9e9190ba7a48eec77b011a9f8a899515a6d96760dbde960bc1d3e5b828fd0b0b7fe3b512efaf049d299bacbd732fda + languageName: node + linkType: hard + "@npmcli/promise-spawn@npm:^8.0.0": version: 8.0.3 resolution: "@npmcli/promise-spawn@npm:8.0.3" @@ -8140,17 +8182,10 @@ __metadata: languageName: node linkType: hard -"@pkgr/utils@npm:^2.3.1": - version: 2.3.1 - resolution: "@pkgr/utils@npm:2.3.1" - dependencies: - cross-spawn: ^7.0.3 - is-glob: ^4.0.3 - open: ^8.4.0 - picocolors: ^1.0.0 - tiny-glob: ^0.2.9 - tslib: ^2.4.0 - checksum: 118a1971120253740121a1db0a6658c21195b7da962acf9c124b507a3df707cfc97b0b84a16edcbd4352853b182e8337da9fc6e8e3d06c60d75ae4fb42321c75 +"@pkgr/core@npm:^0.2.9": + version: 0.2.9 + resolution: "@pkgr/core@npm:0.2.9" + checksum: bb2fb86977d63f836f8f5b09015d74e6af6488f7a411dcd2bfdca79d76b5a681a9112f41c45bdf88a9069f049718efc6f3900d7f1de66a2ec966068308ae517f languageName: node linkType: hard @@ -10470,15 +10505,6 @@ __metadata: languageName: node linkType: hard -"@types/acorn@npm:^4.0.0": - version: 4.0.6 - resolution: "@types/acorn@npm:4.0.6" - dependencies: - "@types/estree": "*" - checksum: 60e1fd28af18d6cb54a93a7231c7c18774a9a8739c9b179e9e8750dca631e10cbef2d82b02830ea3f557b1d121e6406441e9e1250bd492dc81d4b3456e76e4d4 - languageName: node - linkType: hard - "@types/aes-js@npm:^3.1.0": version: 3.1.1 resolution: "@types/aes-js@npm:3.1.1" @@ -10851,12 +10877,12 @@ __metadata: languageName: node linkType: hard -"@types/hast@npm:^2.0.0": - version: 2.3.4 - resolution: "@types/hast@npm:2.3.4" +"@types/hast@npm:^3.0.0": + version: 3.0.4 + resolution: "@types/hast@npm:3.0.4" dependencies: "@types/unist": "*" - checksum: fff47998f4c11e21a7454b58673f70478740ecdafd95aaf50b70a3daa7da9cdc57315545bf9c039613732c40b7b0e9e49d11d03fe9a4304721cdc3b29a88141e + checksum: 7a973e8d16fcdf3936090fa2280f408fb2b6a4f13b42edeb5fbd614efe042b82eac68e298e556d50f6b4ad585a3a93c353e9c826feccdc77af59de8dd400d044 languageName: node linkType: hard @@ -10995,12 +11021,12 @@ __metadata: languageName: node linkType: hard -"@types/mdast@npm:^3.0.0": - version: 3.0.10 - resolution: "@types/mdast@npm:3.0.10" +"@types/mdast@npm:^4.0.0": + version: 4.0.4 + resolution: "@types/mdast@npm:4.0.4" dependencies: "@types/unist": "*" - checksum: 3f587bfc0a9a2403ecadc220e61031b01734fedaf82e27eb4d5ba039c0eb54db8c85681ccc070ab4df3f7ec711b736a82b990e69caa14c74bf7ac0ccf2ac7313 + checksum: 20c4e9574cc409db662a35cba52b068b91eb696b3049e94321219d47d34c8ccc99a142be5c76c80a538b612457b03586bc2f6b727a3e9e7530f4c8568f6282ee languageName: node linkType: hard @@ -11335,13 +11361,20 @@ __metadata: languageName: node linkType: hard -"@types/unist@npm:*, @types/unist@npm:^2.0.0, @types/unist@npm:^2.0.2": +"@types/unist@npm:*, @types/unist@npm:^2.0.0": version: 2.0.6 resolution: "@types/unist@npm:2.0.6" checksum: 25cb860ff10dde48b54622d58b23e66214211a61c84c0f15f88d38b61aa1b53d4d46e42b557924a93178c501c166aa37e28d7f6d994aba13d24685326272d5db languageName: node linkType: hard +"@types/unist@npm:^3.0.0": + version: 3.0.3 + resolution: "@types/unist@npm:3.0.3" + checksum: 96e6453da9e075aaef1dc22482463898198acdc1eeb99b465e65e34303e2ec1e3b1ed4469a9118275ec284dc98019f63c3f5d49422f0e4ac707e5ab90fb3b71a + languageName: node + linkType: hard + "@types/uuid@npm:^9.0.1": version: 9.0.8 resolution: "@types/uuid@npm:9.0.8" @@ -12289,7 +12322,7 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.0.0, acorn@npm:^8.8.0": +"acorn@npm:^8.0.0": version: 8.8.2 resolution: "acorn@npm:8.8.2" bin: @@ -12316,21 +12349,21 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.8.2": - version: 8.10.0 - resolution: "acorn@npm:8.10.0" +"acorn@npm:^8.15.0, acorn@npm:^8.9.0": + version: 8.16.0 + resolution: "acorn@npm:8.16.0" bin: acorn: bin/acorn - checksum: 538ba38af0cc9e5ef983aee196c4b8b4d87c0c94532334fa7e065b2c8a1f85863467bb774231aae91613fcda5e68740c15d97b1967ae3394d20faddddd8af61d + checksum: bbfa466cd0dbd18b4460a85e9d0fc2f35db999380892403c573261beda91f23836db2aa71fd3ae65e94424ad14ff8e2b7bd37c7a2624278fd89137cd6e448c41 languageName: node linkType: hard -"acorn@npm:^8.9.0": - version: 8.16.0 - resolution: "acorn@npm:8.16.0" +"acorn@npm:^8.8.2": + version: 8.10.0 + resolution: "acorn@npm:8.10.0" bin: acorn: bin/acorn - checksum: bbfa466cd0dbd18b4460a85e9d0fc2f35db999380892403c573261beda91f23836db2aa71fd3ae65e94424ad14ff8e2b7bd37c7a2624278fd89137cd6e448c41 + checksum: 538ba38af0cc9e5ef983aee196c4b8b4d87c0c94532334fa7e065b2c8a1f85863467bb774231aae91613fcda5e68740c15d97b1967ae3394d20faddddd8af61d languageName: node linkType: hard @@ -14415,13 +14448,6 @@ __metadata: languageName: node linkType: hard -"character-entities-legacy@npm:^1.0.0": - version: 1.1.4 - resolution: "character-entities-legacy@npm:1.1.4" - checksum: fe03a82c154414da3a0c8ab3188e4237ec68006cbcd681cf23c7cfb9502a0e76cd30ab69a2e50857ca10d984d57de3b307680fff5328ccd427f400e559c3a811 - languageName: node - linkType: hard - "character-entities-legacy@npm:^3.0.0": version: 3.0.0 resolution: "character-entities-legacy@npm:3.0.0" @@ -14429,13 +14455,6 @@ __metadata: languageName: node linkType: hard -"character-entities@npm:^1.0.0": - version: 1.2.4 - resolution: "character-entities@npm:1.2.4" - checksum: e1545716571ead57beac008433c1ff69517cd8ca5b336889321c5b8ff4a99c29b65589a701e9c086cda8a5e346a67295e2684f6c7ea96819fe85cbf49bf8686d - languageName: node - linkType: hard - "character-entities@npm:^2.0.0": version: 2.0.2 resolution: "character-entities@npm:2.0.2" @@ -14443,13 +14462,6 @@ __metadata: languageName: node linkType: hard -"character-reference-invalid@npm:^1.0.0": - version: 1.1.4 - resolution: "character-reference-invalid@npm:1.1.4" - checksum: 20274574c70e05e2f81135f3b93285536bc8ff70f37f0809b0d17791a832838f1e49938382899ed4cb444e5bbd4314ca1415231344ba29f4222ce2ccf24fea0b - languageName: node - linkType: hard - "character-reference-invalid@npm:^2.0.0": version: 2.0.1 resolution: "character-reference-invalid@npm:2.0.1" @@ -16418,6 +16430,15 @@ __metadata: languageName: node linkType: hard +"devlop@npm:^1.0.0, devlop@npm:^1.1.0": + version: 1.1.0 + resolution: "devlop@npm:1.1.0" + dependencies: + dequal: ^2.0.0 + checksum: d2ff650bac0bb6ef08c48f3ba98640bb5fec5cce81e9957eb620408d1bab1204d382a45b785c6b3314dc867bb0684936b84c6867820da6db97cbb5d3c15dd185 + languageName: node + linkType: hard + "diff-sequences@npm:^29.4.3": version: 29.4.3 resolution: "diff-sequences@npm:29.4.3" @@ -16860,6 +16881,13 @@ __metadata: languageName: node linkType: hard +"emoji-regex@npm:^10.2.1": + version: 10.6.0 + resolution: "emoji-regex@npm:10.6.0" + checksum: 8785f6a7ec4559c931bd6640f748fe23791f5af4c743b131d458c5551b4aa7da2a9cd882518723cb3859e8b0b59b0cc08f2ce0f8e65c61a026eed71c2dc407d5 + languageName: node + linkType: hard + "emoji-regex@npm:^8.0.0": version: 8.0.0 resolution: "emoji-regex@npm:8.0.0" @@ -17363,13 +17391,6 @@ __metadata: languageName: node linkType: hard -"escape-string-regexp@npm:^5.0.0": - version: 5.0.0 - resolution: "escape-string-regexp@npm:5.0.0" - checksum: 20daabe197f3cb198ec28546deebcf24b3dbb1a5a269184381b3116d12f0532e06007f4bc8da25669d6a7f8efb68db0758df4cd981f57bc5b57f521a3e12c59e - languageName: node - linkType: hard - "escodegen@npm:^1.11.0, escodegen@npm:^1.11.1": version: 1.14.3 resolution: "escodegen@npm:1.14.3" @@ -17448,27 +17469,29 @@ __metadata: languageName: node linkType: hard -"eslint-mdx@npm:^2.0.5": - version: 2.0.5 - resolution: "eslint-mdx@npm:2.0.5" +"eslint-mdx@npm:^3.7.0": + version: 3.7.0 + resolution: "eslint-mdx@npm:3.7.0" dependencies: - acorn: ^8.8.0 + acorn: ^8.15.0 acorn-jsx: ^5.3.2 - cosmiconfig: ^7.0.1 - espree: ^9.4.0 - estree-util-visit: ^1.2.0 - remark-mdx: ^2.1.3 - remark-parse: ^10.0.1 - remark-stringify: ^10.0.2 - synckit: ^0.8.4 - tslib: ^2.4.0 - unified: ^10.1.2 - unist-util-visit: ^4.1.1 - uvu: ^0.5.6 - vfile: ^5.3.4 + espree: ^9.6.1 || ^10.4.0 + estree-util-visit: ^2.0.0 + remark-mdx: ^3.1.0 + remark-parse: ^11.0.0 + remark-stringify: ^11.0.0 + synckit: ^0.11.8 + unified: ^11.0.5 + unified-engine: ^11.2.2 + unist-util-visit: ^5.0.0 + vfile: ^6.0.3 peerDependencies: eslint: ">=8.0.0" - checksum: f03009d2c19440e5687584d895ba9e0ababd501531ad539838a5b3d81b5aec2632321fa2cd748fe1041f1ce1d0955581b4fabe4cd7d0ff9a4b2582b7f5871de6 + remark-lint-file-extension: "*" + peerDependenciesMeta: + remark-lint-file-extension: + optional: true + checksum: 1ee345ca0cbc9ef3abeae01890565ea290aedc80757632bb2b378fd9888bc4eb7c800cadea792a42394152fba6dd548eb2b60126e3c6ff6f4d37f16459fb51c1 languageName: node linkType: hard @@ -17509,32 +17532,23 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-markdown@npm:^3.0.0": - version: 3.0.0 - resolution: "eslint-plugin-markdown@npm:3.0.0" - dependencies: - mdast-util-from-markdown: ^0.8.5 - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - checksum: ea9e8613cffcf7decbc2de0c900a83553ccdccfb6d90187e5d461a457a403d2634585a8c165cc4adf52c86f3b910161f33b1f24a46f296c4a577d2547780c997 - languageName: node - linkType: hard - -"eslint-plugin-mdx@npm:^2.0.5": - version: 2.0.5 - resolution: "eslint-plugin-mdx@npm:2.0.5" - dependencies: - eslint-mdx: ^2.0.5 - eslint-plugin-markdown: ^3.0.0 - remark-mdx: ^2.1.3 - remark-parse: ^10.0.1 - remark-stringify: ^10.0.2 - tslib: ^2.4.0 - unified: ^10.1.2 - vfile: ^5.3.4 +"eslint-plugin-mdx@npm:^3.7.0": + version: 3.7.0 + resolution: "eslint-plugin-mdx@npm:3.7.0" + dependencies: + eslint-mdx: ^3.7.0 + mdast-util-from-markdown: ^2.0.2 + mdast-util-mdx: ^3.0.0 + micromark-extension-mdxjs: ^3.0.0 + remark-mdx: ^3.1.0 + remark-parse: ^11.0.0 + remark-stringify: ^11.0.0 + synckit: ^0.11.8 + unified: ^11.0.5 + vfile: ^6.0.3 peerDependencies: eslint: ">=8.0.0" - checksum: dee06fbd3a3b1383b5cabdfeb006c9ce5ecc7c53ee8ef313757d1432db6a2e0599d80bde1125d83ec3c9e7d50b05be98ab620a43332b640149fd51230248971e + checksum: ea7127544a8b28482eda0bae917378e8690193153df50f0c2c538c0bd605d9e403e6b4f72caf5cbc70cece48f8a65884a25ef6951cb63d500011786b99d5e2d2 languageName: node linkType: hard @@ -17660,6 +17674,13 @@ __metadata: languageName: node linkType: hard +"eslint-visitor-keys@npm:^4.2.1": + version: 4.2.1 + resolution: "eslint-visitor-keys@npm:4.2.1" + checksum: 3a77e3f99a49109f6fb2c5b7784bc78f9743b834d238cdba4d66c602c6b52f19ed7bcd0a5c5dbbeae3a8689fd785e76c001799f53d2228b278282cf9f699fff5 + languageName: node + linkType: hard + "eslint@npm:^8.56.0": version: 8.57.1 resolution: "eslint@npm:8.57.1" @@ -17708,17 +17729,6 @@ __metadata: languageName: node linkType: hard -"espree@npm:^9.4.0": - version: 9.4.1 - resolution: "espree@npm:9.4.1" - dependencies: - acorn: ^8.8.0 - acorn-jsx: ^5.3.2 - eslint-visitor-keys: ^3.3.0 - checksum: 4d266b0cf81c7dfe69e542c7df0f246e78d29f5b04dda36e514eb4c7af117ee6cfbd3280e560571ed82ff6c9c3f0003c05b82583fc7a94006db7497c4fe4270e - languageName: node - linkType: hard - "espree@npm:^9.6.0, espree@npm:^9.6.1": version: 9.6.1 resolution: "espree@npm:9.6.1" @@ -17730,6 +17740,17 @@ __metadata: languageName: node linkType: hard +"espree@npm:^9.6.1 || ^10.4.0": + version: 10.4.0 + resolution: "espree@npm:10.4.0" + dependencies: + acorn: ^8.15.0 + acorn-jsx: ^5.3.2 + eslint-visitor-keys: ^4.2.1 + checksum: 5f9d0d7c81c1bca4bfd29a55270067ff9d575adb8c729a5d7f779c2c7b910bfc68ccf8ec19b29844b707440fc159a83868f22c8e87bbf7cbcb225ed067df6c85 + languageName: node + linkType: hard + "esprima@npm:^3.1.3": version: 3.1.3 resolution: "esprima@npm:3.1.3" @@ -17798,20 +17819,20 @@ __metadata: languageName: node linkType: hard -"estree-util-is-identifier-name@npm:^2.0.0": - version: 2.1.0 - resolution: "estree-util-is-identifier-name@npm:2.1.0" - checksum: cab317a071fafb99cf83b57df7924bccd2e6ab4e252688739e49f00b16cefd168e279c171442b0557c80a1c80ffaa927d670dadea65bb3c9b151efb8e772e89d +"estree-util-is-identifier-name@npm:^3.0.0": + version: 3.0.0 + resolution: "estree-util-is-identifier-name@npm:3.0.0" + checksum: ea3909f0188ea164af0aadeca87c087e3e5da78d76da5ae9c7954ff1340ea3e4679c4653bbf4299ffb70caa9b322218cc1128db2541f3d2976eb9704f9857787 languageName: node linkType: hard -"estree-util-visit@npm:^1.0.0, estree-util-visit@npm:^1.2.0": - version: 1.2.1 - resolution: "estree-util-visit@npm:1.2.1" +"estree-util-visit@npm:^2.0.0": + version: 2.0.0 + resolution: "estree-util-visit@npm:2.0.0" dependencies: "@types/estree-jsx": ^1.0.0 - "@types/unist": ^2.0.0 - checksum: 6feea4fdc43b0ba0f79faf1d57cf32373007e146d4810c7c09c13f5a9c1b8600c1ac57a8d949967cedd2a9a91dddd246e19b59bacfc01e417168b4ebf220f691 + "@types/unist": ^3.0.0 + checksum: 6444b38f224322945a6d19ea81a8828a0eec64aefb2bf1ea791fe20df496f7b7c543408d637df899e6a8e318b638f66226f16378a33c4c2b192ba5c3f891121f languageName: node linkType: hard @@ -18292,15 +18313,6 @@ __metadata: languageName: node linkType: hard -"fault@npm:^2.0.0": - version: 2.0.1 - resolution: "fault@npm:2.0.1" - dependencies: - format: ^0.2.0 - checksum: c9b30f47d95769177130a9409976a899ed31eb598450fbad5b0d39f2f5f56d5f4a9ff9257e0bee8407cb0fc3ce37165657888c6aa6d78472e403893104329b72 - languageName: node - linkType: hard - "fb-watchman@npm:^2.0.0": version: 2.0.1 resolution: "fb-watchman@npm:2.0.1" @@ -18709,13 +18721,6 @@ __metadata: languageName: node linkType: hard -"format@npm:^0.2.0": - version: 0.2.2 - resolution: "format@npm:0.2.2" - checksum: 646a60e1336250d802509cf24fb801e43bd4a70a07510c816fa133aa42cdbc9c21e66e9cc0801bb183c5b031c9d68be62e7fbb6877756e52357850f92aa28799 - languageName: node - linkType: hard - "forwarded@npm:0.2.0": version: 0.2.0 resolution: "forwarded@npm:0.2.0" @@ -19315,7 +19320,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^8.0.0, glob@npm:^8.0.1": +"glob@npm:^8.0.1": version: 8.1.0 resolution: "glob@npm:8.1.0" dependencies: @@ -19365,13 +19370,6 @@ __metadata: languageName: node linkType: hard -"globalyzer@npm:0.1.0": - version: 0.1.0 - resolution: "globalyzer@npm:0.1.0" - checksum: 419a0f95ba542534fac0842964d31b3dc2936a479b2b1a8a62bad7e8b61054faa9b0a06ad9f2e12593396b9b2621cac93358d9b3071d33723fb1778608d358a1 - languageName: node - linkType: hard - "globby@npm:^11.0.1, globby@npm:^11.0.2, globby@npm:^11.1.0": version: 11.1.0 resolution: "globby@npm:11.1.0" @@ -19413,13 +19411,6 @@ __metadata: languageName: node linkType: hard -"globrex@npm:^0.1.2": - version: 0.1.2 - resolution: "globrex@npm:0.1.2" - checksum: adca162494a176ce9ecf4dd232f7b802956bb1966b37f60c15e49d2e7d961b66c60826366dc2649093cad5a0d69970cfa8875bd1695b5a1a2f33dcd2aa88da3c - languageName: node - linkType: hard - "google-protobuf@npm:^3.14.0": version: 3.21.2 resolution: "google-protobuf@npm:3.21.2" @@ -19847,12 +19838,12 @@ __metadata: languageName: node linkType: hard -"hosted-git-info@npm:^5.0.0": - version: 5.2.1 - resolution: "hosted-git-info@npm:5.2.1" +"hosted-git-info@npm:^7.0.0": + version: 7.0.2 + resolution: "hosted-git-info@npm:7.0.2" dependencies: - lru-cache: ^7.5.1 - checksum: fa35df185224adfd69141f3b2f8cc31f50e705a5ebb415ccfbfd055c5b94bd08d3e658edf1edad9e2ac7d81831ac7cf261f5d219b3adc8d744fb8cdacaaf2ead + lru-cache: ^10.0.1 + checksum: 467cf908a56556417b18e86ae3b8dee03c2360ef1d51e61c4028fe87f6f309b6ff038589c94b5666af207da9d972d5107698906aabeb78aca134641962a5c6f8 languageName: node linkType: hard @@ -20217,13 +20208,6 @@ __metadata: languageName: node linkType: hard -"ignore@npm:^5.0.0": - version: 5.2.4 - resolution: "ignore@npm:5.2.4" - checksum: 3d4c309c6006e2621659311783eaea7ebcd41fe4ca1d78c91c473157ad6666a57a2df790fe0d07a12300d9aac2888204d7be8d59f9aaf665b1c7fcdb432517ef - languageName: node - linkType: hard - "ignore@npm:^5.1.8": version: 5.1.9 resolution: "ignore@npm:5.1.9" @@ -20245,6 +20229,13 @@ __metadata: languageName: node linkType: hard +"ignore@npm:^6.0.0": + version: 6.0.2 + resolution: "ignore@npm:6.0.2" + checksum: b5dfb811428a067d79c128144814d3d20f01ef21e19c2b2e5bc270895995ce352da9b0e1c46a33de7daaf214bf0cf59d3353c78cdf9e365aaea677db729c721e + languageName: node + linkType: hard + "ignore@npm:^7.0.5": version: 7.0.5 resolution: "ignore@npm:7.0.5" @@ -20306,10 +20297,10 @@ __metadata: languageName: node linkType: hard -"import-meta-resolve@npm:^2.0.0": - version: 2.2.1 - resolution: "import-meta-resolve@npm:2.2.1" - checksum: 8dd5ff7f6e96dea13653cbf79457588b9850949fde0412dba3cc944f8a61a155a3713d65878d903af6b0db7da960600ef66dfe994311743cdd49fe33dc91b0c5 +"import-meta-resolve@npm:^4.0.0": + version: 4.2.0 + resolution: "import-meta-resolve@npm:4.2.0" + checksum: fe5ca3258f22dc3dd4e2f2e8f6b54324c1cf0261216c7d9aae801b2eadf664bbd61e26cfb907a1238761285a3e9c8c23403321d52ca0e579c341b8d90c97fa52 languageName: node linkType: hard @@ -20379,10 +20370,10 @@ __metadata: languageName: node linkType: hard -"ini@npm:^3.0.0": - version: 3.0.1 - resolution: "ini@npm:3.0.1" - checksum: 947b582a822f06df3c22c75c90aec217d604ea11f7a20249530ee5c1cf8f508288439abe17b0e1d9b421bda5f4fae5e7aae0b18cb3ded5ac9d68f607df82f10f +"ini@npm:^4.1.2, ini@npm:^4.1.3": + version: 4.1.3 + resolution: "ini@npm:4.1.3" + checksum: 004b2be42388877c58add606149f1a0c7985c90a0ba5dbf45a4738fdc70b0798d922caecaa54617029626505898ac451ff0537a08b949836b49d3267f66542c9 languageName: node linkType: hard @@ -20572,13 +20563,6 @@ __metadata: languageName: node linkType: hard -"is-alphabetical@npm:^1.0.0": - version: 1.0.4 - resolution: "is-alphabetical@npm:1.0.4" - checksum: 6508cce44fd348f06705d377b260974f4ce68c74000e7da4045f0d919e568226dc3ce9685c5a2af272195384df6930f748ce9213fc9f399b5d31b362c66312cb - languageName: node - linkType: hard - "is-alphabetical@npm:^2.0.0": version: 2.0.1 resolution: "is-alphabetical@npm:2.0.1" @@ -20586,16 +20570,6 @@ __metadata: languageName: node linkType: hard -"is-alphanumerical@npm:^1.0.0": - version: 1.0.4 - resolution: "is-alphanumerical@npm:1.0.4" - dependencies: - is-alphabetical: ^1.0.0 - is-decimal: ^1.0.0 - checksum: e2e491acc16fcf5b363f7c726f666a9538dba0a043665740feb45bba1652457a73441e7c5179c6768a638ed396db3437e9905f403644ec7c468fb41f4813d03f - languageName: node - linkType: hard - "is-alphanumerical@npm:^2.0.0": version: 2.0.1 resolution: "is-alphanumerical@npm:2.0.1" @@ -20704,13 +20678,6 @@ __metadata: languageName: node linkType: hard -"is-buffer@npm:^2.0.0": - version: 2.0.5 - resolution: "is-buffer@npm:2.0.5" - checksum: 764c9ad8b523a9f5a32af29bdf772b08eb48c04d2ad0a7240916ac2688c983bf5f8504bf25b35e66240edeb9d9085461f9b5dae1f3d2861c6b06a65fe983de42 - languageName: node - linkType: hard - "is-builtin-module@npm:^3.2.0": version: 3.2.1 resolution: "is-builtin-module@npm:3.2.1" @@ -20829,13 +20796,6 @@ __metadata: languageName: node linkType: hard -"is-decimal@npm:^1.0.0": - version: 1.0.4 - resolution: "is-decimal@npm:1.0.4" - checksum: ed483a387517856dc395c68403a10201fddcc1b63dc56513fbe2fe86ab38766120090ecdbfed89223d84ca8b1cd28b0641b93cb6597b6e8f4c097a7c24e3fb96 - languageName: node - linkType: hard - "is-decimal@npm:^2.0.0": version: 2.0.1 resolution: "is-decimal@npm:2.0.1" @@ -20991,13 +20951,6 @@ __metadata: languageName: node linkType: hard -"is-hexadecimal@npm:^1.0.0": - version: 1.0.4 - resolution: "is-hexadecimal@npm:1.0.4" - checksum: a452e047587b6069332d83130f54d30da4faf2f2ebaa2ce6d073c27b5703d030d58ed9e0b729c8e4e5b52c6f1dab26781bb77b7bc6c7805f14f320e328ff8cd5 - languageName: node - linkType: hard - "is-hexadecimal@npm:^2.0.0": version: 2.0.1 resolution: "is-hexadecimal@npm:2.0.1" @@ -22513,7 +22466,6 @@ __metadata: version: 0.0.0-use.local resolution: "keplr@workspace:." dependencies: - "@1stg/remark-preset": ^2.0.0 "@octokit/core": ^3.5.1 "@types/filesystem": ^0.0.32 "@types/jest": ^29.4.0 @@ -22528,7 +22480,7 @@ __metadata: eslint: ^8.56.0 eslint-config-prettier: ^8.6.0 eslint-plugin-import: ^2.27.5 - eslint-plugin-mdx: ^2.0.5 + eslint-plugin-mdx: ^3.7.0 eslint-plugin-prettier: ^4.2.1 eslint-plugin-react: ^7.32.2 eslint-plugin-react-hooks: ^4.6.0 @@ -22601,13 +22553,6 @@ __metadata: languageName: node linkType: hard -"kleur@npm:^4.0.3": - version: 4.1.5 - resolution: "kleur@npm:4.1.5" - checksum: 1dc476e32741acf0b1b5b0627ffd0d722e342c1b0da14de3e8ae97821327ca08f9fb944542fb3c126d90ac5f27f9d804edbe7c585bf7d12ef495d115e0f22c12 - languageName: node - linkType: hard - "lazy-universal-dotenv@npm:^4.0.0": version: 4.0.0 resolution: "lazy-universal-dotenv@npm:4.0.0" @@ -22726,15 +22671,6 @@ __metadata: languageName: node linkType: hard -"levenshtein-edit-distance@npm:^1.0.0": - version: 1.0.0 - resolution: "levenshtein-edit-distance@npm:1.0.0" - bin: - levenshtein-edit-distance: cli.js - checksum: d628c4011a6724e4d6aea1af1c5d9ec8b3ba7f95eec0deb2e40ee77269e12b0433a7027f66e5375a17b803d36f86601c2692dfa4b2e6030aabc14969a2941de6 - languageName: node - linkType: hard - "levn@npm:^0.4.1": version: 0.4.1 resolution: "levn@npm:0.4.1" @@ -22788,7 +22724,7 @@ __metadata: languageName: node linkType: hard -"lines-and-columns@npm:2.0.3, lines-and-columns@npm:^2.0.2": +"lines-and-columns@npm:2.0.3": version: 2.0.3 resolution: "lines-and-columns@npm:2.0.3" checksum: 5955363dfd7d3d7c476d002eb47944dbe0310d57959e2112dce004c0dc76cecfd479cf8c098fd479ff344acdf04ee0e82b455462a26492231ac152f6c48d17a1 @@ -22802,6 +22738,13 @@ __metadata: languageName: node linkType: hard +"lines-and-columns@npm:^2.0.3": + version: 2.0.4 + resolution: "lines-and-columns@npm:2.0.4" + checksum: f5e3e207467d3e722280c962b786dc20ebceb191821dcd771d14ab3146b6744cae28cf305ee4638805bec524ac54800e15698c853fcc53243821f88df37e4975 + languageName: node + linkType: hard + "lint-staged@npm:^10.5.4": version: 10.5.4 resolution: "lint-staged@npm:10.5.4" @@ -22898,13 +22841,13 @@ __metadata: languageName: node linkType: hard -"load-plugin@npm:^5.0.0": - version: 5.1.0 - resolution: "load-plugin@npm:5.1.0" +"load-plugin@npm:^6.0.0": + version: 6.0.3 + resolution: "load-plugin@npm:6.0.3" dependencies: - "@npmcli/config": ^6.0.0 - import-meta-resolve: ^2.0.0 - checksum: d450c9a0838cec7f7581abcb8ff9170a5c6e1bc1ca816e4288e4a014bc0e819c4ca16163fd78b7e1fb6c1dffedf6ac881f9893d5a025b69f369a157d9a2aacf6 + "@npmcli/config": ^8.0.0 + import-meta-resolve: ^4.0.0 + checksum: 75e34816535fe3a1229452e4f9c98e661b282221be7d840f4f567d33955cf36e98443013e4d5c8a920800f2c32e76292e163e1b711ebe468b7c3a24b5f9d6dd6 languageName: node linkType: hard @@ -23235,13 +23178,6 @@ __metadata: languageName: node linkType: hard -"lru-cache@npm:^7.5.1": - version: 7.16.0 - resolution: "lru-cache@npm:7.16.0" - checksum: 1a5511a0e695154c37f443c495ebcf3170ec8c291df62e140329cf0c48e2549db836e69371853a28286dc9de73a98f486a7566c64246249a6041c43d4a28bc60 - languageName: node - linkType: hard - "lru-cache@npm:^7.7.1": version: 7.15.0 resolution: "lru-cache@npm:7.15.0" @@ -23445,13 +23381,6 @@ __metadata: languageName: node linkType: hard -"markdown-table@npm:^3.0.0": - version: 3.0.3 - resolution: "markdown-table@npm:3.0.3" - checksum: 8fcd3d9018311120fbb97115987f8b1665a603f3134c93fbecc5d1463380c8036f789e2a62c19432058829e594fff8db9ff81c88f83690b2f8ed6c074f8d9e10 - languageName: node - linkType: hard - "markdown-to-jsx@npm:^7.1.8": version: 7.4.7 resolution: "markdown-to-jsx@npm:7.4.7" @@ -23472,16 +23401,6 @@ __metadata: languageName: node linkType: hard -"mdast-comment-marker@npm:^2.0.0": - version: 2.1.1 - resolution: "mdast-comment-marker@npm:2.1.1" - dependencies: - "@types/mdast": ^3.0.0 - mdast-util-mdx-expression: ^1.1.0 - checksum: aab4b99068d7530404dced0442102b56fc88bdd385ede999e865386b505d7c8e2bd4f2111455d0d3d1f628dcbfc149042a721a172665fb6924c20033a50d5de0 - languageName: node - linkType: hard - "mdast-util-definitions@npm:^4.0.0": version: 4.0.0 resolution: "mdast-util-definitions@npm:4.0.0" @@ -23491,223 +23410,111 @@ __metadata: languageName: node linkType: hard -"mdast-util-find-and-replace@npm:^2.0.0": - version: 2.2.2 - resolution: "mdast-util-find-and-replace@npm:2.2.2" - dependencies: - "@types/mdast": ^3.0.0 - escape-string-regexp: ^5.0.0 - unist-util-is: ^5.0.0 - unist-util-visit-parents: ^5.0.0 - checksum: b4ce463c43fe6e1c38a53a89703f755c84ab5437f49bff9a0ac751279733332ca11c85ed0262aa6c17481f77b555d26ca6d64e70d6814f5b8d12d34a3e53a60b - languageName: node - linkType: hard - -"mdast-util-from-markdown@npm:^0.8.5": - version: 0.8.5 - resolution: "mdast-util-from-markdown@npm:0.8.5" - dependencies: - "@types/mdast": ^3.0.0 - mdast-util-to-string: ^2.0.0 - micromark: ~2.11.0 - parse-entities: ^2.0.0 - unist-util-stringify-position: ^2.0.0 - checksum: 5a9d0d753a42db763761e874c22365d0c7c9934a5a18b5ff76a0643610108a208a041ffdb2f3d3dd1863d3d915225a4020a0aade282af0facfd0df110601eee6 - languageName: node - linkType: hard - -"mdast-util-from-markdown@npm:^1.0.0, mdast-util-from-markdown@npm:^1.1.0": - version: 1.3.0 - resolution: "mdast-util-from-markdown@npm:1.3.0" +"mdast-util-from-markdown@npm:^2.0.0, mdast-util-from-markdown@npm:^2.0.2": + version: 2.0.3 + resolution: "mdast-util-from-markdown@npm:2.0.3" dependencies: - "@types/mdast": ^3.0.0 - "@types/unist": ^2.0.0 + "@types/mdast": ^4.0.0 + "@types/unist": ^3.0.0 decode-named-character-reference: ^1.0.0 - mdast-util-to-string: ^3.1.0 - micromark: ^3.0.0 - micromark-util-decode-numeric-character-reference: ^1.0.0 - micromark-util-decode-string: ^1.0.0 - micromark-util-normalize-identifier: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - unist-util-stringify-position: ^3.0.0 - uvu: ^0.5.0 - checksum: cc971d1ad381150f6504fd753fbcffcc64c0abb527540ce343625c2bba76104505262122ef63d14ab66eb47203f323267017c6d09abfa8535ee6a8e14069595f - languageName: node - linkType: hard - -"mdast-util-frontmatter@npm:^1.0.0": - version: 1.0.1 - resolution: "mdast-util-frontmatter@npm:1.0.1" - dependencies: - "@types/mdast": ^3.0.0 - mdast-util-to-markdown: ^1.3.0 - micromark-extension-frontmatter: ^1.0.0 - checksum: 0b0552047b753931da8265f2231a0b79aad1309ad7ad6599181c2d264e9b70b61acf742f29bdf2de8e901eb6eb37dd42b6f66007e735fd8138b2bf4d9acb0118 - languageName: node - linkType: hard - -"mdast-util-gfm-autolink-literal@npm:^1.0.0": - version: 1.0.3 - resolution: "mdast-util-gfm-autolink-literal@npm:1.0.3" - dependencies: - "@types/mdast": ^3.0.0 - ccount: ^2.0.0 - mdast-util-find-and-replace: ^2.0.0 - micromark-util-character: ^1.0.0 - checksum: 1748a8727cfc533bac0c287d6e72d571d165bfa77ae0418be4828177a3ec73c02c3f2ee534d87eb75cbaffa00c0866853bbcc60ae2255babb8210f7636ec2ce2 - languageName: node - linkType: hard - -"mdast-util-gfm-footnote@npm:^1.0.0": - version: 1.0.2 - resolution: "mdast-util-gfm-footnote@npm:1.0.2" - dependencies: - "@types/mdast": ^3.0.0 - mdast-util-to-markdown: ^1.3.0 - micromark-util-normalize-identifier: ^1.0.0 - checksum: 2d77505f9377ed7e14472ef5e6b8366c3fec2cf5f936bb36f9fbe5b97ccb7cce0464d9313c236fa86fb844206fd585db05707e4fcfb755e4fc1864194845f1f6 + devlop: ^1.0.0 + mdast-util-to-string: ^4.0.0 + micromark: ^4.0.0 + micromark-util-decode-numeric-character-reference: ^2.0.0 + micromark-util-decode-string: ^2.0.0 + micromark-util-normalize-identifier: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + unist-util-stringify-position: ^4.0.0 + checksum: 2e6e9fbf521e0974c8c83082a4e5e5d17c32442e7584cbe248f92037d6b4ea13b822af3380363b99e81b181cddf7e61e62353aa3746a395de7e2fd0101007b8e languageName: node linkType: hard -"mdast-util-gfm-strikethrough@npm:^1.0.0": - version: 1.0.3 - resolution: "mdast-util-gfm-strikethrough@npm:1.0.3" - dependencies: - "@types/mdast": ^3.0.0 - mdast-util-to-markdown: ^1.3.0 - checksum: 17003340ff1bba643ec4a59fd4370fc6a32885cab2d9750a508afa7225ea71449fb05acaef60faa89c6378b8bcfbd86a9d94b05f3c6651ff27a60e3ddefc2549 - languageName: node - linkType: hard - -"mdast-util-gfm-table@npm:^1.0.0": - version: 1.0.7 - resolution: "mdast-util-gfm-table@npm:1.0.7" - dependencies: - "@types/mdast": ^3.0.0 - markdown-table: ^3.0.0 - mdast-util-from-markdown: ^1.0.0 - mdast-util-to-markdown: ^1.3.0 - checksum: 8b8c401bb4162e53f072a2dff8efbca880fd78d55af30601c791315ab6722cb2918176e8585792469a0c530cebb9df9b4e7fede75fdc4d83df2839e238836692 - languageName: node - linkType: hard - -"mdast-util-gfm-task-list-item@npm:^1.0.0": - version: 1.0.2 - resolution: "mdast-util-gfm-task-list-item@npm:1.0.2" - dependencies: - "@types/mdast": ^3.0.0 - mdast-util-to-markdown: ^1.3.0 - checksum: c9b86037d6953b84f11fb2fc3aa23d5b8e14ca0dfcb0eb2fb289200e172bb9d5647bfceb4f86606dc6d935e8d58f6a458c04d3e55e87ff8513c7d4ade976200b - languageName: node - linkType: hard - -"mdast-util-gfm@npm:^2.0.0": - version: 2.0.2 - resolution: "mdast-util-gfm@npm:2.0.2" - dependencies: - mdast-util-from-markdown: ^1.0.0 - mdast-util-gfm-autolink-literal: ^1.0.0 - mdast-util-gfm-footnote: ^1.0.0 - mdast-util-gfm-strikethrough: ^1.0.0 - mdast-util-gfm-table: ^1.0.0 - mdast-util-gfm-task-list-item: ^1.0.0 - mdast-util-to-markdown: ^1.0.0 - checksum: 7078cb985255208bcbce94a121906417d38353c6b1a9acbe56ee8888010d3500608b5d51c16b0999ac63ca58848fb13012d55f26930ff6c6f3450f053d56514e - languageName: node - linkType: hard - -"mdast-util-heading-style@npm:^2.0.0": +"mdast-util-mdx-expression@npm:^2.0.0": version: 2.0.1 - resolution: "mdast-util-heading-style@npm:2.0.1" - dependencies: - "@types/mdast": ^3.0.0 - checksum: 8ea1a36f526170f09e4ad597c3b75a580efe5b1736d6d21ccad222a002281ae4066414bf2e12911c72561762069a5045a817899a245999a66c01edefdba5a96b - languageName: node - linkType: hard - -"mdast-util-mdx-expression@npm:^1.0.0, mdast-util-mdx-expression@npm:^1.1.0": - version: 1.3.2 - resolution: "mdast-util-mdx-expression@npm:1.3.2" + resolution: "mdast-util-mdx-expression@npm:2.0.1" dependencies: "@types/estree-jsx": ^1.0.0 - "@types/hast": ^2.0.0 - "@types/mdast": ^3.0.0 - mdast-util-from-markdown: ^1.0.0 - mdast-util-to-markdown: ^1.0.0 - checksum: e4c90f26deaa5eb6217b0a9af559a80de41da02ab3bcd864c56bed3304b056ae703896e9876bc6ded500f4aff59f4de5cbf6a4b109a5ba408f2342805fe6dc05 + "@types/hast": ^3.0.0 + "@types/mdast": ^4.0.0 + devlop: ^1.0.0 + mdast-util-from-markdown: ^2.0.0 + mdast-util-to-markdown: ^2.0.0 + checksum: 6af56b06bde3ab971129db9855dcf0d31806c70b3b052d7a90a5499a366b57ffd0c2efca67d281c448c557298ba7e3e61bd07133733b735440840dd339b28e19 languageName: node linkType: hard -"mdast-util-mdx-jsx@npm:^2.0.0": - version: 2.1.2 - resolution: "mdast-util-mdx-jsx@npm:2.1.2" +"mdast-util-mdx-jsx@npm:^3.0.0": + version: 3.2.0 + resolution: "mdast-util-mdx-jsx@npm:3.2.0" dependencies: "@types/estree-jsx": ^1.0.0 - "@types/hast": ^2.0.0 - "@types/mdast": ^3.0.0 - "@types/unist": ^2.0.0 + "@types/hast": ^3.0.0 + "@types/mdast": ^4.0.0 + "@types/unist": ^3.0.0 ccount: ^2.0.0 - mdast-util-from-markdown: ^1.1.0 - mdast-util-to-markdown: ^1.3.0 + devlop: ^1.1.0 + mdast-util-from-markdown: ^2.0.0 + mdast-util-to-markdown: ^2.0.0 parse-entities: ^4.0.0 stringify-entities: ^4.0.0 - unist-util-remove-position: ^4.0.0 - unist-util-stringify-position: ^3.0.0 - vfile-message: ^3.0.0 - checksum: 637e0bbd97c0c783f6b12bb05ccb1edaec076c5aa6d349147d77b8e6e10677f1be8e2870c05b1896f69095c9bc527f34be72b349b30737ab2e499bfc579b3a28 + unist-util-stringify-position: ^4.0.0 + vfile-message: ^4.0.0 + checksum: 224f5f6ad247f0f2622ee36c82ac7a4c6a60c31850de4056bf95f531bd2f7ec8943ef34dfe8a8375851f65c07e4913c4f33045d703df4ff4d11b2de5a088f7f9 languageName: node linkType: hard -"mdast-util-mdx@npm:^2.0.0": - version: 2.0.1 - resolution: "mdast-util-mdx@npm:2.0.1" +"mdast-util-mdx@npm:^3.0.0": + version: 3.0.0 + resolution: "mdast-util-mdx@npm:3.0.0" dependencies: - mdast-util-from-markdown: ^1.0.0 - mdast-util-mdx-expression: ^1.0.0 - mdast-util-mdx-jsx: ^2.0.0 - mdast-util-mdxjs-esm: ^1.0.0 - mdast-util-to-markdown: ^1.0.0 - checksum: 7303149230a26e524e319833b782bffca94e49cdab012996618701259bd056e014ca22a35d25ffa8880ba9064ee126a2a002f01e5c90a31ca726339ed775875e + mdast-util-from-markdown: ^2.0.0 + mdast-util-mdx-expression: ^2.0.0 + mdast-util-mdx-jsx: ^3.0.0 + mdast-util-mdxjs-esm: ^2.0.0 + mdast-util-to-markdown: ^2.0.0 + checksum: e2b007d826fcd49fd57ed03e190753c8b0f7d9eff6c7cb26ba609cde15cd3a472c0cd5e4a1ee3e39a40f14be22fdb57de243e093cea0c064d6f3366cff3e3af2 languageName: node linkType: hard -"mdast-util-mdxjs-esm@npm:^1.0.0": - version: 1.3.1 - resolution: "mdast-util-mdxjs-esm@npm:1.3.1" +"mdast-util-mdxjs-esm@npm:^2.0.0": + version: 2.0.1 + resolution: "mdast-util-mdxjs-esm@npm:2.0.1" dependencies: "@types/estree-jsx": ^1.0.0 - "@types/hast": ^2.0.0 - "@types/mdast": ^3.0.0 - mdast-util-from-markdown: ^1.0.0 - mdast-util-to-markdown: ^1.0.0 - checksum: ee78a4f58adfec38723cbc920f05481201ebb001eff3982f2d0e5f5ce5c75685e732e9d361ad4a1be8b936b4e5de0f2599cb96b92ad4bd92698ac0c4a09bbec3 + "@types/hast": ^3.0.0 + "@types/mdast": ^4.0.0 + devlop: ^1.0.0 + mdast-util-from-markdown: ^2.0.0 + mdast-util-to-markdown: ^2.0.0 + checksum: 1f9dad04d31d59005332e9157ea9510dc1d03092aadbc607a10475c7eec1c158b475aa0601a3a4f74e13097ca735deb8c2d9d37928ddef25d3029fd7c9e14dc3 languageName: node linkType: hard -"mdast-util-phrasing@npm:^3.0.0": - version: 3.0.1 - resolution: "mdast-util-phrasing@npm:3.0.1" +"mdast-util-phrasing@npm:^4.0.0": + version: 4.1.0 + resolution: "mdast-util-phrasing@npm:4.1.0" dependencies: - "@types/mdast": ^3.0.0 - unist-util-is: ^5.0.0 - checksum: c5b616d9b1eb76a6b351d195d94318494722525a12a89d9c8a3b091af7db3dd1fc55d294f9d29266d8159a8267b0df4a7a133bda8a3909d5331c383e1e1ff328 + "@types/mdast": ^4.0.0 + unist-util-is: ^6.0.0 + checksum: 3a97533e8ad104a422f8bebb34b3dde4f17167b8ed3a721cf9263c7416bd3447d2364e6d012a594aada40cac9e949db28a060bb71a982231693609034ed5324e languageName: node linkType: hard -"mdast-util-to-markdown@npm:^1.0.0, mdast-util-to-markdown@npm:^1.3.0": - version: 1.5.0 - resolution: "mdast-util-to-markdown@npm:1.5.0" +"mdast-util-to-markdown@npm:^2.0.0": + version: 2.1.2 + resolution: "mdast-util-to-markdown@npm:2.1.2" dependencies: - "@types/mdast": ^3.0.0 - "@types/unist": ^2.0.0 + "@types/mdast": ^4.0.0 + "@types/unist": ^3.0.0 longest-streak: ^3.0.0 - mdast-util-phrasing: ^3.0.0 - mdast-util-to-string: ^3.0.0 - micromark-util-decode-string: ^1.0.0 - unist-util-visit: ^4.0.0 + mdast-util-phrasing: ^4.0.0 + mdast-util-to-string: ^4.0.0 + micromark-util-classify-character: ^2.0.0 + micromark-util-decode-string: ^2.0.0 + unist-util-visit: ^5.0.0 zwitch: ^2.0.0 - checksum: 64338eb33e49bb0aea417591fd986f72fdd39205052563bb7ce9eb9ecc160824509bfacd740086a05af355c6d5c36353aafe95cab9e6927d674478757cee6259 + checksum: 288d152bd50c00632e6e01c610bb904a220d1e226c8086c40627877959746f83ab0b872f4150cb7d910198953b1bf756e384ac3fee3e7b0ddb4517f9084c5803 languageName: node linkType: hard @@ -23718,19 +23525,12 @@ __metadata: languageName: node linkType: hard -"mdast-util-to-string@npm:^2.0.0": - version: 2.0.0 - resolution: "mdast-util-to-string@npm:2.0.0" - checksum: 0b2113ada10e002fbccb014170506dabe2f2ddacaacbe4bc1045c33f986652c5a162732a2c057c5335cdb58419e2ad23e368e5be226855d4d4e280b81c4e9ec2 - languageName: node - linkType: hard - -"mdast-util-to-string@npm:^3.0.0, mdast-util-to-string@npm:^3.1.0": - version: 3.1.1 - resolution: "mdast-util-to-string@npm:3.1.1" +"mdast-util-to-string@npm:^4.0.0": + version: 4.0.0 + resolution: "mdast-util-to-string@npm:4.0.0" dependencies: - "@types/mdast": ^3.0.0 - checksum: 5e9375e1757ebf2950e122ef3538e4257ed2b6f43ab1d3e9c45db5dd5d5b5d14fd041490afcde00934f1cdb4b99877597ae04eb810d313ec7b38c6009058dddd + "@types/mdast": ^4.0.0 + checksum: 35489fb5710d58cbc2d6c8b6547df161a3f81e0f28f320dfb3548a9393555daf07c310c0c497708e67ed4dfea4a06e5655799e7d631ca91420c288b4525d6c29 languageName: node linkType: hard @@ -23852,459 +23652,347 @@ __metadata: languageName: node linkType: hard -"micromark-core-commonmark@npm:^1.0.0, micromark-core-commonmark@npm:^1.0.1": - version: 1.0.6 - resolution: "micromark-core-commonmark@npm:1.0.6" +"micromark-core-commonmark@npm:^2.0.0": + version: 2.0.3 + resolution: "micromark-core-commonmark@npm:2.0.3" dependencies: decode-named-character-reference: ^1.0.0 - micromark-factory-destination: ^1.0.0 - micromark-factory-label: ^1.0.0 - micromark-factory-space: ^1.0.0 - micromark-factory-title: ^1.0.0 - micromark-factory-whitespace: ^1.0.0 - micromark-util-character: ^1.0.0 - micromark-util-chunked: ^1.0.0 - micromark-util-classify-character: ^1.0.0 - micromark-util-html-tag-name: ^1.0.0 - micromark-util-normalize-identifier: ^1.0.0 - micromark-util-resolve-all: ^1.0.0 - micromark-util-subtokenize: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.1 - uvu: ^0.5.0 - checksum: 4b483c46077f696ed310f6d709bb9547434c218ceb5c1220fde1707175f6f68b44da15ab8668f9c801e1a123210071e3af883a7d1215122c913fd626f122bfc2 - languageName: node - linkType: hard - -"micromark-extension-frontmatter@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-extension-frontmatter@npm:1.0.0" - dependencies: - fault: ^2.0.0 - micromark-util-character: ^1.0.0 - micromark-util-symbol: ^1.0.0 - checksum: d0bacd6aadd6e33e26245628b93f5bcaf9a1de47787cea6807f8569213ceeb1376c37fadcf059280f5eafe6a07682bd148989e65489e99c9a3f4d523eea5f5c0 - languageName: node - linkType: hard - -"micromark-extension-gfm-autolink-literal@npm:^1.0.0": - version: 1.0.3 - resolution: "micromark-extension-gfm-autolink-literal@npm:1.0.3" - dependencies: - micromark-util-character: ^1.0.0 - micromark-util-sanitize-uri: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - uvu: ^0.5.0 - checksum: bb181972ac346ca73ca1ab0b80b80c9d6509ed149799d2217d5442670f499c38a94edff73d32fa52b390d89640974cfbd7f29e4ad7d599581d5e1cabcae636a2 - languageName: node - linkType: hard - -"micromark-extension-gfm-footnote@npm:^1.0.0": - version: 1.0.4 - resolution: "micromark-extension-gfm-footnote@npm:1.0.4" - dependencies: - micromark-core-commonmark: ^1.0.0 - micromark-factory-space: ^1.0.0 - micromark-util-character: ^1.0.0 - micromark-util-normalize-identifier: ^1.0.0 - micromark-util-sanitize-uri: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - uvu: ^0.5.0 - checksum: 8daa203f5cf753338d5ecdbaae6b3ab6319d34b6013b90ea6860bed299418cecf86e69e48dabe42562e334760c738c77c5acdb47e75ae26f5f01f02f3bf0952d - languageName: node - linkType: hard - -"micromark-extension-gfm-strikethrough@npm:^1.0.0": - version: 1.0.4 - resolution: "micromark-extension-gfm-strikethrough@npm:1.0.4" - dependencies: - micromark-util-chunked: ^1.0.0 - micromark-util-classify-character: ^1.0.0 - micromark-util-resolve-all: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - uvu: ^0.5.0 - checksum: f43d316b85fe93df1711cdcdc99a5320b941239349234bd262fc708cb67ad47bdfb41d1a7ebe2a5829816b0e9d3107380a5c1e558cb536a75354cbe4857823ba - languageName: node - linkType: hard - -"micromark-extension-gfm-table@npm:^1.0.0": - version: 1.0.5 - resolution: "micromark-extension-gfm-table@npm:1.0.5" - dependencies: - micromark-factory-space: ^1.0.0 - micromark-util-character: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - uvu: ^0.5.0 - checksum: f0aab3b4333cc24b1534b08dc4cce986dd606df8b7ed913e5a1de9fe2d3ae67b2435663c0bc271b528874af4928e580e1ad540ea9117d7f2d74edb28859c97ef - languageName: node - linkType: hard - -"micromark-extension-gfm-tagfilter@npm:^1.0.0": - version: 1.0.1 - resolution: "micromark-extension-gfm-tagfilter@npm:1.0.1" - dependencies: - micromark-util-types: ^1.0.0 - checksum: 63e8d68f25871722900a67a8001d5da21f19ea707f3566fc7d0b2eb1f6d52476848bb6a41576cf22470565124af9497c5aae842355faa4c14ec19cb1847e71ec - languageName: node - linkType: hard - -"micromark-extension-gfm-task-list-item@npm:^1.0.0": - version: 1.0.3 - resolution: "micromark-extension-gfm-task-list-item@npm:1.0.3" - dependencies: - micromark-factory-space: ^1.0.0 - micromark-util-character: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - uvu: ^0.5.0 - checksum: d320b0c5301f87e211c06a2330d1ee0fee6da14f0d6d44d5211055b465dadff34390cd6b258a5e0ca376fcda3364fef9a12fe6e26a0c858231fa3b98ddbf7785 - languageName: node - linkType: hard - -"micromark-extension-gfm@npm:^2.0.0": - version: 2.0.1 - resolution: "micromark-extension-gfm@npm:2.0.1" - dependencies: - micromark-extension-gfm-autolink-literal: ^1.0.0 - micromark-extension-gfm-footnote: ^1.0.0 - micromark-extension-gfm-strikethrough: ^1.0.0 - micromark-extension-gfm-table: ^1.0.0 - micromark-extension-gfm-tagfilter: ^1.0.0 - micromark-extension-gfm-task-list-item: ^1.0.0 - micromark-util-combine-extensions: ^1.0.0 - micromark-util-types: ^1.0.0 - checksum: b181479c87be38d5ae8d28e6dc52fab73c894fd2706876746f27a91fb186644ce03532a9c35dca2186327a0e2285cd5242ad0361dc89adedd4a50376ffd94e22 - languageName: node - linkType: hard - -"micromark-extension-mdx-expression@npm:^1.0.0": - version: 1.0.4 - resolution: "micromark-extension-mdx-expression@npm:1.0.4" + devlop: ^1.0.0 + micromark-factory-destination: ^2.0.0 + micromark-factory-label: ^2.0.0 + micromark-factory-space: ^2.0.0 + micromark-factory-title: ^2.0.0 + micromark-factory-whitespace: ^2.0.0 + micromark-util-character: ^2.0.0 + micromark-util-chunked: ^2.0.0 + micromark-util-classify-character: ^2.0.0 + micromark-util-html-tag-name: ^2.0.0 + micromark-util-normalize-identifier: ^2.0.0 + micromark-util-resolve-all: ^2.0.0 + micromark-util-subtokenize: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: cfb0fd9c895f86a4e9344f7f0344fe6bd1018945798222835248146a42430b8c7bc0b2857af574cf4e1b4ce4e5c1a35a1479942421492e37baddde8de85814dc + languageName: node + linkType: hard + +"micromark-extension-mdx-expression@npm:^3.0.0": + version: 3.0.1 + resolution: "micromark-extension-mdx-expression@npm:3.0.1" dependencies: - micromark-factory-mdx-expression: ^1.0.0 - micromark-factory-space: ^1.0.0 - micromark-util-character: ^1.0.0 - micromark-util-events-to-acorn: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - uvu: ^0.5.0 - checksum: d19a31f9813dd5d4ad96b99e35b7c48067e69d75f92ec670dad5242857fb7688ba8b7c6a15616797b5df25dd89fd3b54916f93cb60ce2cfe97aca84739b45954 + "@types/estree": ^1.0.0 + devlop: ^1.0.0 + micromark-factory-mdx-expression: ^2.0.0 + micromark-factory-space: ^2.0.0 + micromark-util-character: ^2.0.0 + micromark-util-events-to-acorn: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: 0e15bc3911b53704723acc300d99093e46e31a1f2210f6fadeaf065d04c964cd4588cf4aa1e9c324430bfd943dfa7f36e369a3bc92f4641015b107bbb2190034 languageName: node linkType: hard -"micromark-extension-mdx-jsx@npm:^1.0.0": - version: 1.0.3 - resolution: "micromark-extension-mdx-jsx@npm:1.0.3" +"micromark-extension-mdx-jsx@npm:^3.0.0": + version: 3.0.2 + resolution: "micromark-extension-mdx-jsx@npm:3.0.2" dependencies: - "@types/acorn": ^4.0.0 - estree-util-is-identifier-name: ^2.0.0 - micromark-factory-mdx-expression: ^1.0.0 - micromark-factory-space: ^1.0.0 - micromark-util-character: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - uvu: ^0.5.0 - vfile-message: ^3.0.0 - checksum: 1a5566890aabc52fe96b78e3a3a507dee03a2232e44b9360b00617734e156f934e85bc6a477fbb856c793fe33c9fb7d2207a4f50e680168c0d04ba9c9336d960 + "@types/estree": ^1.0.0 + devlop: ^1.0.0 + estree-util-is-identifier-name: ^3.0.0 + micromark-factory-mdx-expression: ^2.0.0 + micromark-factory-space: ^2.0.0 + micromark-util-character: ^2.0.0 + micromark-util-events-to-acorn: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + vfile-message: ^4.0.0 + checksum: abe07e592a95804445d2c667bc999696ac39ddd551374f5a39e2d910c8b25e75bf61b4933213696f7bc26f4a5a56d91b3ce31d9a063b6fd7bbd4633565b1d6ec languageName: node linkType: hard -"micromark-extension-mdx-md@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-extension-mdx-md@npm:1.0.0" +"micromark-extension-mdx-md@npm:^2.0.0": + version: 2.0.0 + resolution: "micromark-extension-mdx-md@npm:2.0.0" dependencies: - micromark-util-types: ^1.0.0 - checksum: b4f205e1d5f0946b4755541ef44ffd0b3be8c7ecfc08d8b139b6a21fbd3ff62d8fdb6b7e6d17bd9a3b610450267f43a41703dc48b341da9addd743a28cdefa64 + micromark-util-types: ^2.0.0 + checksum: 7daf03372fd7faddf3f0ac87bdb0debb0bb770f33b586f72251e1072b222ceee75400ab6194c0e130dbf1e077369a5b627be6e9130d7a2e9e6b849f0d18ff246 languageName: node linkType: hard -"micromark-extension-mdxjs-esm@npm:^1.0.0": - version: 1.0.3 - resolution: "micromark-extension-mdxjs-esm@npm:1.0.3" +"micromark-extension-mdxjs-esm@npm:^3.0.0": + version: 3.0.0 + resolution: "micromark-extension-mdxjs-esm@npm:3.0.0" dependencies: - micromark-core-commonmark: ^1.0.0 - micromark-util-character: ^1.0.0 - micromark-util-events-to-acorn: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - unist-util-position-from-estree: ^1.1.0 - uvu: ^0.5.0 - vfile-message: ^3.0.0 - checksum: 756074656391a5e5bb96bc8a0e9c1df7d9f7be5299847c9719e6a90552e1c76a11876aa89986ad5da89ab485f776a4a43a61ea3acddd4f865a5cee43ac523ffd + "@types/estree": ^1.0.0 + devlop: ^1.0.0 + micromark-core-commonmark: ^2.0.0 + micromark-util-character: ^2.0.0 + micromark-util-events-to-acorn: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + unist-util-position-from-estree: ^2.0.0 + vfile-message: ^4.0.0 + checksum: fb33d850200afce567b95c90f2f7d42259bd33eea16154349e4fa77c3ec934f46c8e5c111acea16321dce3d9f85aaa4c49afe8b810e31b34effc11617aeee8f6 languageName: node linkType: hard -"micromark-extension-mdxjs@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-extension-mdxjs@npm:1.0.0" +"micromark-extension-mdxjs@npm:^3.0.0": + version: 3.0.0 + resolution: "micromark-extension-mdxjs@npm:3.0.0" dependencies: acorn: ^8.0.0 acorn-jsx: ^5.0.0 - micromark-extension-mdx-expression: ^1.0.0 - micromark-extension-mdx-jsx: ^1.0.0 - micromark-extension-mdx-md: ^1.0.0 - micromark-extension-mdxjs-esm: ^1.0.0 - micromark-util-combine-extensions: ^1.0.0 - micromark-util-types: ^1.0.0 - checksum: ba836c6d2dfc67597886e88f533ffa02f2029dbe216a0651f1066e70f8529a700bcc7fa2bc4201ee12fd3d1cd7da7093d5a442442daeb84b27df96aaffb7699c + micromark-extension-mdx-expression: ^3.0.0 + micromark-extension-mdx-jsx: ^3.0.0 + micromark-extension-mdx-md: ^2.0.0 + micromark-extension-mdxjs-esm: ^3.0.0 + micromark-util-combine-extensions: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: 7da6f0fb0e1e0270a2f5ad257e7422cc16e68efa7b8214c63c9d55bc264cb872e9ca4ac9a71b9dfd13daf52e010f730bac316086f4340e4fcc6569ec699915bf languageName: node linkType: hard -"micromark-factory-destination@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-factory-destination@npm:1.0.0" +"micromark-factory-destination@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-factory-destination@npm:2.0.1" dependencies: - micromark-util-character: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - checksum: 8e733ae9c1c2342f14ff290bf09946e20f6f540117d80342377a765cac48df2ea5e748f33c8b07501ad7a43414b1a6597c8510ede2052b6bf1251fab89748e20 + micromark-util-character: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: 9c4baa9ca2ed43c061bbf40ddd3d85154c2a0f1f485de9dea41d7dd2ad994ebb02034a003b2c1dbe228ba83a0576d591f0e90e0bf978713f84ee7d7f3aa98320 languageName: node linkType: hard -"micromark-factory-label@npm:^1.0.0": - version: 1.0.2 - resolution: "micromark-factory-label@npm:1.0.2" +"micromark-factory-label@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-factory-label@npm:2.0.1" dependencies: - micromark-util-character: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - uvu: ^0.5.0 - checksum: 957e9366bdc8dbc1437c0706ff96972fa985ab4b1274abcae12f6094f527cbf5c69e7f2304c23c7f4b96e311ff7911d226563b8b43dcfcd4091e8c985fb97ce6 + devlop: ^1.0.0 + micromark-util-character: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: bd03f5a75f27cdbf03b894ddc5c4480fc0763061fecf9eb927d6429233c930394f223969a99472df142d570c831236134de3dc23245d23d9f046f9d0b623b5c2 languageName: node linkType: hard -"micromark-factory-mdx-expression@npm:^1.0.0": - version: 1.0.7 - resolution: "micromark-factory-mdx-expression@npm:1.0.7" +"micromark-factory-mdx-expression@npm:^2.0.0": + version: 2.0.3 + resolution: "micromark-factory-mdx-expression@npm:2.0.3" dependencies: - micromark-factory-space: ^1.0.0 - micromark-util-character: ^1.0.0 - micromark-util-events-to-acorn: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - unist-util-position-from-estree: ^1.0.0 - uvu: ^0.5.0 - vfile-message: ^3.0.0 - checksum: e7893f21576bcb7755d341e45d3ff202ba466fa2278c6f31ae4db4002a28d6d13a4efad331ef46223372ec2010d9bc2ff27e2cd57a4580be6491e59ca21ba59d + "@types/estree": ^1.0.0 + devlop: ^1.0.0 + micromark-factory-space: ^2.0.0 + micromark-util-character: ^2.0.0 + micromark-util-events-to-acorn: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + unist-util-position-from-estree: ^2.0.0 + vfile-message: ^4.0.0 + checksum: f007987092a3bd00617f023d324caff10c63982e5125a3e3ff147baaf03f378e21c47306e2094b8c6480a726c57785c2175b4ffc3f3a6fde8be87e40fbdff068 languageName: node linkType: hard -"micromark-factory-space@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-factory-space@npm:1.0.0" +"micromark-factory-space@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-factory-space@npm:2.0.1" dependencies: - micromark-util-character: ^1.0.0 - micromark-util-types: ^1.0.0 - checksum: 70d3aafde4e68ef4e509a3b644e9a29e4aada00801279e346577b008cbca06d78051bcd62aa7ea7425856ed73f09abd2b36607803055f726f52607ee7cb706b0 + micromark-util-character: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: 1bd68a017c1a66f4787506660c1e1c5019169aac3b1cb075d49ac5e360e0b2065e984d4e1d6e9e52a9d44000f2fa1c98e66a743d7aae78b4b05616bf3242ed71 languageName: node linkType: hard -"micromark-factory-title@npm:^1.0.0": - version: 1.0.2 - resolution: "micromark-factory-title@npm:1.0.2" +"micromark-factory-title@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-factory-title@npm:2.0.1" dependencies: - micromark-factory-space: ^1.0.0 - micromark-util-character: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - uvu: ^0.5.0 - checksum: 9a9cf66babde0bad1e25d6c1087082bfde6dfc319a36cab67c89651cc1a53d0e21cdec83262b5a4c33bff49f0e3c8dc2a7bd464e991d40dbea166a8f9b37e5b2 + micromark-factory-space: ^2.0.0 + micromark-util-character: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: b4d2e4850a8ba0dff25ce54e55a3eb0d43dda88a16293f53953153288f9d84bcdfa8ca4606b2cfbb4f132ea79587bbb478a73092a349f893f5264fbcdbce2ee1 languageName: node linkType: hard -"micromark-factory-whitespace@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-factory-whitespace@npm:1.0.0" +"micromark-factory-whitespace@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-factory-whitespace@npm:2.0.1" dependencies: - micromark-factory-space: ^1.0.0 - micromark-util-character: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - checksum: 0888386e6ea2dd665a5182c570d9b3d0a172d3f11694ca5a2a84e552149c9f1429f5b975ec26e1f0fa4388c55a656c9f359ce5e0603aff6175ba3e255076f20b + micromark-factory-space: ^2.0.0 + micromark-util-character: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: 67b3944d012a42fee9e10e99178254a04d48af762b54c10a50fcab988688799993efb038daf9f5dbc04001a97b9c1b673fc6f00e6a56997877ab25449f0c8650 languageName: node linkType: hard -"micromark-util-character@npm:^1.0.0": - version: 1.1.0 - resolution: "micromark-util-character@npm:1.1.0" +"micromark-util-character@npm:^2.0.0": + version: 2.1.1 + resolution: "micromark-util-character@npm:2.1.1" dependencies: - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - checksum: 504a4e3321f69bddf3fec9f0c1058239fc23336bda5be31d532b150491eda47965a251b37f8a7a9db0c65933b3aaa49cf88044fb1028be3af7c5ee6212bf8d5f + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: e9e409efe4f2596acd44587e8591b722bfc041c1577e8fe0d9c007a4776fb800f9b3637a22862ad2ba9489f4bdf72bb547fce5767dbbfe0a5e6760e2a21c6495 languageName: node linkType: hard -"micromark-util-chunked@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-util-chunked@npm:1.0.0" +"micromark-util-chunked@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-chunked@npm:2.0.1" dependencies: - micromark-util-symbol: ^1.0.0 - checksum: c1efd56e8c4217bcf1c6f1a9fb9912b4a2a5503b00d031da902be922fb3fee60409ac53f11739991291357b2784fb0647ddfc74c94753a068646c0cb0fd71421 + micromark-util-symbol: ^2.0.0 + checksum: f8cb2a67bcefe4bd2846d838c97b777101f0043b9f1de4f69baf3e26bb1f9885948444e3c3aec66db7595cad8173bd4567a000eb933576c233d54631f6323fe4 languageName: node linkType: hard -"micromark-util-classify-character@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-util-classify-character@npm:1.0.0" +"micromark-util-classify-character@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-classify-character@npm:2.0.1" dependencies: - micromark-util-character: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - checksum: 180446e6a1dec653f625ded028f244784e1db8d10ad05c5d70f08af9de393b4a03dc6cf6fa5ed8ccc9c24bbece7837abf3bf66681c0b4adf159364b7d5236dfd + micromark-util-character: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: 4d8bbe3a6dbf69ac0fc43516866b5bab019fe3f4568edc525d4feaaaf78423fa54e6b6732b5bccbeed924455279a3758ffc9556954aafb903982598a95a02704 languageName: node linkType: hard -"micromark-util-combine-extensions@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-util-combine-extensions@npm:1.0.0" +"micromark-util-combine-extensions@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-combine-extensions@npm:2.0.1" dependencies: - micromark-util-chunked: ^1.0.0 - micromark-util-types: ^1.0.0 - checksum: 5304a820ef75340e1be69d6ad167055b6ba9a3bafe8171e5945a935752f462415a9dd61eb3490220c055a8a11167209a45bfa73f278338b7d3d61fa1464d3f35 + micromark-util-chunked: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: 5d22fb9ee37e8143adfe128a72b50fa09568c2cc553b3c76160486c96dbbb298c5802a177a10a215144a604b381796071b5d35be1f2c2b2ee17995eda92f0c8e languageName: node linkType: hard -"micromark-util-decode-numeric-character-reference@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-util-decode-numeric-character-reference@npm:1.0.0" +"micromark-util-decode-numeric-character-reference@npm:^2.0.0": + version: 2.0.2 + resolution: "micromark-util-decode-numeric-character-reference@npm:2.0.2" dependencies: - micromark-util-symbol: ^1.0.0 - checksum: f3ae2bb582a80f1e9d3face026f585c0c472335c064bd850bde152376f0394cb2831746749b6be6e0160f7d73626f67d10716026c04c87f402c0dd45a1a28633 + micromark-util-symbol: ^2.0.0 + checksum: ee11c8bde51e250e302050474c4a2adca094bca05c69f6cdd241af12df285c48c88d19ee6e022b9728281c280be16328904adca994605680c43af56019f4b0b6 languageName: node linkType: hard -"micromark-util-decode-string@npm:^1.0.0": - version: 1.0.2 - resolution: "micromark-util-decode-string@npm:1.0.2" +"micromark-util-decode-string@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-decode-string@npm:2.0.1" dependencies: decode-named-character-reference: ^1.0.0 - micromark-util-character: ^1.0.0 - micromark-util-decode-numeric-character-reference: ^1.0.0 - micromark-util-symbol: ^1.0.0 - checksum: 2dbb41c9691cc71505d39706405139fb7d6699429d577a524c7c248ac0cfd09d3dd212ad8e91c143a00b2896f26f81136edc67c5bda32d20446f0834d261b17a + micromark-util-character: ^2.0.0 + micromark-util-decode-numeric-character-reference: ^2.0.0 + micromark-util-symbol: ^2.0.0 + checksum: e9546ae53f9b5a4f9aa6aaf3e750087100d3429485ca80dbacec99ff2bb15a406fa7d93784a0fc2fe05ad7296b9295e75160ef71faec9e90110b7be2ae66241a languageName: node linkType: hard -"micromark-util-encode@npm:^1.0.0": - version: 1.0.1 - resolution: "micromark-util-encode@npm:1.0.1" - checksum: 9290583abfdc79ea3e7eb92c012c47a0e14327888f8aaa6f57ff79b3058d8e7743716b9d91abca3646f15ab3d78fdad9779fdb4ccf13349cd53309dfc845253a +"micromark-util-encode@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-encode@npm:2.0.1" + checksum: be890b98e78dd0cdd953a313f4148c4692cc2fb05533e56fef5f421287d3c08feee38ca679f318e740530791fc251bfe8c80efa926fcceb4419b269c9343d226 languageName: node linkType: hard -"micromark-util-events-to-acorn@npm:^1.0.0": - version: 1.2.1 - resolution: "micromark-util-events-to-acorn@npm:1.2.1" +"micromark-util-events-to-acorn@npm:^2.0.0": + version: 2.0.3 + resolution: "micromark-util-events-to-acorn@npm:2.0.3" dependencies: - "@types/acorn": ^4.0.0 "@types/estree": ^1.0.0 - estree-util-visit: ^1.0.0 - micromark-util-types: ^1.0.0 - uvu: ^0.5.0 - vfile-location: ^4.0.0 - vfile-message: ^3.0.0 - checksum: baf1cad66d860980cf20963f641c48c434e5be5802beabefdda21be136ae037845dd236b5e9ce5cf9409bf1b9ba8b4131a396d3a5bfa12098dae13e4a9724f2b + "@types/unist": ^3.0.0 + devlop: ^1.0.0 + estree-util-visit: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + vfile-message: ^4.0.0 + checksum: 8240f1aa072b3a2ec6df4fb55a0a19dd9f53923125a892da156e378b2af0333557f803f8da5228b03e5b1511c999701f0edbff9e483d00c5af5840f8466fb314 languageName: node linkType: hard -"micromark-util-html-tag-name@npm:^1.0.0": - version: 1.1.0 - resolution: "micromark-util-html-tag-name@npm:1.1.0" - checksum: a9b783cec89ec813648d59799464c1950fe281ae797b2a965f98ad0167d7fa1a247718eff023b4c015f47211a172f9446b8e6b98aad50e3cd44a3337317dad2c +"micromark-util-html-tag-name@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-html-tag-name@npm:2.0.1" + checksum: dea365f5ad28ad74ff29fcb581f7b74fc1f80271c5141b3b2bc91c454cbb6dfca753f28ae03730d657874fcbd89d0494d0e3965dfdca06d9855f467c576afa9d languageName: node linkType: hard -"micromark-util-normalize-identifier@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-util-normalize-identifier@npm:1.0.0" +"micromark-util-normalize-identifier@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-normalize-identifier@npm:2.0.1" dependencies: - micromark-util-symbol: ^1.0.0 - checksum: d7c09d5e8318fb72f194af72664bd84a48a2928e3550b2b21c8fbc0ec22524f2a72e0f6663d2b95dc189a6957d3d7759b60716e888909710767cd557be821f8b + micromark-util-symbol: ^2.0.0 + checksum: 1eb9a289d7da067323df9fdc78bfa90ca3207ad8fd893ca02f3133e973adcb3743b233393d23d95c84ccaf5d220ae7f5a28402a644f135dcd4b8cfa60a7b5f84 languageName: node linkType: hard -"micromark-util-resolve-all@npm:^1.0.0": - version: 1.0.0 - resolution: "micromark-util-resolve-all@npm:1.0.0" +"micromark-util-resolve-all@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-resolve-all@npm:2.0.1" dependencies: - micromark-util-types: ^1.0.0 - checksum: 409667f2bd126ef8acce009270d2aecaaa5584c5807672bc657b09e50aa91bd2e552cf41e5be1e6469244a83349cbb71daf6059b746b1c44e3f35446fef63e50 + micromark-util-types: ^2.0.0 + checksum: 9275f3ddb6c26f254dd2158e66215d050454b279707a7d9ce5a3cd0eba23201021cedcb78ae1a746c1b23227dcc418ee40dd074ade195359506797a5493550cc languageName: node linkType: hard -"micromark-util-sanitize-uri@npm:^1.0.0": - version: 1.1.0 - resolution: "micromark-util-sanitize-uri@npm:1.1.0" +"micromark-util-sanitize-uri@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-sanitize-uri@npm:2.0.1" dependencies: - micromark-util-character: ^1.0.0 - micromark-util-encode: ^1.0.0 - micromark-util-symbol: ^1.0.0 - checksum: fe6093faa0adeb8fad606184d927ce37f207dcc2ec7256438e7f273c8829686245dd6161b597913ef25a3c4fb61863d3612a40cb04cf15f83ba1b4087099996b + micromark-util-character: ^2.0.0 + micromark-util-encode: ^2.0.0 + micromark-util-symbol: ^2.0.0 + checksum: d01517840c17de67aaa0b0f03bfe05fac8a41d99723cd8ce16c62f6810e99cd3695364a34c335485018e5e2c00e69031744630a1b85c6868aa2f2ca1b36daa2f languageName: node linkType: hard -"micromark-util-subtokenize@npm:^1.0.0": - version: 1.0.2 - resolution: "micromark-util-subtokenize@npm:1.0.2" +"micromark-util-subtokenize@npm:^2.0.0": + version: 2.1.0 + resolution: "micromark-util-subtokenize@npm:2.1.0" dependencies: - micromark-util-chunked: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.0 - uvu: ^0.5.0 - checksum: c32ee58a7e1384ab1161a9ee02fbb04ad7b6e96d0b8c93dba9803c329a53d07f22ab394c7a96b2e30d6b8fbe3585b85817dba07277b1317111fc234e166bd2d1 + devlop: ^1.0.0 + micromark-util-chunked: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: 2e194bc8a5279d256582020500e5072a95c1094571be49043704343032e1fffbe09c862ef9c131cf5c762e296ddb54ff8bc767b3786a798524a68d1db6942934 languageName: node linkType: hard -"micromark-util-symbol@npm:^1.0.0": - version: 1.0.1 - resolution: "micromark-util-symbol@npm:1.0.1" - checksum: c6a3023b3a7432c15864b5e33a1bcb5042ac7aa097f2f452e587bef45433d42d39e0a5cce12fbea91e0671098ba0c3f62a2b30ce1cde66ecbb5e8336acf4391d +"micromark-util-symbol@npm:^2.0.0": + version: 2.0.1 + resolution: "micromark-util-symbol@npm:2.0.1" + checksum: fb7346950550bc85a55793dda94a8b3cb3abc068dbd7570d1162db7aee803411d06c0a5de4ae59cd945f46143bdeadd4bba02a02248fa0d18cc577babaa00044 languageName: node linkType: hard -"micromark-util-types@npm:^1.0.0, micromark-util-types@npm:^1.0.1": - version: 1.0.2 - resolution: "micromark-util-types@npm:1.0.2" - checksum: 08dc901b7c06ee3dfeb54befca05cbdab9525c1cf1c1080967c3878c9e72cb9856c7e8ff6112816e18ead36ce6f99d55aaa91560768f2f6417b415dcba1244df +"micromark-util-types@npm:^2.0.0": + version: 2.0.2 + resolution: "micromark-util-types@npm:2.0.2" + checksum: 884f7974839e4bc6d2bd662e57c973a9164fd5c0d8fe16cddf07472b86a7e6726747c00674952c0321d17685d700cd3295e9f58a842a53acdf6c6d55ab051aab languageName: node linkType: hard -"micromark@npm:^3.0.0": - version: 3.1.0 - resolution: "micromark@npm:3.1.0" +"micromark@npm:^4.0.0": + version: 4.0.2 + resolution: "micromark@npm:4.0.2" dependencies: "@types/debug": ^4.0.0 debug: ^4.0.0 decode-named-character-reference: ^1.0.0 - micromark-core-commonmark: ^1.0.1 - micromark-factory-space: ^1.0.0 - micromark-util-character: ^1.0.0 - micromark-util-chunked: ^1.0.0 - micromark-util-combine-extensions: ^1.0.0 - micromark-util-decode-numeric-character-reference: ^1.0.0 - micromark-util-encode: ^1.0.0 - micromark-util-normalize-identifier: ^1.0.0 - micromark-util-resolve-all: ^1.0.0 - micromark-util-sanitize-uri: ^1.0.0 - micromark-util-subtokenize: ^1.0.0 - micromark-util-symbol: ^1.0.0 - micromark-util-types: ^1.0.1 - uvu: ^0.5.0 - checksum: 5fe5bc3bf92e2ddd37b5f0034080fc3a4d4b3c1130dd5e435bb96ec75e9453091272852e71a4d74906a8fcf992d6f79d794607657c534bda49941e9950a92e28 - languageName: node - linkType: hard - -"micromark@npm:~2.11.0": - version: 2.11.4 - resolution: "micromark@npm:2.11.4" - dependencies: - debug: ^4.0.0 - parse-entities: ^2.0.0 - checksum: f8a5477d394908a5d770227aea71657a76423d420227c67ea0699e659a5f62eb39d504c1f7d69ec525a6af5aaeb6a7bffcdba95614968c03d41d3851edecb0d6 + devlop: ^1.0.0 + micromark-core-commonmark: ^2.0.0 + micromark-factory-space: ^2.0.0 + micromark-util-character: ^2.0.0 + micromark-util-chunked: ^2.0.0 + micromark-util-combine-extensions: ^2.0.0 + micromark-util-decode-numeric-character-reference: ^2.0.0 + micromark-util-encode: ^2.0.0 + micromark-util-normalize-identifier: ^2.0.0 + micromark-util-resolve-all: ^2.0.0 + micromark-util-sanitize-uri: ^2.0.0 + micromark-util-subtokenize: ^2.0.0 + micromark-util-symbol: ^2.0.0 + micromark-util-types: ^2.0.0 + checksum: 5306c15dd12f543755bc627fc361d4255dfc430e7af6069a07ac0eacc338fbd761fe8e93f02a8bfab6097bab12ee903192fe31389222459d5029242a5aaba3b8 languageName: node linkType: hard @@ -24881,7 +24569,7 @@ __metadata: languageName: node linkType: hard -"mri@npm:^1.1.0, mri@npm:^1.2.0": +"mri@npm:^1.2.0": version: 1.2.0 resolution: "mri@npm:1.2.0" checksum: 83f515abbcff60150873e424894a2f65d68037e5a7fcde8a9e2b285ee9c13ac581b63cfc1e6826c4732de3aeb84902f7c1e16b7aff46cd3f897a0f757a894e85 @@ -25344,14 +25032,14 @@ __metadata: languageName: node linkType: hard -"nopt@npm:^7.0.0": - version: 7.0.0 - resolution: "nopt@npm:7.0.0" +"nopt@npm:^7.2.1": + version: 7.2.1 + resolution: "nopt@npm:7.2.1" dependencies: abbrev: ^2.0.0 bin: nopt: bin/nopt.js - checksum: 71d296ea66a00e877ff8dd432d004ae6844f582d2ac203ce191c5e2873a878401093f13193f25352a749ae77d422fe2df1c168f55a720ee5285c85e7de3cb3a3 + checksum: 6fa729cc77ce4162cfad8abbc9ba31d4a0ff6850c3af61d59b505653bef4781ec059f8890ecfe93ee8aa0c511093369cca88bfc998101616a2904e715bbbb7c9 languageName: node linkType: hard @@ -25413,6 +25101,17 @@ __metadata: languageName: node linkType: hard +"normalize-package-data@npm:^6.0.0": + version: 6.0.2 + resolution: "normalize-package-data@npm:6.0.2" + dependencies: + hosted-git-info: ^7.0.0 + semver: ^7.3.5 + validate-npm-package-license: ^3.0.4 + checksum: ea35f8de68e03fc845f545c8197857c0cd256207fdb809ca63c2b39fe76ae77765ee939eb21811fb6c3b533296abf49ebe3cd617064f98a775adaccb24ff2e03 + languageName: node + linkType: hard + "normalize-path@npm:^2.1.1": version: 2.1.1 resolution: "normalize-path@npm:2.1.1" @@ -25454,6 +25153,15 @@ __metadata: languageName: node linkType: hard +"npm-install-checks@npm:^6.0.0": + version: 6.3.0 + resolution: "npm-install-checks@npm:6.3.0" + dependencies: + semver: ^7.1.1 + checksum: 6c20dadb878a0d2f1f777405217b6b63af1299d0b43e556af9363ee6eefaa98a17dfb7b612a473a473e96faf7e789c58b221e0d8ffdc1d34903c4f71618df3b4 + languageName: node + linkType: hard + "npm-install-checks@npm:^7.1.0": version: 7.1.2 resolution: "npm-install-checks@npm:7.1.2" @@ -25505,6 +25213,18 @@ __metadata: languageName: node linkType: hard +"npm-package-arg@npm:^11.0.0": + version: 11.0.3 + resolution: "npm-package-arg@npm:11.0.3" + dependencies: + hosted-git-info: ^7.0.0 + proc-log: ^4.0.0 + semver: ^7.3.5 + validate-npm-package-name: ^5.0.0 + checksum: cc6f22c39201aa14dcceeddb81bfbf7fa0484f94bcd2b3ad038e18afec5167c843cdde90c897f6034dc368faa0100c1eeee6e3f436a89e0af32ba932af4a8c28 + languageName: node + linkType: hard + "npm-package-arg@npm:^12.0.0": version: 12.0.2 resolution: "npm-package-arg@npm:12.0.2" @@ -25563,6 +25283,18 @@ __metadata: languageName: node linkType: hard +"npm-pick-manifest@npm:^9.0.0": + version: 9.1.0 + resolution: "npm-pick-manifest@npm:9.1.0" + dependencies: + npm-install-checks: ^6.0.0 + npm-normalize-package-bin: ^3.0.0 + npm-package-arg: ^11.0.0 + semver: ^7.3.5 + checksum: cbaad1e1420869efa851e8ba5d725263f679779e15bfca3713ec3ee1e897efab254e75c5445f442ffc96453cdfb15d362d25b0c0fcb03b156fe1653f9220cc40 + languageName: node + linkType: hard + "npm-registry-fetch@npm:19.1.0": version: 19.1.0 resolution: "npm-registry-fetch@npm:19.1.0" @@ -26533,20 +26265,6 @@ __metadata: languageName: node linkType: hard -"parse-entities@npm:^2.0.0": - version: 2.0.0 - resolution: "parse-entities@npm:2.0.0" - dependencies: - character-entities: ^1.0.0 - character-entities-legacy: ^1.0.0 - character-reference-invalid: ^1.0.0 - is-alphanumerical: ^1.0.0 - is-decimal: ^1.0.0 - is-hexadecimal: ^1.0.0 - checksum: 7addfd3e7d747521afac33c8121a5f23043c6973809756920d37e806639b4898385d386fcf4b3c8e2ecf1bc28aac5ae97df0b112d5042034efbe80f44081ebce - languageName: node - linkType: hard - "parse-entities@npm:^4.0.0": version: 4.0.1 resolution: "parse-entities@npm:4.0.1" @@ -26597,15 +26315,16 @@ __metadata: languageName: node linkType: hard -"parse-json@npm:^6.0.0": - version: 6.0.2 - resolution: "parse-json@npm:6.0.2" +"parse-json@npm:^7.0.0": + version: 7.1.1 + resolution: "parse-json@npm:7.1.1" dependencies: - "@babel/code-frame": ^7.16.0 + "@babel/code-frame": ^7.21.4 error-ex: ^1.3.2 - json-parse-even-better-errors: ^2.3.1 - lines-and-columns: ^2.0.2 - checksum: b33d93abf869f3102804896b9a1f8c04bf371e3c55d7afafaf18fca2813a20b2e14a1ae5c6823feea3b4fabc63f35984dc272fa057c4767531ffe1b46d52fa79 + json-parse-even-better-errors: ^3.0.0 + lines-and-columns: ^2.0.3 + type-fest: ^3.8.0 + checksum: 187275c7ac097dcfb3c7420bca2399caa4da33bcd5d5aac3604bda0e2b8eee4df61cc26aa0d79fab97f0d67bf42d41d332baa9f9f56ad27636ad785f1ae639e5 languageName: node linkType: hard @@ -26885,6 +26604,13 @@ __metadata: languageName: node linkType: hard +"picocolors@npm:^1.1.1": + version: 1.1.1 + resolution: "picocolors@npm:1.1.1" + checksum: e1cf46bf84886c79055fdfa9dcb3e4711ad259949e3565154b004b260cd356c5d54b31a1437ce9782624bf766272fe6b0154f5f0c744fb7af5d454d2b60db045 + languageName: node + linkType: hard + "picomatch@npm:^2.0.4, picomatch@npm:^2.0.5, picomatch@npm:^2.2.1": version: 2.2.2 resolution: "picomatch@npm:2.2.2" @@ -27799,10 +27525,10 @@ __metadata: languageName: node linkType: hard -"proc-log@npm:^3.0.0": - version: 3.0.0 - resolution: "proc-log@npm:3.0.0" - checksum: 02b64e1b3919e63df06f836b98d3af002b5cd92655cab18b5746e37374bfb73e03b84fe305454614b34c25b485cc687a9eebdccf0242cda8fda2475dd2c97e02 +"proc-log@npm:^4.0.0, proc-log@npm:^4.2.0": + version: 4.2.0 + resolution: "proc-log@npm:4.2.0" + checksum: 98f6cd012d54b5334144c5255ecb941ee171744f45fca8b43b58ae5a0c1af07352475f481cadd9848e7f0250376ee584f6aa0951a856ff8f021bdfbff4eb33fc languageName: node linkType: hard @@ -27937,15 +27663,6 @@ __metadata: languageName: node linkType: hard -"propose@npm:0.0.5": - version: 0.0.5 - resolution: "propose@npm:0.0.5" - dependencies: - levenshtein-edit-distance: ^1.0.0 - checksum: ad472212772dd0ef60cafa859f140176243bd318c989f76577d0bb3a18cb425c1db2aab30986e325946fdc130cb11fe3d34274220d29b3535de61427b0dd922b - languageName: node - linkType: hard - "protobufjs@npm:6.11.3, protobufjs@npm:^6.11.2, protobufjs@npm:^6.11.3, protobufjs@npm:^6.8.8, protobufjs@npm:~6.11.2, protobufjs@npm:~6.11.3": version: 6.11.3 resolution: "protobufjs@npm:6.11.3" @@ -28511,7 +28228,7 @@ __metadata: languageName: node linkType: hard -"read-package-json-fast@npm:^3.0.0, read-package-json-fast@npm:^3.0.2": +"read-package-json-fast@npm:^3.0.0": version: 3.0.2 resolution: "read-package-json-fast@npm:3.0.2" dependencies: @@ -28929,876 +28646,25 @@ __metadata: languageName: node linkType: hard -"remark-frontmatter@npm:^4.0.1": - version: 4.0.1 - resolution: "remark-frontmatter@npm:4.0.1" - dependencies: - "@types/mdast": ^3.0.0 - mdast-util-frontmatter: ^1.0.0 - micromark-extension-frontmatter: ^1.0.0 - unified: ^10.0.0 - checksum: c1c448923cd0239e9eeafb42d7129c05081c9a1bca4c8164b562cbb748e80d103bfd058597a48d54000ce3c776200ab8ccd64a9679d955423f07e4a4e77f10c3 - languageName: node - linkType: hard - -"remark-gfm@npm:^3.0.1": - version: 3.0.1 - resolution: "remark-gfm@npm:3.0.1" - dependencies: - "@types/mdast": ^3.0.0 - mdast-util-gfm: ^2.0.0 - micromark-extension-gfm: ^2.0.0 - unified: ^10.0.0 - checksum: 02254f74d67b3419c2c9cf62d799ec35f6c6cd74db25c001361751991552a7ce86049a972107bff8122d85d15ae4a8d1a0618f3bc01a7df837af021ae9b2a04e - languageName: node - linkType: hard - -"remark-lint-blockquote-indentation@npm:^3.0.0": - version: 3.1.1 - resolution: "remark-lint-blockquote-indentation@npm:3.1.1" - dependencies: - "@types/mdast": ^3.0.0 - pluralize: ^8.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-generated: ^2.0.0 - unist-util-position: ^4.0.0 - unist-util-visit: ^4.0.0 - checksum: 605636c4bf43ab98948e08302dedaf60aa6f16a9f40696b5685e99e22ef647b56415a3e7d803f20003ff1c57e310f784d9fc14e2c2caf78d4e17664a84f51696 - languageName: node - linkType: hard - -"remark-lint-checkbox-character-style@npm:^4.0.0": - version: 4.1.1 - resolution: "remark-lint-checkbox-character-style@npm:4.1.1" - dependencies: - "@types/mdast": ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-position: ^4.0.0 - unist-util-visit: ^4.0.0 - checksum: 7e35509f4bab21a697a0d4d8484b97c9c58cda775dda42a7f9133a1e60b8c966d32fc292e20c3c6b250b42103b0e1a8aa01db8210c34f2da87fcb4f988bcf767 - languageName: node - linkType: hard - -"remark-lint-code-block-style@npm:^3.0.0": - version: 3.1.0 - resolution: "remark-lint-code-block-style@npm:3.1.0" - dependencies: - "@types/mdast": ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-generated: ^2.0.0 - unist-util-position: ^4.0.0 - unist-util-visit: ^4.0.0 - checksum: 7fe60387a9f006928a7fc3265eaa4edda2b715d1ea5f3b6032fe75831e28631b3e7b563729b6093ddcae43e0356e19c22c0d373a748d4ed1d30d1d3b5eb5a339 - languageName: node - linkType: hard - -"remark-lint-definition-case@npm:^3.0.0": - version: 3.1.1 - resolution: "remark-lint-definition-case@npm:3.1.1" - dependencies: - "@types/mdast": ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-position: ^4.0.0 - unist-util-visit: ^4.0.0 - checksum: 00e19d692ae029b0e47afd1633c596108f948eff861c77134892aed05bd139aab3dfa858b1b55db3d865412369d0e2af1d9e1fed35cf4ed582dc94cf6977059f - languageName: node - linkType: hard - -"remark-lint-definition-spacing@npm:^3.0.0": - version: 3.1.1 - resolution: "remark-lint-definition-spacing@npm:3.1.1" - dependencies: - "@types/mdast": ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-position: ^4.0.0 - unist-util-visit: ^4.0.0 - checksum: 4d8ae38fd24225f0f89d2dddc3eab505ed6f2eea4683c10a8e398c89545b485d8fcb475204ccacf1a37c7305bdd2bca6a7383947e404de5b164b913cd12a42d2 - languageName: node - linkType: hard - -"remark-lint-emphasis-marker@npm:^3.0.0": - version: 3.1.1 - resolution: "remark-lint-emphasis-marker@npm:3.1.1" - dependencies: - "@types/mdast": ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-position: ^4.0.0 - unist-util-visit: ^4.0.0 - checksum: db84e912a84ba2b5717d53bccb2393f8d132760c54b28d6a9b842439592e564d7eafeb4dd49ab859d9bf31b1693893728c1e047a451477da8d3b12450babf7d3 - languageName: node - linkType: hard - -"remark-lint-fenced-code-flag@npm:^3.0.0": - version: 3.1.1 - resolution: "remark-lint-fenced-code-flag@npm:3.1.1" - dependencies: - "@types/mdast": ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-generated: ^2.0.0 - unist-util-position: ^4.0.0 - unist-util-visit: ^4.0.0 - checksum: 729b8fd83df0df4f61ac92e9f56a9ddddbe68ef88c54d24d982620bfd302d720789d887b2338d57a5318cffda4ddff2ae76b89a404c56eb17826c87cf419c3f7 - languageName: node - linkType: hard - -"remark-lint-fenced-code-marker@npm:^3.0.0": - version: 3.1.1 - resolution: "remark-lint-fenced-code-marker@npm:3.1.1" - dependencies: - "@types/mdast": ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-position: ^4.0.0 - unist-util-visit: ^4.0.0 - checksum: 7ce47fb1a3c457a52ef0044f619b9e6dbe8706a42c457eb56d063f75c6db8826253ac8dab5c4cb9cdcd89feb35a7997e30830a544fbca9e2b508923d743d2529 - languageName: node - linkType: hard - -"remark-lint-file-extension@npm:^2.0.0": - version: 2.1.1 - resolution: "remark-lint-file-extension@npm:2.1.1" - dependencies: - "@types/mdast": ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - checksum: 70bc12640e3d43bb81475f51d6156bbf0bb9829ce8a9ea26e893680d091f2f72ed9ac921364e1d654a628ec1f5666536a412a5e885f2c004e6d2857a04f7506e - languageName: node - linkType: hard - -"remark-lint-final-definition@npm:^3.0.0": - version: 3.1.1 - resolution: "remark-lint-final-definition@npm:3.1.1" - dependencies: - "@types/mdast": ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-generated: ^2.0.0 - unist-util-position: ^4.0.0 - unist-util-visit: ^4.0.0 - checksum: 2bf203f268923e2e91a6bb5443ccfa400cc4c3d33f476bc676d549ff9a29263ba33ff4cd319a63786a649883c112de32f898ee082cb3ba098088f961f016b5fe - languageName: node - linkType: hard - -"remark-lint-final-newline@npm:^2.0.0": - version: 2.1.1 - resolution: "remark-lint-final-newline@npm:2.1.1" - dependencies: - "@types/mdast": ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - checksum: d68291b1794dfa67d6b5cca1dd73ce3032dafd845efaf9e0514cf3361c9a495245fef975aaf72a987be8859bea22cd3b0b5b744cff7320d42ab1b67dae0cfe8d - languageName: node - linkType: hard - -"remark-lint-hard-break-spaces@npm:^3.0.0": - version: 3.1.1 - resolution: "remark-lint-hard-break-spaces@npm:3.1.1" - dependencies: - "@types/mdast": ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-generated: ^2.0.0 - unist-util-position: ^4.0.0 - unist-util-visit: ^4.0.0 - checksum: 8e84b0c66ef8aefedd0ff36f8ce7bf4db1434ab591f21184d8b922622d9ed2ed6cb8c8d612d4c409cfeed11701a704625f4787b51018ff3e6cb67f66e4f71d10 - languageName: node - linkType: hard - -"remark-lint-heading-increment@npm:^3.0.0": - version: 3.1.1 - resolution: "remark-lint-heading-increment@npm:3.1.1" - dependencies: - "@types/mdast": ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-generated: ^2.0.0 - unist-util-visit: ^4.0.0 - checksum: 2895b5849e6fbf9436451c637b39aea3db9083766d9bd0178300f18096be4c1c4022dc049ea044ed3f766c0314ef03937e38603c6fbe4fecedacd24b647be03b - languageName: node - linkType: hard - -"remark-lint-heading-style@npm:^3.0.0": - version: 3.1.1 - resolution: "remark-lint-heading-style@npm:3.1.1" - dependencies: - "@types/mdast": ^3.0.0 - mdast-util-heading-style: ^2.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-generated: ^2.0.0 - unist-util-visit: ^4.0.0 - checksum: fb5ee308a4b4d64e8039dfdfddf20406e5164002c245955b29d99facad2e21af911d8ef10b26640ab0d1e3efe3a657afc9affd42f9ae9aeefa79e781ffd4c571 - languageName: node - linkType: hard - -"remark-lint-link-title-style@npm:^3.0.0": - version: 3.1.1 - resolution: "remark-lint-link-title-style@npm:3.1.1" - dependencies: - "@types/mdast": ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-position: ^4.0.0 - unist-util-visit: ^4.0.0 - vfile-location: ^4.0.0 - checksum: 29b38865fb9378390ab8818d4e121b77315ec525275e9c5c249c7b2c55e3bc359a2f17aa200c3343ad6d0926795a48ec78dddd10b061cf23b4126727af5435ed - languageName: node - linkType: hard - -"remark-lint-list-item-bullet-indent@npm:^4.0.0": - version: 4.1.1 - resolution: "remark-lint-list-item-bullet-indent@npm:4.1.1" - dependencies: - "@types/mdast": ^3.0.0 - pluralize: ^8.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-visit: ^4.0.0 - checksum: 8248285196970ad5f84ab94db3cf64a6083012a07a06649acfa5bb4a22a8786221d67d6752d22a49ce14bd1e61dd69a51ee24f8277d05690843e24b89d482d09 - languageName: node - linkType: hard - -"remark-lint-list-item-content-indent@npm:^3.0.0": - version: 3.1.1 - resolution: "remark-lint-list-item-content-indent@npm:3.1.1" - dependencies: - "@types/mdast": ^3.0.0 - pluralize: ^8.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-position: ^4.0.0 - unist-util-visit: ^4.0.0 - checksum: ac1ea5f482af848050def5f550cbe0a42e610ce3cb200d54ab19d485fe923a80ee333e8cd005084ce0db5a834fce84b262547b43bb8f95f2c419ccee83ec7b0b - languageName: node - linkType: hard - -"remark-lint-list-item-indent@npm:^3.0.0": - version: 3.1.1 - resolution: "remark-lint-list-item-indent@npm:3.1.1" - dependencies: - "@types/mdast": ^3.0.0 - pluralize: ^8.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-generated: ^2.0.0 - unist-util-position: ^4.0.0 - unist-util-visit: ^4.0.0 - checksum: 955c670d1617e3193a328624225cf52f1c59a75b65045dac8a644909ac834f65ab02b7647996d7e6f6a98456527f1cb4f3d4fa8a3be67e5ce51500bc4940d2ff - languageName: node - linkType: hard - -"remark-lint-list-item-spacing@npm:^4.0.0": - version: 4.1.1 - resolution: "remark-lint-list-item-spacing@npm:4.1.1" - dependencies: - "@types/mdast": ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-generated: ^2.0.0 - unist-util-position: ^4.0.0 - unist-util-visit: ^4.0.0 - checksum: 1c75e05572741e4e9b3ad65beaea85bd226196959c683017cef56ccb927d0f34ed4cdb4bad0074b1145e9b7b0c29c713c7dcec73a48c6625f8e38a112cdfde56 - languageName: node - linkType: hard - -"remark-lint-maximum-heading-length@npm:^3.0.0": - version: 3.1.1 - resolution: "remark-lint-maximum-heading-length@npm:3.1.1" - dependencies: - "@types/mdast": ^3.0.0 - mdast-util-to-string: ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-generated: ^2.0.0 - unist-util-visit: ^4.0.0 - checksum: d477a436c07a410cb82600965a5f94c597398007f84fbcb9880db6f8b17fdb01194b394923a3d61c812402b38b3f58ce161216ee14b5a6e9ac8a02fac1b8d4f2 - languageName: node - linkType: hard - -"remark-lint-maximum-line-length@npm:^3.0.0": - version: 3.1.2 - resolution: "remark-lint-maximum-line-length@npm:3.1.2" - dependencies: - "@types/mdast": ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-generated: ^2.0.0 - unist-util-position: ^4.0.0 - unist-util-visit: ^4.0.0 - checksum: 1370014b718b8cc34f6a740d30bb91f404fb67b9cc8952297933e9f608cd75bcd72fc183ad686ab45aeedf1d4a37ad523d2768226d42b604e32a31edf0f99ab2 - languageName: node - linkType: hard - -"remark-lint-no-blockquote-without-marker@npm:^5.0.0": - version: 5.1.1 - resolution: "remark-lint-no-blockquote-without-marker@npm:5.1.1" - dependencies: - "@types/mdast": ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-generated: ^2.0.0 - unist-util-position: ^4.0.0 - unist-util-visit: ^4.0.0 - vfile-location: ^4.0.0 - checksum: 73d4583dbec06c206ed1ceeb754135117c478fa3c1e118c15446863910514ded3653f05f826b43f514728a6c0b3a6f26d463052fb8d2942c72df0c3b1a45be07 - languageName: node - linkType: hard - -"remark-lint-no-consecutive-blank-lines@npm:^4.0.0": - version: 4.1.2 - resolution: "remark-lint-no-consecutive-blank-lines@npm:4.1.2" - dependencies: - "@types/mdast": ^3.0.0 - "@types/unist": ^2.0.0 - pluralize: ^8.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-generated: ^2.0.0 - unist-util-position: ^4.0.0 - unist-util-visit: ^4.0.0 - checksum: 1c4d38ac356deb66a86c1f0d9a7b4c47630467b692ca82800d425177db9ed4c678fe553436228606b2d7804d37abae0370f1b75c5b5afc350d19e53375aa7cd2 - languageName: node - linkType: hard - -"remark-lint-no-duplicate-definitions@npm:^3.0.0": - version: 3.1.1 - resolution: "remark-lint-no-duplicate-definitions@npm:3.1.1" - dependencies: - "@types/mdast": ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-generated: ^2.0.0 - unist-util-position: ^4.0.0 - unist-util-stringify-position: ^3.0.0 - unist-util-visit: ^4.0.0 - checksum: a8de885eb64f521429ececfa72ea5d736c3a54c636c1d0c2c9aaf82c28cbb4c33a912d1caae5ae011635d36a40e550fb146be3d637e0968d573dbda465117a97 - languageName: node - linkType: hard - -"remark-lint-no-duplicate-headings-in-section@npm:^3.1.1": - version: 3.1.1 - resolution: "remark-lint-no-duplicate-headings-in-section@npm:3.1.1" - dependencies: - "@types/mdast": ^3.0.0 - mdast-util-to-string: ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-generated: ^2.0.0 - unist-util-position: ^4.0.0 - unist-util-stringify-position: ^3.0.0 - unist-util-visit: ^4.0.0 - checksum: cad91c4d75a36b370c511925c7aa34c9e528c07e11353d49a0cfec204422b4315b1b6ba8eef47e87e15e9e0e3cc039c57e22735d2156acf99d1e439312d837b8 - languageName: node - linkType: hard - -"remark-lint-no-duplicate-headings@npm:^3.0.0, remark-lint-no-duplicate-headings@npm:^3.1.1": - version: 3.1.1 - resolution: "remark-lint-no-duplicate-headings@npm:3.1.1" - dependencies: - "@types/mdast": ^3.0.0 - mdast-util-to-string: ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-generated: ^2.0.0 - unist-util-position: ^4.0.0 - unist-util-stringify-position: ^3.0.0 - unist-util-visit: ^4.0.0 - checksum: 81baa468d1e90664a0777041bde07483c3611e8d154989c9e411662be14eed6420b43b1f1308ef34907ddf9d5973639439487534cf34a42227cce430388eabef - languageName: node - linkType: hard - -"remark-lint-no-emphasis-as-heading@npm:^3.0.0": - version: 3.1.1 - resolution: "remark-lint-no-emphasis-as-heading@npm:3.1.1" - dependencies: - "@types/mdast": ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-generated: ^2.0.0 - unist-util-visit: ^4.0.0 - checksum: 751937ce6eb19ac57c0b654e5585a0e0bb67e36f3353030ec5720bde3357bdf9c6791aeca90c02ba336a5a84b18b51b708d9bc5ffa0f05f5696d83f21785ed28 - languageName: node - linkType: hard - -"remark-lint-no-file-name-articles@npm:^2.0.0": - version: 2.1.1 - resolution: "remark-lint-no-file-name-articles@npm:2.1.1" - dependencies: - "@types/mdast": ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - checksum: 9b23f833a4983900e4944521f4728eeb6a1ad26608fe91ec2e7b9d4c4571f9fb01961f08cf5c3e3f0e9ebe26d58afaca0301a28ce965d174d2c521d1bf50fe88 - languageName: node - linkType: hard - -"remark-lint-no-file-name-consecutive-dashes@npm:^2.0.0": - version: 2.1.1 - resolution: "remark-lint-no-file-name-consecutive-dashes@npm:2.1.1" - dependencies: - "@types/mdast": ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - checksum: 860d037031af9cbcad696776a0f62f60d8b38b853582980b414a3463c8753d88d24e02db48266e2ab63894266d6122ab8cd47ba09ec01f0382f3035f3ace66ec - languageName: node - linkType: hard - -"remark-lint-no-file-name-irregular-characters@npm:^2.0.0": - version: 2.1.1 - resolution: "remark-lint-no-file-name-irregular-characters@npm:2.1.1" - dependencies: - "@types/mdast": ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - checksum: 67a89de0022bcf6a4ed84b47f36eb5dc5c60f407dffe745a16c81c6f69687008665a7bad37ea6f21fffd777d7a7fd87d6607bec1a8982add7eeb7ef64ed144eb - languageName: node - linkType: hard - -"remark-lint-no-file-name-mixed-case@npm:^2.0.0": - version: 2.1.1 - resolution: "remark-lint-no-file-name-mixed-case@npm:2.1.1" - dependencies: - "@types/mdast": ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - checksum: 242641391e330c2f2229c606fbcaaf8cefc1aab4dc244d63cac328710af06c22eb5eda912cfff49b7b81f4fc0a617195fb16c4a958b187913dd1b9643012deb5 - languageName: node - linkType: hard - -"remark-lint-no-file-name-outer-dashes@npm:^2.0.0": - version: 2.1.1 - resolution: "remark-lint-no-file-name-outer-dashes@npm:2.1.1" - dependencies: - "@types/mdast": ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - checksum: 455ce51f8e90835a943f61797882103386623ecea102a9abf233009d068b35b68116b1c66487c7cd2acec28e200abdac0ff92c95ac271413a5e0d52ba8508959 - languageName: node - linkType: hard - -"remark-lint-no-heading-content-indent@npm:^4.0.0": - version: 4.1.1 - resolution: "remark-lint-no-heading-content-indent@npm:4.1.1" - dependencies: - "@types/mdast": ^3.0.0 - mdast-util-heading-style: ^2.0.0 - pluralize: ^8.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-generated: ^2.0.0 - unist-util-position: ^4.0.0 - unist-util-visit: ^4.0.0 - checksum: 086eaffdfae19116cda175b0a803c444350f86059e9486d7f1a6b30138d53ff6e0a06c6587942aeff5850b226974be768e244e4b8575fa7b0271b714049b2bfa - languageName: node - linkType: hard - -"remark-lint-no-heading-punctuation@npm:^3.0.0": - version: 3.1.1 - resolution: "remark-lint-no-heading-punctuation@npm:3.1.1" - dependencies: - "@types/mdast": ^3.0.0 - mdast-util-to-string: ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-generated: ^2.0.0 - unist-util-visit: ^4.0.0 - checksum: 9f15c8b0767c5b05538b0ad333e6b24c57680375ed15fe929ec7481fac709afd9af708fb369bef6c89d972cfb168fe0f7dcc2f128cbb96a7b9dfa17ccbec8bad - languageName: node - linkType: hard - -"remark-lint-no-inline-padding@npm:^4.0.0": - version: 4.1.1 - resolution: "remark-lint-no-inline-padding@npm:4.1.1" - dependencies: - "@types/mdast": ^3.0.0 - mdast-util-to-string: ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-generated: ^2.0.0 - unist-util-visit: ^4.0.0 - checksum: fab21dc8fb4418b1bab16bb1b80742dea44a1ce560d83d6dc6019990a22af6a339c533d1c01d6e04f006ebd5f2875928971bcdb70732c149a5b8deb6e59d17b4 - languageName: node - linkType: hard - -"remark-lint-no-literal-urls@npm:^3.0.0": - version: 3.1.1 - resolution: "remark-lint-no-literal-urls@npm:3.1.1" - dependencies: - "@types/mdast": ^3.0.0 - mdast-util-to-string: ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-generated: ^2.0.0 - unist-util-position: ^4.0.0 - unist-util-visit: ^4.0.0 - checksum: 305d0d24dbd14c0bd4bc843f2298eda92861fb1ce77f3079a1b89a52e620f35b52b12510f09006cc2e02bd356e0d80dec92d2615b09c5f6ef940c9116f70f499 - languageName: node - linkType: hard - -"remark-lint-no-multiple-toplevel-headings@npm:^3.0.0": - version: 3.1.1 - resolution: "remark-lint-no-multiple-toplevel-headings@npm:3.1.1" - dependencies: - "@types/mdast": ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-generated: ^2.0.0 - unist-util-position: ^4.0.0 - unist-util-stringify-position: ^3.0.0 - unist-util-visit: ^4.0.0 - checksum: dd462c590887bfa15c1551c832cfb7ce5db49645803751c695a14576d1d53c4ee2d83e18bc5549ec2b521e0eaf7804b313f15cf1ef2a614a8a2a26edbedd93d7 - languageName: node - linkType: hard - -"remark-lint-no-shell-dollars@npm:^3.0.0": - version: 3.1.1 - resolution: "remark-lint-no-shell-dollars@npm:3.1.1" - dependencies: - "@types/mdast": ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-generated: ^2.0.0 - unist-util-visit: ^4.0.0 - checksum: df675246299fb833151a3bf227902e85ca20c8990be5e80693ae4b2235cae5f913118940a2ceb11ee1233b4be7f4dac14ad25b445c2cf1d6f0b8246f9021fc65 - languageName: node - linkType: hard - -"remark-lint-no-shortcut-reference-image@npm:^3.0.0": - version: 3.1.1 - resolution: "remark-lint-no-shortcut-reference-image@npm:3.1.1" - dependencies: - "@types/mdast": ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-generated: ^2.0.0 - unist-util-visit: ^4.0.0 - checksum: 8ad48be913dac09b9c1f78c1a5b91b98684d1ae93e3d4a78aad19ec5ffaa17172114c2ccaf947b256a510e26e80d72255df859159f621b4e13bcac8003c2c59d - languageName: node - linkType: hard - -"remark-lint-no-shortcut-reference-link@npm:^3.0.0": - version: 3.1.1 - resolution: "remark-lint-no-shortcut-reference-link@npm:3.1.1" - dependencies: - "@types/mdast": ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-generated: ^2.0.0 - unist-util-visit: ^4.0.0 - checksum: c3b78bc0cbce6551dc47f4beda7e2e5c6a5f1f22e895d1d11865897c13f6105cd458a4b11a75576b503e2cff82b9d8de763326344323d6058a45b4b3114b018b - languageName: node - linkType: hard - -"remark-lint-no-table-indentation@npm:^4.0.0": - version: 4.1.1 - resolution: "remark-lint-no-table-indentation@npm:4.1.1" - dependencies: - "@types/mdast": ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-position: ^4.0.0 - unist-util-visit: ^4.0.0 - vfile-location: ^4.0.0 - checksum: d594b79ba404c244fa13b966008abe57e8db754ea253d963be3a5bd2ba0de207fb52e8ca9020aa0f898dcdeb67e576397d5f19ae3b67b7d35693ff9b1bbfb209 - languageName: node - linkType: hard - -"remark-lint-no-undefined-references@npm:^4.0.0": - version: 4.2.0 - resolution: "remark-lint-no-undefined-references@npm:4.2.0" - dependencies: - "@types/mdast": ^3.0.0 - micromark-util-normalize-identifier: ^1.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-generated: ^2.0.0 - unist-util-position: ^4.0.0 - unist-util-visit: ^4.0.0 - vfile-location: ^4.0.0 - checksum: effedeb3f06dab28f6d8e7dbb664d18215eff328b3ee2a29b6061d54f4a825b571003d976d6daa3d04df332c66755c31a78b62da61679c2cc164bb4cfcca2942 - languageName: node - linkType: hard - -"remark-lint-no-unused-definitions@npm:^3.0.0": +"remark-mdx@npm:^3.1.0": version: 3.1.1 - resolution: "remark-lint-no-unused-definitions@npm:3.1.1" + resolution: "remark-mdx@npm:3.1.1" dependencies: - "@types/mdast": ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-generated: ^2.0.0 - unist-util-visit: ^4.0.0 - checksum: 959e5d4e508c9265a846a3a23256f15f47a7f5bbed22f5c9494e190cd117ef3a44dbe429d2e53bf801d3bd1116549386be0c68c6ff4ee641d62fbb944fe775a1 + mdast-util-mdx: ^3.0.0 + micromark-extension-mdxjs: ^3.0.0 + checksum: 9e6406ba83e545b5232ce98de71c29ad5746c2d920eed070a2c58687412453875bad52dfdfaf21bee6de59d3a45fa84cf785b3111c5eb4822f29b67cf1dfec96 languageName: node linkType: hard -"remark-lint-ordered-list-marker-style@npm:^3.0.0": - version: 3.1.1 - resolution: "remark-lint-ordered-list-marker-style@npm:3.1.1" - dependencies: - "@types/mdast": ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-generated: ^2.0.0 - unist-util-position: ^4.0.0 - unist-util-visit: ^4.0.0 - checksum: b7437f7176772aae80732402c4d2d7e13e84c0fe41dbaebf0db157b15b634057cd424f723d0fe6be2af31965a970359a5f0fe9116f948d555d25b51dc141bbc6 - languageName: node - linkType: hard - -"remark-lint-ordered-list-marker-value@npm:^3.0.0": - version: 3.1.1 - resolution: "remark-lint-ordered-list-marker-value@npm:3.1.1" - dependencies: - "@types/mdast": ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-generated: ^2.0.0 - unist-util-position: ^4.0.0 - unist-util-visit: ^4.0.0 - checksum: 532c0733d07225e3fc312afe3662ea09f04b8b42eca872c5d962830cd6b8a54773222292f2224306de3cabf1a65f3023cd4bd45d77430314283b1a7332169177 - languageName: node - linkType: hard - -"remark-lint-rule-style@npm:^3.0.0": - version: 3.1.1 - resolution: "remark-lint-rule-style@npm:3.1.1" - dependencies: - "@types/mdast": ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-position: ^4.0.0 - unist-util-visit: ^4.0.0 - checksum: e2899cfd21c530043e31eb52154eacc1a7f046945848fe227b5061a959ed896c48d108a567d85aefb2b0b8b9613c35aff2ce7cbd0e3c63774f3a924bcdd11e37 - languageName: node - linkType: hard - -"remark-lint-strong-marker@npm:^3.0.0": - version: 3.1.1 - resolution: "remark-lint-strong-marker@npm:3.1.1" - dependencies: - "@types/mdast": ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-position: ^4.0.0 - unist-util-visit: ^4.0.0 - checksum: af837e372ead8d8090f5bfe45dcef5034704e2b8b9690c0b0fb10fed9a60809d21b402d00065a96bde9195172b87bb83f65228f4351b872b48e61c6d75d8cea9 - languageName: node - linkType: hard - -"remark-lint-table-cell-padding@npm:^4.0.0": - version: 4.1.2 - resolution: "remark-lint-table-cell-padding@npm:4.1.2" - dependencies: - "@types/mdast": ^3.0.0 - "@types/unist": ^2.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-position: ^4.0.0 - unist-util-visit: ^4.0.0 - checksum: 432e5c774ee1125caa93c0306b70b4f8f91e41c0a06c18454b1cd80bb22af063822529a77233983a26d2a9d8865e9a1727efb3739510cc92102da39aa0472ed3 - languageName: node - linkType: hard - -"remark-lint-table-pipe-alignment@npm:^3.0.0": - version: 3.1.1 - resolution: "remark-lint-table-pipe-alignment@npm:3.1.1" - dependencies: - "@types/mdast": ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-position: ^4.0.0 - unist-util-visit: ^4.0.0 - checksum: 0dfe58de4ea5676f07e5a7c8ccea89ce3906539e801c51541b8f488c251ef8cd44ab2e24e09a6d20ca7bac0ca0ebbd529ff71bafe2874a21a890ab01c47335e2 - languageName: node - linkType: hard - -"remark-lint-table-pipes@npm:^4.0.0": - version: 4.1.1 - resolution: "remark-lint-table-pipes@npm:4.1.1" - dependencies: - "@types/mdast": ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-position: ^4.0.0 - unist-util-visit: ^4.0.0 - checksum: 14c973a5ef4372d25c4807cf00236be832388dd93cdab655bc7f5db9e234955b7bde08775221eb1553525d1a08b57fcdc8714c7d43e0e37fd1cd560cdd4fd727 - languageName: node - linkType: hard - -"remark-lint-unordered-list-marker-style@npm:^3.0.0": - version: 3.1.1 - resolution: "remark-lint-unordered-list-marker-style@npm:3.1.1" - dependencies: - "@types/mdast": ^3.0.0 - unified: ^10.0.0 - unified-lint-rule: ^2.0.0 - unist-util-generated: ^2.0.0 - unist-util-position: ^4.0.0 - unist-util-visit: ^4.0.0 - checksum: 226f572a00dc2f29ca0e085a12e0490a7e1d9e6508aa02f42d8b6c35be4b880f9d915aca2e2a72b92e96ac3c165a83810ca6599f386df7ec5ec999c74dbf0e07 - languageName: node - linkType: hard - -"remark-lint@npm:^9.0.0, remark-lint@npm:^9.1.1": - version: 9.1.1 - resolution: "remark-lint@npm:9.1.1" - dependencies: - "@types/mdast": ^3.0.0 - remark-message-control: ^7.0.0 - unified: ^10.1.0 - checksum: c5a2ca78fd9fca028cfd178b07782c4be543b56572e60bdc66032485ef5f043f7090a6026d1e4c7f56001af3783fde4d4c93e2f0035ba2e2dc60ecb51d898b17 - languageName: node - linkType: hard - -"remark-mdx@npm:^2.1.3": - version: 2.3.0 - resolution: "remark-mdx@npm:2.3.0" - dependencies: - mdast-util-mdx: ^2.0.0 - micromark-extension-mdxjs: ^1.0.0 - checksum: 98486986c5b6f6a8321eb2f3b13c70fcd5644821428c77b7bfeb5ee5d4605b9761b322b2f6b531e83883cd2d5bc7bc4623427149aee00e1eba012f538b3d5627 - languageName: node - linkType: hard - -"remark-message-control@npm:^7.0.0": - version: 7.1.1 - resolution: "remark-message-control@npm:7.1.1" - dependencies: - "@types/mdast": ^3.0.0 - mdast-comment-marker: ^2.0.0 - unified: ^10.0.0 - unified-message-control: ^4.0.0 - vfile: ^5.0.0 - checksum: ac6058e93b07c9cb46a828e89b6f372063b76f4c41283358c2913c10e69076cba35401189c0a0eb2a60b7c31714dd30430e770346b2a0ac8ba6b81572345fdd9 - languageName: node - linkType: hard - -"remark-parse@npm:^10.0.1": - version: 10.0.1 - resolution: "remark-parse@npm:10.0.1" +"remark-parse@npm:^11.0.0": + version: 11.0.0 + resolution: "remark-parse@npm:11.0.0" dependencies: - "@types/mdast": ^3.0.0 - mdast-util-from-markdown: ^1.0.0 - unified: ^10.0.0 - checksum: 505088e564ab53ff054433368adbb7b551f69240c7d9768975529837a86f1d0f085e72d6211929c5c42db315273df4afc94f3d3a8662ffdb69468534c6643d29 - languageName: node - linkType: hard - -"remark-preset-lint-consistent@npm:^5.1.1": - version: 5.1.1 - resolution: "remark-preset-lint-consistent@npm:5.1.1" - dependencies: - "@types/mdast": ^3.0.0 - remark-lint: ^9.0.0 - remark-lint-blockquote-indentation: ^3.0.0 - remark-lint-checkbox-character-style: ^4.0.0 - remark-lint-code-block-style: ^3.0.0 - remark-lint-emphasis-marker: ^3.0.0 - remark-lint-fenced-code-marker: ^3.0.0 - remark-lint-heading-style: ^3.0.0 - remark-lint-link-title-style: ^3.0.0 - remark-lint-list-item-content-indent: ^3.0.0 - remark-lint-ordered-list-marker-style: ^3.0.0 - remark-lint-rule-style: ^3.0.0 - remark-lint-strong-marker: ^3.0.0 - remark-lint-table-cell-padding: ^4.0.0 - unified: ^10.0.0 - checksum: ee4b70662e7b6686a18940df6f3886bac58a94e80a048df2b9e79d4e155460556a5be16a6f211c72fd241fb8d081f17a0c04ab6628f1058166a62eb89a4ed3b4 - languageName: node - linkType: hard - -"remark-preset-lint-markdown-style-guide@npm:^5.1.2": - version: 5.1.2 - resolution: "remark-preset-lint-markdown-style-guide@npm:5.1.2" - dependencies: - "@types/mdast": ^3.0.0 - remark-lint: ^9.0.0 - remark-lint-blockquote-indentation: ^3.0.0 - remark-lint-code-block-style: ^3.0.0 - remark-lint-definition-case: ^3.0.0 - remark-lint-definition-spacing: ^3.0.0 - remark-lint-emphasis-marker: ^3.0.0 - remark-lint-fenced-code-flag: ^3.0.0 - remark-lint-fenced-code-marker: ^3.0.0 - remark-lint-file-extension: ^2.0.0 - remark-lint-final-definition: ^3.0.0 - remark-lint-hard-break-spaces: ^3.0.0 - remark-lint-heading-increment: ^3.0.0 - remark-lint-heading-style: ^3.0.0 - remark-lint-link-title-style: ^3.0.0 - remark-lint-list-item-content-indent: ^3.0.0 - remark-lint-list-item-indent: ^3.0.0 - remark-lint-list-item-spacing: ^4.0.0 - remark-lint-maximum-heading-length: ^3.0.0 - remark-lint-maximum-line-length: ^3.0.0 - remark-lint-no-blockquote-without-marker: ^5.0.0 - remark-lint-no-consecutive-blank-lines: ^4.0.0 - remark-lint-no-duplicate-headings: ^3.0.0 - remark-lint-no-emphasis-as-heading: ^3.0.0 - remark-lint-no-file-name-articles: ^2.0.0 - remark-lint-no-file-name-consecutive-dashes: ^2.0.0 - remark-lint-no-file-name-irregular-characters: ^2.0.0 - remark-lint-no-file-name-mixed-case: ^2.0.0 - remark-lint-no-file-name-outer-dashes: ^2.0.0 - remark-lint-no-heading-punctuation: ^3.0.0 - remark-lint-no-inline-padding: ^4.0.0 - remark-lint-no-literal-urls: ^3.0.0 - remark-lint-no-multiple-toplevel-headings: ^3.0.0 - remark-lint-no-shell-dollars: ^3.0.0 - remark-lint-no-shortcut-reference-image: ^3.0.0 - remark-lint-no-shortcut-reference-link: ^3.0.0 - remark-lint-no-table-indentation: ^4.0.0 - remark-lint-ordered-list-marker-style: ^3.0.0 - remark-lint-ordered-list-marker-value: ^3.0.0 - remark-lint-rule-style: ^3.0.0 - remark-lint-strong-marker: ^3.0.0 - remark-lint-table-cell-padding: ^4.0.0 - remark-lint-table-pipe-alignment: ^3.0.0 - remark-lint-table-pipes: ^4.0.0 - remark-lint-unordered-list-marker-style: ^3.0.0 - unified: ^10.0.0 - checksum: 795f9d3a6e406f8d449afadaf14d584e3680e3e89d00706b02ba7985efddb1b2243aa73ea668ee8eb53d9987f00d92511039c91dc359bc49a3850329a9e11c5a - languageName: node - linkType: hard - -"remark-preset-lint-recommended@npm:^6.1.2": - version: 6.1.2 - resolution: "remark-preset-lint-recommended@npm:6.1.2" - dependencies: - "@types/mdast": ^3.0.0 - remark-lint: ^9.0.0 - remark-lint-final-newline: ^2.0.0 - remark-lint-hard-break-spaces: ^3.0.0 - remark-lint-list-item-bullet-indent: ^4.0.0 - remark-lint-list-item-indent: ^3.0.0 - remark-lint-no-blockquote-without-marker: ^5.0.0 - remark-lint-no-duplicate-definitions: ^3.0.0 - remark-lint-no-heading-content-indent: ^4.0.0 - remark-lint-no-inline-padding: ^4.0.0 - remark-lint-no-literal-urls: ^3.0.0 - remark-lint-no-shortcut-reference-image: ^3.0.0 - remark-lint-no-shortcut-reference-link: ^3.0.0 - remark-lint-no-undefined-references: ^4.0.0 - remark-lint-no-unused-definitions: ^3.0.0 - remark-lint-ordered-list-marker-style: ^3.0.0 - unified: ^10.0.0 - checksum: 8f92dab9648ed8030b6fa9b855b849d6e95536535b24c5622185f8134bba2131d69e4e496f8ca60ff7ff38e4bc1d2e755973cf6f11bb78eb78e975fb3f91e6c6 - languageName: node - linkType: hard - -"remark-preset-prettier@npm:^2.0.1": - version: 2.0.1 - resolution: "remark-preset-prettier@npm:2.0.1" - peerDependencies: - prettier: ">=1.0.0" - checksum: 4c2bfbd902a81e4f5e5b68c4e75b43af402b3fc9049fd65023a6818be036b7f48f9d6f1a1e4b2cf354cb639c6f1a0a11c597b3e33eb2e1f93ce9a3f24a8c95b7 + "@types/mdast": ^4.0.0 + mdast-util-from-markdown: ^2.0.0 + micromark-util-types: ^2.0.0 + unified: ^11.0.0 + checksum: d83d245290fa84bb04fb3e78111f09c74f7417e7c012a64dd8dc04fccc3699036d828fbd8eeec8944f774b6c30cc1d925c98f8c46495ebcee7c595496342ab7f languageName: node linkType: hard @@ -29813,33 +28679,14 @@ __metadata: languageName: node linkType: hard -"remark-stringify@npm:^10.0.2": - version: 10.0.2 - resolution: "remark-stringify@npm:10.0.2" - dependencies: - "@types/mdast": ^3.0.0 - mdast-util-to-markdown: ^1.0.0 - unified: ^10.0.0 - checksum: 25424201e698353f6c0afc9ec29a8cac1dac8c06750d92214e3216bd76ac37902a0ba3702ac2a11c1040938a667b3bc22d6949af1d35e8fc81a3572643cdc263 - languageName: node - linkType: hard - -"remark-validate-links@npm:^12.1.0": - version: 12.1.0 - resolution: "remark-validate-links@npm:12.1.0" +"remark-stringify@npm:^11.0.0": + version: 11.0.0 + resolution: "remark-stringify@npm:11.0.0" dependencies: - "@types/mdast": ^3.0.0 - github-slugger: ^1.0.0 - hosted-git-info: ^5.0.0 - mdast-util-to-string: ^3.0.0 - propose: 0.0.5 - to-vfile: ^7.0.0 - trough: ^2.0.0 - unified: ^10.0.0 - unified-engine: ^10.0.1 - unist-util-visit: ^4.0.0 - vfile: ^5.0.0 - checksum: eb1743c0f4e9ca54a7da58f97d70e97393e10a5a7573e23559b6f4ac6ee8a98db6983cfd4bbf1bb9393966e002000943c20e2203d8364283f02b5f3dfdad48ad + "@types/mdast": ^4.0.0 + mdast-util-to-markdown: ^2.0.0 + unified: ^11.0.0 + checksum: 59e07460eb629d6c3b3c0f438b0b236e7e6858fd5ab770303078f5a556ec00354d9c7fb9ef6d5f745a4617ac7da1ab618b170fbb4dac120e183fecd9cc86bce6 languageName: node linkType: hard @@ -30271,15 +29118,6 @@ __metadata: languageName: node linkType: hard -"sade@npm:^1.7.3": - version: 1.8.1 - resolution: "sade@npm:1.8.1" - dependencies: - mri: ^1.1.0 - checksum: 0756e5b04c51ccdc8221ebffd1548d0ce5a783a44a0fa9017a026659b97d632913e78f7dca59f2496aa996a0be0b0c322afd87ca72ccd909406f49dbffa0f45d - languageName: node - linkType: hard - "safe-buffer@npm:5.1.2, safe-buffer@npm:~5.1.0, safe-buffer@npm:~5.1.1": version: 5.1.2 resolution: "safe-buffer@npm:5.1.2" @@ -31581,7 +30419,7 @@ __metadata: languageName: node linkType: hard -"string-width@npm:^5.0.0, string-width@npm:^5.0.1, string-width@npm:^5.1.2": +"string-width@npm:^5.0.1, string-width@npm:^5.1.2": version: 5.1.2 resolution: "string-width@npm:5.1.2" dependencies: @@ -31592,6 +30430,17 @@ __metadata: languageName: node linkType: hard +"string-width@npm:^6.0.0": + version: 6.1.0 + resolution: "string-width@npm:6.1.0" + dependencies: + eastasianwidth: ^0.2.0 + emoji-regex: ^10.2.1 + strip-ansi: ^7.0.1 + checksum: 8aefb456a230c8d7fe254049b1b2d62603da1a3b6c7fc9f3332f6779583cc1c72653f9b6e4cd0c1c92befee1565d4a0a7542d09ba4ceb6d96af02fbd8425bb03 + languageName: node + linkType: hard + "string.prototype.matchall@npm:^4.0.8": version: 4.0.8 resolution: "string.prototype.matchall@npm:4.0.8" @@ -32001,13 +30850,12 @@ __metadata: languageName: node linkType: hard -"synckit@npm:^0.8.4": - version: 0.8.5 - resolution: "synckit@npm:0.8.5" +"synckit@npm:^0.11.8": + version: 0.11.12 + resolution: "synckit@npm:0.11.12" dependencies: - "@pkgr/utils": ^2.3.1 - tslib: ^2.5.0 - checksum: 8a9560e5d8f3d94dc3cf5f7b9c83490ffa30d320093560a37b88f59483040771fd1750e76b9939abfbb1b5a23fd6dfbae77f6b338abffe7cae7329cd9b9bb86b + "@pkgr/core": ^0.2.9 + checksum: a53fb563d01ba8912a111b883fc3c701e267896ff8273e7aba9001f5f74711e125888f4039e93060795cd416122cf492ae419eb10a6a3e3b00e830917669d2cf languageName: node linkType: hard @@ -32336,16 +31184,6 @@ __metadata: languageName: node linkType: hard -"tiny-glob@npm:^0.2.9": - version: 0.2.9 - resolution: "tiny-glob@npm:0.2.9" - dependencies: - globalyzer: 0.1.0 - globrex: ^0.1.2 - checksum: aea5801eb6663ddf77ebb74900b8f8bd9dfcfc9b6a1cc8018cb7421590c00bf446109ff45e4b64a98e6c95ddb1255a337a5d488fb6311930e2a95334151ec9c6 - languageName: node - linkType: hard - "tiny-inflate@npm:^1.0.0": version: 1.0.3 resolution: "tiny-inflate@npm:1.0.3" @@ -32469,16 +31307,6 @@ __metadata: languageName: node linkType: hard -"to-vfile@npm:^7.0.0": - version: 7.2.4 - resolution: "to-vfile@npm:7.2.4" - dependencies: - is-buffer: ^2.0.0 - vfile: ^5.1.0 - checksum: 5fa9bd6c3b0dae6abc65a882bef4d899940305237449740ee803c5ef87c1df56425290009abde7d215884f481c3254d78d19120f38109217c6c066676f2e7609 - languageName: node - linkType: hard - "tocbot@npm:^4.20.1": version: 4.27.16 resolution: "tocbot@npm:4.27.16" @@ -32729,7 +31557,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.4.0, tslib@npm:^2.5.0": +"tslib@npm:^2.4.0": version: 2.5.0 resolution: "tslib@npm:2.5.0" checksum: ae3ed5f9ce29932d049908ebfdf21b3a003a85653a9a140d614da6b767a93ef94f460e52c3d787f0e4f383546981713f165037dc2274df212ea9f8a4541004e1 @@ -32865,6 +31693,13 @@ __metadata: languageName: node linkType: hard +"type-fest@npm:^3.8.0": + version: 3.13.1 + resolution: "type-fest@npm:3.13.1" + checksum: c06b0901d54391dc46de3802375f5579868949d71f93b425ce564e19a428a0d411ae8d8cb0e300d330071d86152c3ea86e744c3f2860a42a79585b6ec2fdae8e + languageName: node + linkType: hard + "type-is@npm:~1.6.18": version: 1.6.18 resolution: "type-is@npm:1.6.18" @@ -33102,74 +31937,47 @@ __metadata: languageName: node linkType: hard -"unified-engine@npm:^10.0.1": - version: 10.1.0 - resolution: "unified-engine@npm:10.1.0" +"unified-engine@npm:^11.2.2": + version: 11.2.2 + resolution: "unified-engine@npm:11.2.2" dependencies: "@types/concat-stream": ^2.0.0 "@types/debug": ^4.0.0 "@types/is-empty": ^1.0.0 - "@types/node": ^18.0.0 - "@types/unist": ^2.0.0 + "@types/node": ^22.0.0 + "@types/unist": ^3.0.0 concat-stream: ^2.0.0 debug: ^4.0.0 - fault: ^2.0.0 - glob: ^8.0.0 - ignore: ^5.0.0 - is-buffer: ^2.0.0 + extend: ^3.0.0 + glob: ^10.0.0 + ignore: ^6.0.0 is-empty: ^1.0.0 is-plain-obj: ^4.0.0 - load-plugin: ^5.0.0 - parse-json: ^6.0.0 - to-vfile: ^7.0.0 + load-plugin: ^6.0.0 + parse-json: ^7.0.0 trough: ^2.0.0 - unist-util-inspect: ^7.0.0 - vfile-message: ^3.0.0 - vfile-reporter: ^7.0.0 - vfile-statistics: ^2.0.0 + unist-util-inspect: ^8.0.0 + vfile: ^6.0.0 + vfile-message: ^4.0.0 + vfile-reporter: ^8.0.0 + vfile-statistics: ^3.0.0 yaml: ^2.0.0 - checksum: 27f4e5cd05c70a0f8a0ffa011f20257d97d62dc1b7ced0fa3c70516f23a4e8e9b676496e94a04726c85da2783153412f526724e512ec1dddcf5af82ce39b2fd2 - languageName: node - linkType: hard - -"unified-lint-rule@npm:^2.0.0": - version: 2.1.1 - resolution: "unified-lint-rule@npm:2.1.1" - dependencies: - "@types/unist": ^2.0.0 - trough: ^2.0.0 - unified: ^10.0.0 - vfile: ^5.0.0 - checksum: 224cd0a89396d560674d7caac8cc9b52e89912954e83a4b940efadb3c41b34e5f6602c0d79f801a18666cf01427b1c6a09cf50abeb0f2b84f5c9d3b647885435 - languageName: node - linkType: hard - -"unified-message-control@npm:^4.0.0": - version: 4.0.0 - resolution: "unified-message-control@npm:4.0.0" - dependencies: - "@types/unist": ^2.0.0 - unist-util-is: ^5.0.0 - unist-util-visit: ^3.0.0 - vfile: ^5.0.0 - vfile-location: ^4.0.0 - vfile-message: ^3.0.0 - checksum: a90a9f8c371fce8679162dcf917f82ed4562518ad37e12778cabc9cb02500d504cd29d2500f37d822fd450ea6ddfa5f7be82cdba6c1f221c53288d1f0f25fd2c + checksum: 294d56b57293b315bf879458bc5897917033cb5bf586b3d412bae33dfe0a87d6406787b84f2b09c668badc9d264500279e55abf9ed14d2e9b2582f26963b3a45 languageName: node linkType: hard -"unified@npm:^10.0.0, unified@npm:^10.1.0, unified@npm:^10.1.2": - version: 10.1.2 - resolution: "unified@npm:10.1.2" +"unified@npm:^11.0.0, unified@npm:^11.0.5": + version: 11.0.5 + resolution: "unified@npm:11.0.5" dependencies: - "@types/unist": ^2.0.0 + "@types/unist": ^3.0.0 bail: ^2.0.0 + devlop: ^1.0.0 extend: ^3.0.0 - is-buffer: ^2.0.0 is-plain-obj: ^4.0.0 trough: ^2.0.0 - vfile: ^5.0.0 - checksum: 053e7c65ede644607f87bd625a299e4b709869d2f76ec8138569e6e886903b6988b21cd9699e471eda42bee189527be0a9dac05936f1d069a5e65d0125d5d756 + vfile: ^6.0.0 + checksum: b3bf7fd6f568cc261e074dae21188483b0f2a8ab858d62e6e85b75b96cc655f59532906ae3c64d56a9b257408722d71f1d4135292b3d7ee02907c8b592fb3cf0 languageName: node linkType: hard @@ -33262,19 +32070,12 @@ __metadata: languageName: node linkType: hard -"unist-util-generated@npm:^2.0.0": - version: 2.0.1 - resolution: "unist-util-generated@npm:2.0.1" - checksum: 6221ad0571dcc9c8964d6b054f39ef6571ed59cc0ce3e88ae97ea1c70afe76b46412a5ffaa91f96814644ac8477e23fb1b477d71f8d70e625728c5258f5c0d99 - languageName: node - linkType: hard - -"unist-util-inspect@npm:^7.0.0": - version: 7.0.2 - resolution: "unist-util-inspect@npm:7.0.2" +"unist-util-inspect@npm:^8.0.0": + version: 8.1.0 + resolution: "unist-util-inspect@npm:8.1.0" dependencies: - "@types/unist": ^2.0.0 - checksum: e8f2a3836516e5ac973d56914832fad83c2391686143008a40fa8c852eb452f04bd5a42c30ce716c52217b202328ca2f365c7f0f13e67f838603d659c39b9720 + "@types/unist": ^3.0.0 + checksum: 2c943aabebdcc3245be42fef4944b4511fb9b65d8ced1d77621da124ab18659abb37d5c33e85847baf3f2f30be8365499c064f86a014abaaf26aadd0e063baa8 languageName: node linkType: hard @@ -33285,56 +32086,30 @@ __metadata: languageName: node linkType: hard -"unist-util-is@npm:^5.0.0": - version: 5.2.0 - resolution: "unist-util-is@npm:5.2.0" - checksum: b80debe1ce5d40a8d685c510f597e5c8b8f7089540e9e268bda1b05bcce735c10bf36d5b0e4ecded50c63fa43b8a11b0e4b784beecf1559f153a2f2855e8526c - languageName: node - linkType: hard - -"unist-util-position-from-estree@npm:^1.0.0, unist-util-position-from-estree@npm:^1.1.0": - version: 1.1.2 - resolution: "unist-util-position-from-estree@npm:1.1.2" - dependencies: - "@types/unist": ^2.0.0 - checksum: e3f4060e2a9e894c6ed63489c5a7cb58ff282e5dae9497cbc2073033ca74d6e412af4d4d342c97aea08d997c908b8bce2fe43a2062aafc2bb3f266533016588b - languageName: node - linkType: hard - -"unist-util-position@npm:^4.0.0": - version: 4.0.4 - resolution: "unist-util-position@npm:4.0.4" - dependencies: - "@types/unist": ^2.0.0 - checksum: e7487b6cec9365299695e3379ded270a1717074fa11fd2407c9b934fb08db6fe1d9077ddeaf877ecf1813665f8ccded5171693d3d9a7a01a125ec5cdd5e88691 - languageName: node - linkType: hard - -"unist-util-remove-position@npm:^4.0.0": - version: 4.0.2 - resolution: "unist-util-remove-position@npm:4.0.2" +"unist-util-is@npm:^6.0.0": + version: 6.0.1 + resolution: "unist-util-is@npm:6.0.1" dependencies: - "@types/unist": ^2.0.0 - unist-util-visit: ^4.0.0 - checksum: 989831da913d09a82a99ed9b47b78471b6409bde95942cde47e09da54b7736516f17e3c7e026af468684c1efcec5fb52df363381b2f9dc7fd96ce791c5a2fa4a + "@types/unist": ^3.0.0 + checksum: e57733e1766b55c9a873a42d2f34daa211580788b1bba26af2fc22e48e147bdcff0f9a752ed2a19238864823735fbbe27a1804d6a5a22b182c23aa0191e41c12 languageName: node linkType: hard -"unist-util-stringify-position@npm:^2.0.0": - version: 2.0.3 - resolution: "unist-util-stringify-position@npm:2.0.3" +"unist-util-position-from-estree@npm:^2.0.0": + version: 2.0.0 + resolution: "unist-util-position-from-estree@npm:2.0.0" dependencies: - "@types/unist": ^2.0.2 - checksum: f755cadc959f9074fe999578a1a242761296705a7fe87f333a37c00044de74ab4b184b3812989a57d4cd12211f0b14ad397b327c3a594c7af84361b1c25a7f09 + "@types/unist": ^3.0.0 + checksum: d3b3048a5727c2367f64ef6dcc5b20c4717215ef8b1372ff9a7c426297c5d1e5776409938acd01531213e2cd2543218d16e73f9f862f318e9496e2c73bb18354 languageName: node linkType: hard -"unist-util-stringify-position@npm:^3.0.0": - version: 3.0.3 - resolution: "unist-util-stringify-position@npm:3.0.3" +"unist-util-stringify-position@npm:^4.0.0": + version: 4.0.0 + resolution: "unist-util-stringify-position@npm:4.0.0" dependencies: - "@types/unist": ^2.0.0 - checksum: dbd66c15183607ca942a2b1b7a9f6a5996f91c0d30cf8966fb88955a02349d9eefd3974e9010ee67e71175d784c5a9fea915b0aa0b0df99dcb921b95c4c9e124 + "@types/unist": ^3.0.0 + checksum: e2e7aee4b92ddb64d314b4ac89eef7a46e4c829cbd3ee4aee516d100772b490eb6b4974f653ba0717a0071ca6ea0770bf22b0a2ea62c65fcba1d071285e96324 languageName: node linkType: hard @@ -33348,23 +32123,13 @@ __metadata: languageName: node linkType: hard -"unist-util-visit-parents@npm:^4.0.0": - version: 4.1.1 - resolution: "unist-util-visit-parents@npm:4.1.1" - dependencies: - "@types/unist": ^2.0.0 - unist-util-is: ^5.0.0 - checksum: 49d78984a6dd858a989f849d2b4330c8a04d1ee99c0e9920a5e37668cf847dab95db77a3bf0c8aaeb3e66abeae12e2d454949ec401614efef377d8f82d215662 - languageName: node - linkType: hard - -"unist-util-visit-parents@npm:^5.0.0, unist-util-visit-parents@npm:^5.1.1": - version: 5.1.3 - resolution: "unist-util-visit-parents@npm:5.1.3" +"unist-util-visit-parents@npm:^6.0.0": + version: 6.0.2 + resolution: "unist-util-visit-parents@npm:6.0.2" dependencies: - "@types/unist": ^2.0.0 - unist-util-is: ^5.0.0 - checksum: 8ecada5978994f846b64658cf13b4092cd78dea39e1ba2f5090a5de842ba4852712c02351a8ae95250c64f864635e7b02aedf3b4a093552bb30cf1bd160efbaa + "@types/unist": ^3.0.0 + unist-util-is: ^6.0.0 + checksum: cf28578a6f0b81877965e261fe82460f83b8c3a9cab3b2080c046b215f3223c6195b01064256619ca3411a1930face93a1a2a72d34d8716e684d6cd59f53cd9a languageName: node linkType: hard @@ -33379,25 +32144,14 @@ __metadata: languageName: node linkType: hard -"unist-util-visit@npm:^3.0.0": - version: 3.1.0 - resolution: "unist-util-visit@npm:3.1.0" - dependencies: - "@types/unist": ^2.0.0 - unist-util-is: ^5.0.0 - unist-util-visit-parents: ^4.0.0 - checksum: c37dbc0c5509f85f3abdf46d927b3dd11e6c419159771b1f1a5ce446d36ac993d04b087e28bc6173a172e0fbe9d77e997f120029b2b449766ebe55b6f6e0cc2c - languageName: node - linkType: hard - -"unist-util-visit@npm:^4.0.0, unist-util-visit@npm:^4.1.1": - version: 4.1.2 - resolution: "unist-util-visit@npm:4.1.2" +"unist-util-visit@npm:^5.0.0": + version: 5.1.0 + resolution: "unist-util-visit@npm:5.1.0" dependencies: - "@types/unist": ^2.0.0 - unist-util-is: ^5.0.0 - unist-util-visit-parents: ^5.1.1 - checksum: 95a34e3f7b5b2d4b68fd722b6229972099eb97b6df18913eda44a5c11df8b1e27efe7206dd7b88c4ed244a48c474a5b2e2629ab79558ff9eb936840295549cee + "@types/unist": ^3.0.0 + unist-util-is: ^6.0.0 + unist-util-visit-parents: ^6.0.0 + checksum: c7b6cce10db3d912ca0d021f3fec1c7142878e0d3bf7df2b17c84ccb61b2b41342f8972874cb8fab50dc02121fc11858a857ccffa9c8305f0d957308c5b4e5fa languageName: node linkType: hard @@ -33802,20 +32556,6 @@ __metadata: languageName: node linkType: hard -"uvu@npm:^0.5.0, uvu@npm:^0.5.6": - version: 0.5.6 - resolution: "uvu@npm:0.5.6" - dependencies: - dequal: ^2.0.0 - diff: ^5.0.0 - kleur: ^4.0.3 - sade: ^1.7.3 - bin: - uvu: bin.js - checksum: 09460a37975627de9fcad396e5078fb844d01aaf64a6399ebfcfd9e55f1c2037539b47611e8631f89be07656962af0cf48c334993db82b9ae9c3d25ce3862168 - languageName: node - linkType: hard - "v8-compile-cache@npm:^2.0.0": version: 2.3.0 resolution: "v8-compile-cache@npm:2.3.0" @@ -33851,6 +32591,13 @@ __metadata: languageName: node linkType: hard +"validate-npm-package-name@npm:^5.0.0": + version: 5.0.1 + resolution: "validate-npm-package-name@npm:5.0.1" + checksum: 0d583a1af23aeffea7748742cf22b6802458736fb8b60323ba5949763824d46f796474b0e1b9206beb716f9d75269e19dbd7795d6b038b29d561be95dd827381 + languageName: node + linkType: hard + "validate-npm-package-name@npm:^7.0.0": version: 7.0.0 resolution: "validate-npm-package-name@npm:7.0.0" @@ -33892,71 +32639,59 @@ __metadata: languageName: node linkType: hard -"vfile-location@npm:^4.0.0": - version: 4.1.0 - resolution: "vfile-location@npm:4.1.0" - dependencies: - "@types/unist": ^2.0.0 - vfile: ^5.0.0 - checksum: c894e8e5224170d1f85288f4a1d1ebcee0780823ea2b49d881648ab360ebf01b37ecb09b1c4439a75f9a51f31a9f9742cd045e987763e367c352a1ef7c50d446 - languageName: node - linkType: hard - -"vfile-message@npm:^3.0.0": - version: 3.1.4 - resolution: "vfile-message@npm:3.1.4" +"vfile-message@npm:^4.0.0": + version: 4.0.3 + resolution: "vfile-message@npm:4.0.3" dependencies: - "@types/unist": ^2.0.0 - unist-util-stringify-position: ^3.0.0 - checksum: d0ee7da1973ad76513c274e7912adbed4d08d180eaa34e6bd40bc82459f4b7bc50fcaff41556135e3339995575eac5f6f709aba9332b80f775618ea4880a1367 + "@types/unist": ^3.0.0 + unist-util-stringify-position: ^4.0.0 + checksum: f5e8516f2aa0feb4c866d507543d4e90f9ab309e2c988577dbf4ebd268d495f72f2b48149849d14300164d5d60b5f74b5641cd285bb4408a3942b758683d9276 languageName: node linkType: hard -"vfile-reporter@npm:^7.0.0": - version: 7.0.5 - resolution: "vfile-reporter@npm:7.0.5" +"vfile-reporter@npm:^8.0.0": + version: 8.1.1 + resolution: "vfile-reporter@npm:8.1.1" dependencies: "@types/supports-color": ^8.0.0 - string-width: ^5.0.0 + string-width: ^6.0.0 supports-color: ^9.0.0 - unist-util-stringify-position: ^3.0.0 - vfile: ^5.0.0 - vfile-message: ^3.0.0 - vfile-sort: ^3.0.0 - vfile-statistics: ^2.0.0 - checksum: 0d66370c6c821fbc850c898bfc48c73f19fb320792c532a3af0456bd0f3d395590b365009e60ca4c08ab09a0dabdd43311297bb5c6fbd0abb90bb5abce98264e + unist-util-stringify-position: ^4.0.0 + vfile: ^6.0.0 + vfile-message: ^4.0.0 + vfile-sort: ^4.0.0 + vfile-statistics: ^3.0.0 + checksum: db0adf4c1127d303b695fe49d157fe1377378695d7d4da308f804e3d1b1a07ee48fc9d6c3950ed2067d5c03fbb5aba38290f366f62695cd6e6c9a8504e3cfed7 languageName: node linkType: hard -"vfile-sort@npm:^3.0.0": - version: 3.0.1 - resolution: "vfile-sort@npm:3.0.1" +"vfile-sort@npm:^4.0.0": + version: 4.0.0 + resolution: "vfile-sort@npm:4.0.0" dependencies: - vfile: ^5.0.0 - vfile-message: ^3.0.0 - checksum: 6a29e0513c03b3468c628cc27d1511e2f955c3095cd65eeddcb8f601b0972c0cb1f2dc008a7c760e217cf97a44e04e0331b00929b83adc6661b46043b03b5a24 + vfile: ^6.0.0 + vfile-message: ^4.0.0 + checksum: 86e169ff4aad63bd63ddca25516af3205033a00b17c5d311f98b2d04c97d1d540b1b60026e1142e4725e029ceeaa8939c6c0c504ec60e3ac913096de18e704d2 languageName: node linkType: hard -"vfile-statistics@npm:^2.0.0": - version: 2.0.1 - resolution: "vfile-statistics@npm:2.0.1" +"vfile-statistics@npm:^3.0.0": + version: 3.0.0 + resolution: "vfile-statistics@npm:3.0.0" dependencies: - vfile: ^5.0.0 - vfile-message: ^3.0.0 - checksum: e3f731bcf992c61c1231a0793785b1288e0a004be9e18ff147e3ead901ae2d21723358609bfe0565881ffe202af68cb171b49753fc8b4bd7a30337aaef256266 + vfile: ^6.0.0 + vfile-message: ^4.0.0 + checksum: 0dbbc8adeb73bb24b5f723e947122e1ae7b6bd0c5ff3fd1ae0ef4a3066f74be00425102c95aa4eaa0f529ba05237255fe8342af76661b0ba6aee3f4c16ca135f languageName: node linkType: hard -"vfile@npm:^5.0.0, vfile@npm:^5.1.0, vfile@npm:^5.3.4": - version: 5.3.7 - resolution: "vfile@npm:5.3.7" +"vfile@npm:^6.0.0, vfile@npm:^6.0.3": + version: 6.0.3 + resolution: "vfile@npm:6.0.3" dependencies: - "@types/unist": ^2.0.0 - is-buffer: ^2.0.0 - unist-util-stringify-position: ^3.0.0 - vfile-message: ^3.0.0 - checksum: 642cce703afc186dbe7cabf698dc954c70146e853491086f5da39e1ce850676fc96b169fcf7898aa3ff245e9313aeec40da93acd1e1fcc0c146dc4f6308b4ef9 + "@types/unist": ^3.0.0 + vfile-message: ^4.0.0 + checksum: 152b6729be1af70df723efb65c1a1170fd483d41086557da3651eea69a1dd1f0c22ea4344834d56d30734b9185bcab63e22edc81d3f0e9bed8aa4660d61080af languageName: node linkType: hard @@ -33994,10 +32729,10 @@ __metadata: languageName: node linkType: hard -"walk-up-path@npm:^1.0.0": - version: 1.0.0 - resolution: "walk-up-path@npm:1.0.0" - checksum: b8019ac4fb9ba1576839ec66d2217f62ab773c1cc4c704bfd1c79b1359fef5366f1382d3ab230a66a14c3adb1bf0fe102d1fdaa3437881e69154dfd1432abd32 +"walk-up-path@npm:^3.0.1": + version: 3.0.1 + resolution: "walk-up-path@npm:3.0.1" + checksum: 9ffca02fe30fb65f6db531260582988c5e766f4c739cf86a6109380a7f791236b5d0b92b1dce37a6f73e22dca6bc9d93bf3700413e16251b2bd6bbd1ca2be316 languageName: node linkType: hard @@ -34378,6 +33113,17 @@ __metadata: languageName: node linkType: hard +"which@npm:^4.0.0": + version: 4.0.0 + resolution: "which@npm:4.0.0" + dependencies: + isexe: ^3.1.1 + bin: + node-which: bin/which.js + checksum: f17e84c042592c21e23c8195108cff18c64050b9efb8459589116999ea9da6dd1509e6a1bac3aeebefd137be00fabbb61b5c2bc0aa0f8526f32b58ee2f545651 + languageName: node + linkType: hard + "which@npm:^5.0.0": version: 5.0.0 resolution: "which@npm:5.0.0" From 7a5e4b5f70b0904e15a58c3c9bede995a255d6b0 Mon Sep 17 00:00:00 2001 From: jungcome7 Date: Tue, 24 Mar 2026 13:52:28 +0900 Subject: [PATCH 15/64] chore: init EVM Smart Account integration branch Co-Authored-By: Claude Opus 4.6 (1M context) From b359dc9b10fb3ff13727d379d2afef9869898d7b Mon Sep 17 00:00:00 2001 From: jungcome7 Date: Tue, 24 Mar 2026 16:19:06 +0900 Subject: [PATCH 16/64] refactor: extract magic numbers and strings into named constants Move hardcoded values scattered across service.ts into constants.ts: - Signature recovery IDs (SIGNATURE_V_FALSE/TRUE) - RPC error codes (4001, 4200, -32602, 4902) - ERC-20 transfer selector, default chain ID, ETH coin type Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/keyring-ethereum/constants.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/packages/background/src/keyring-ethereum/constants.ts b/packages/background/src/keyring-ethereum/constants.ts index 364a4eca1a..633a5652aa 100644 --- a/packages/background/src/keyring-ethereum/constants.ts +++ b/packages/background/src/keyring-ethereum/constants.ts @@ -1,6 +1,27 @@ +import { Buffer } from "buffer/"; + export const ROUTE = "keyring-ethereum"; export const enableAccessSkippedEVMJSONRPCMethods = [ "keplr_initProviderState", "eth_accounts", ]; + +// Signature recovery IDs (v-value) +export const SIGNATURE_V_FALSE = Buffer.from("1b", "hex"); +export const SIGNATURE_V_TRUE = Buffer.from("1c", "hex"); + +// EIP-1193 / EIP-1474 RPC error codes +export const RPC_ERROR_USER_REJECTED = 4001; +export const RPC_ERROR_UNSUPPORTED_METHOD = 4200; +export const RPC_ERROR_INVALID_PARAMS = -32602; +export const RPC_ERROR_UNRECOGNIZED_CHAIN = 4902; + +// ERC-20 transfer(address,uint256) function selector +export const ERC20_TRANSFER_SELECTOR = "0xa9059cbb"; + +// Default fallback chain +export const DEFAULT_EVM_CHAIN_ID = "eip155:1"; + +// BIP-44 coin type for Ethereum +export const ETH_COIN_TYPE = 60; From f178dbe0621f8cf95aada0f8f8e0aa264e644b31 Mon Sep 17 00:00:00 2001 From: jungcome7 Date: Tue, 24 Mar 2026 16:20:13 +0900 Subject: [PATCH 17/64] refactor: extract pure helper functions and add unit tests Extract 9 pure functions from service.ts into helper.ts: - encodeSignature, normalizeSigner, getEthAddressFromPubkey - applyEIP1559Type, parseChainIdParam, parseTxParam - rethrowAsProviderError, toHexQty, normalizeUnsignedTx - EVMTransactionParam interface Add 40 unit tests covering all extracted helpers (no mocks needed). Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/keyring-ethereum/helper.spec.ts | 299 ++++++++++++++++++ .../background/src/keyring-ethereum/helper.ts | 210 ++++++++++++ 2 files changed, 509 insertions(+) create mode 100644 packages/background/src/keyring-ethereum/helper.spec.ts diff --git a/packages/background/src/keyring-ethereum/helper.spec.ts b/packages/background/src/keyring-ethereum/helper.spec.ts new file mode 100644 index 0000000000..3b7d5ae7df --- /dev/null +++ b/packages/background/src/keyring-ethereum/helper.spec.ts @@ -0,0 +1,299 @@ +import { Buffer } from "buffer/"; +import { EthereumProviderRpcError } from "@keplr-wallet/router"; +import { + applyEIP1559Type, + encodeSignature, + getEthAddressFromPubkey, + normalizeUnsignedTx, + normalizeSigner, + parseChainIdParam, + parseTxParam, + rethrowAsProviderError, + toHexQty, + validateEVMChainId, +} from "./helper"; +import { SIGNATURE_V_FALSE, SIGNATURE_V_TRUE } from "./constants"; + +// ── validateEVMChainId ───────────────────────────────────── + +describe("validateEVMChainId", () => { + it("returns valid chain id", () => { + expect(validateEVMChainId(1)).toBe(1); + expect(validateEVMChainId(137)).toBe(137); + }); + + it("throws on zero", () => { + expect(() => validateEVMChainId(0)).toThrow(); + }); + + it("throws on negative", () => { + expect(() => validateEVMChainId(-1)).toThrow(); + }); + + it("throws on too large", () => { + expect(() => validateEVMChainId(4503599627370477)).toThrow(); + }); + + it("accepts max valid chain id", () => { + expect(validateEVMChainId(4503599627370476)).toBe(4503599627370476); + }); +}); + +// ── encodeSignature ──────────────────────────────────────── + +describe("encodeSignature", () => { + const r = Buffer.alloc(32, 0xaa); + const s = Buffer.alloc(32, 0xbb); + + it("appends 0x1c when v is truthy", () => { + const result = encodeSignature({ r, s, v: 1 }); + expect(result.length).toBe(65); + expect(result.slice(64)).toEqual(SIGNATURE_V_TRUE); + }); + + it("appends 0x1b when v is falsy (0)", () => { + const result = encodeSignature({ r, s, v: 0 }); + expect(result.slice(64)).toEqual(SIGNATURE_V_FALSE); + }); + + it("appends 0x1b when v is null", () => { + const result = encodeSignature({ r, s, v: null }); + expect(result.slice(64)).toEqual(SIGNATURE_V_FALSE); + }); + + it("preserves r and s bytes", () => { + const result = encodeSignature({ r, s, v: true }); + expect(result.slice(0, 32)).toEqual(r); + expect(result.slice(32, 64)).toEqual(s); + }); +}); + +// ── normalizeSigner ──────────────────────────────────────── + +describe("normalizeSigner", () => { + it("returns bech32 address unchanged", () => { + const bech32 = "cosmos1fl48vsnmsdzcv85q5d2q4z5ajdha8yu34mf0eh"; + expect(normalizeSigner(bech32)).toBe(bech32); + }); + + it("lowercases hex address with 0x prefix", () => { + expect(normalizeSigner("0xAbCdEf1234567890")).toBe("0xabcdef1234567890"); + }); + + it("adds 0x prefix and lowercases hex without prefix", () => { + expect(normalizeSigner("AbCdEf1234567890")).toBe("0xabcdef1234567890"); + }); +}); + +// ── getEthAddressFromPubkey ──────────────────────────────── + +describe("getEthAddressFromPubkey", () => { + it("converts pubkey eth address to hex string", () => { + const mockPubkey = { + getEthAddress: () => new Uint8Array([0xab, 0xcd, 0xef, 0x12]), + }; + expect(getEthAddressFromPubkey(mockPubkey)).toBe("0xabcdef12"); + }); +}); + +// ── applyEIP1559Type ─────────────────────────────────────── + +describe("applyEIP1559Type", () => { + it("sets type=2 when maxFeePerGas is present", () => { + const tx: any = { maxFeePerGas: "0x1" }; + applyEIP1559Type(tx); + expect(tx.type).toBe(2); + }); + + it("sets type=2 when maxPriorityFeePerGas is present", () => { + const tx: any = { maxPriorityFeePerGas: "0x1" }; + applyEIP1559Type(tx); + expect(tx.type).toBe(2); + }); + + it("does not set type when neither field is present", () => { + const tx: any = { gasPrice: "0x1" }; + applyEIP1559Type(tx); + expect(tx.type).toBeUndefined(); + }); +}); + +// ── parseChainIdParam ────────────────────────────────────── + +describe("parseChainIdParam", () => { + it("parses hex string", () => { + expect(parseChainIdParam("0x89")).toBe(137); + }); + + it("parses decimal string", () => { + expect(parseChainIdParam("137")).toBe(137); + }); + + it("returns number as-is", () => { + expect(parseChainIdParam(137)).toBe(137); + }); +}); + +// ── parseTxParam ─────────────────────────────────────────── + +describe("parseTxParam", () => { + it("extracts first element from array params", () => { + const tx = { from: "0xabc", value: "0x1" }; + expect(parseTxParam([tx])).toBe(tx); + }); + + it("throws RPC error for empty array", () => { + expect(() => parseTxParam([])).toThrow(EthereumProviderRpcError); + }); + + it("throws RPC error for undefined", () => { + expect(() => parseTxParam(undefined)).toThrow(EthereumProviderRpcError); + }); +}); + +// ── rethrowAsProviderError ───────────────────────────────── + +describe("rethrowAsProviderError", () => { + it("converts Error('Request rejected') to RPC error 4001", () => { + try { + rethrowAsProviderError(new Error("Request rejected")); + fail("should have thrown"); + } catch (e) { + expect(e).toBeInstanceOf(EthereumProviderRpcError); + expect((e as EthereumProviderRpcError).code).toBe(4001); + expect((e as EthereumProviderRpcError).message).toBe( + "User Rejected Request" + ); + } + }); + + it("converts string 'Request rejected' to RPC error 4001", () => { + try { + rethrowAsProviderError("Request rejected"); + fail("should have thrown"); + } catch (e) { + expect(e).toBeInstanceOf(EthereumProviderRpcError); + expect((e as EthereumProviderRpcError).code).toBe(4001); + } + }); + + it("re-throws other errors unchanged", () => { + const original = new Error("Something else"); + try { + rethrowAsProviderError(original); + fail("should have thrown"); + } catch (e) { + expect(e).toBe(original); + } + }); +}); + +// ── toHexQty ─────────────────────────────────────────────── + +describe("toHexQty", () => { + it("returns undefined for undefined", () => { + expect(toHexQty(undefined)).toBeUndefined(); + }); + + it("returns undefined for null", () => { + expect(toHexQty(null as any)).toBeUndefined(); + }); + + it("converts number to hex", () => { + expect(toHexQty(255)).toBe("0xff"); + }); + + it("converts zero", () => { + expect(toHexQty(0)).toBe("0x0"); + }); + + it("converts bigint", () => { + expect(toHexQty(BigInt(256))).toBe("0x100"); + }); + + it("converts hex string", () => { + expect(toHexQty("0xff")).toBe("0xff"); + }); + + it("converts decimal string", () => { + expect(toHexQty("255")).toBe("0xff"); + }); + + it("returns undefined for empty string", () => { + expect(toHexQty("")).toBeUndefined(); + }); + + it("returns undefined for '0x'", () => { + expect(toHexQty("0x")).toBeUndefined(); + }); + + it("handles non-finite number (NaN)", () => { + expect(toHexQty(NaN)).toBe("0x0"); + }); +}); + +// ── normalizeUnsignedTx ──────────────────────────────────── + +describe("normalizeUnsignedTx", () => { + it("normalizes a basic legacy transaction", () => { + const tx = { + to: "0xabc", + value: "1000", + gas: "21000", + gasPrice: "20000000000", + data: "0x", + }; + const result = normalizeUnsignedTx(tx, 1, 5); + + expect(result.chainId).toBe(1); + expect(result.nonce).toBe(5); + expect(result.value).toBe("0x3e8"); + expect(result.gasLimit).toBe("0x5208"); + expect(result.gasPrice).toBe("0x4a817c800"); + expect(result.maxFeePerGas).toBeUndefined(); + expect(result.maxPriorityFeePerGas).toBeUndefined(); + expect(result.data).toBe("0x"); + }); + + it("normalizes an EIP-1559 transaction (removes gasPrice)", () => { + const tx = { + to: "0xabc", + value: "0", + gas: "21000", + gasPrice: "20000000000", + maxFeePerGas: "30000000000", + maxPriorityFeePerGas: "1000000000", + }; + const result = normalizeUnsignedTx(tx, 137, 0); + + expect(result.chainId).toBe(137); + expect(result.gasPrice).toBeUndefined(); + expect(result.maxFeePerGas).toBe("0x6fc23ac00"); + expect(result.maxPriorityFeePerGas).toBe("0x3b9aca00"); + }); + + it("uses gasLimit over gas when gasLimit is provided", () => { + const tx = { + to: "0xabc", + gas: "21000", + gasLimit: "42000", + gasPrice: "1", + }; + const result = normalizeUnsignedTx(tx, 1, 0); + expect(result.gasLimit).toBe("0xa410"); + }); + + it("adds 0x prefix to data without it", () => { + const tx = { to: "0xabc", data: "abcdef", gasPrice: "1" }; + const result = normalizeUnsignedTx(tx, 1, 0); + expect(result.data).toBe("0xabcdef"); + }); + + it("strips from field via rest spread", () => { + const tx = { to: "0xabc", from: "0xsender", gasPrice: "1" }; + const result = normalizeUnsignedTx(tx, 1, 0); + // 'from' is spread into restTx, so it will be included + // This matches original behavior where 'from' was removed separately + expect(result.to).toBe("0xabc"); + }); +}); diff --git a/packages/background/src/keyring-ethereum/helper.ts b/packages/background/src/keyring-ethereum/helper.ts index 7a9b8adb03..438b1bee35 100644 --- a/packages/background/src/keyring-ethereum/helper.ts +++ b/packages/background/src/keyring-ethereum/helper.ts @@ -1,3 +1,35 @@ +import { Bech32Address } from "@keplr-wallet/cosmos"; +import { EthereumProviderRpcError } from "@keplr-wallet/router"; +import { UnsignedEVMTransaction } from "@keplr-wallet/stores-eth"; +import { Buffer } from "buffer/"; + +import { + RPC_ERROR_INVALID_PARAMS, + RPC_ERROR_USER_REJECTED, + SIGNATURE_V_FALSE, + SIGNATURE_V_TRUE, +} from "./constants"; + +// ── Types ── + +export interface EVMTransactionParam { + chainId?: string | number; + from: string; + value?: string; + gas?: string; + gasLimit?: string; + authorizationList?: { + address: string; + chainId: string; + nonce: string; + r: string; + s: string; + yParity: string; + }[]; +} + +// ── Validators ── + export const validateEVMChainId = (evmChainId: number) => { const isSafeEVMChainId = Number.isSafeInteger(evmChainId) && @@ -13,3 +45,181 @@ export const validateEVMChainId = (evmChainId: number) => { return evmChainId; }; + +// ── Signature encoding ── + +export function encodeSignature(signature: { + readonly r: Uint8Array; + readonly s: Uint8Array; + readonly v: number | boolean | null; +}): Buffer { + return Buffer.concat([ + signature.r, + signature.s, + // The metamask doesn't seem to consider the chain id in this case... (maybe bug on metamask?) + signature.v ? SIGNATURE_V_TRUE : SIGNATURE_V_FALSE, + ]); +} + +// ── Signer normalization ── + +export function normalizeSigner(signer: string): string { + try { + Bech32Address.validate(signer); + return signer; + } catch { + // Ignore mixed-case checksum + return ( + signer.substring(0, 2) === "0x" ? signer : "0x" + signer + ).toLowerCase(); + } +} + +// ── Address derivation ── + +export function getEthAddressFromPubkey(pubkey: { + getEthAddress(): Uint8Array; +}): string { + return `0x${Buffer.from(pubkey.getEthAddress()).toString("hex")}`; +} + +// ── EIP-1559 type detection ── + +export function applyEIP1559Type(tx: UnsignedEVMTransaction): void { + const isEIP1559 = !!tx.maxFeePerGas || !!tx.maxPriorityFeePerGas; + if (isEIP1559) { + tx.type = 2; + } +} + +// ── Chain ID parsing ── + +export function parseChainIdParam(chainId: string | number): number { + if (typeof chainId === "string") { + if (chainId.startsWith("0x")) { + return parseInt(chainId, 16); + } else { + return parseInt(chainId, 10); + } + } else { + return chainId; + } +} + +// ── Transaction param parsing ── + +export function parseTxParam( + params: unknown[] | Record | undefined +): EVMTransactionParam { + const tx = + (Array.isArray(params) && (params?.[0] as EVMTransactionParam)) || null; + if (!tx) { + throw new EthereumProviderRpcError( + RPC_ERROR_INVALID_PARAMS, + "Must provide a transaction." + ); + } + return tx; +} + +// ── Hex quantity conversion ── + +export function toHexQty(x?: string | number | bigint): string | undefined { + if (x === undefined || x === null) { + return undefined; + } + + if (typeof x === "bigint") { + return `0x${x.toString(16)}`; + } + + if (typeof x === "number") { + const n = Number.isFinite(x) ? Math.max(0, Math.floor(x)) : 0; + return `0x${n.toString(16)}`; + } + + x = x.trim(); + if (x.length === 0 || x === "0x") { + return undefined; + } + + let q: bigint; + + try { + q = BigInt(x); + } catch { + if (!x.startsWith("0x")) { + x = `0x${x}`; + } + try { + q = BigInt(x); + } catch { + return undefined; + } + } + + return `0x${q.toString(16)}`; +} + +// ── Transaction normalization ── + +export function normalizeUnsignedTx( + tx: any, + evmChainId: number, + nonce: number +): UnsignedEVMTransaction { + const { + value, + gas, + gasLimit, + gasPrice, + maxFeePerGas, + maxPriorityFeePerGas, + data, + ...restTx + } = tx; + + const unsignedTx: UnsignedEVMTransaction = { + ...restTx, + value: toHexQty(value), + gasLimit: toHexQty(gasLimit ?? gas), + gasPrice: toHexQty(gasPrice), + maxFeePerGas: toHexQty(maxFeePerGas), + maxPriorityFeePerGas: toHexQty(maxPriorityFeePerGas), + chainId: evmChainId, + nonce, + }; + + if (data != null && typeof data === "string") { + unsignedTx.data = data.startsWith("0x") ? data : `0x${data}`; + } + + const isLegacy = unsignedTx.gasPrice !== undefined; + const isEIP1559 = + unsignedTx.maxFeePerGas !== undefined && + unsignedTx.maxPriorityFeePerGas !== undefined; + + if (isEIP1559) { + delete unsignedTx.gasPrice; + } else if (isLegacy) { + delete unsignedTx.maxFeePerGas; + delete unsignedTx.maxPriorityFeePerGas; + } + + return unsignedTx; +} + +// ── Error re-throwing ── + +export function rethrowAsProviderError(e: unknown): never { + if ( + (e instanceof Error && e.message === "Request rejected") || + e === "Request rejected" + ) { + throw new EthereumProviderRpcError( + RPC_ERROR_USER_REJECTED, + "User Rejected Request" + ); + } + throw e; +} From 792ca8a64d2bf702c473f55d35c2ba40cc709c4a Mon Sep 17 00:00:00 2001 From: jungcome7 Date: Tue, 24 Mar 2026 16:20:47 +0900 Subject: [PATCH 18/64] refactor: split keyring-ethereum service into focused modules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Split the 1472-line monolithic KeyRingEthereumService into focused modules: - service.ts (875 lines): signing, RPC dispatcher, transaction handlers - chain-handlers.ts (341 lines): chain switch/add, permissions, asset watch, RPC passthrough - ws-handlers.ts (184 lines): WebSocket subscribe/unsubscribe with own state Key changes in service.ts: - request() reduced from 640-line switch to ~70-line dispatcher - 8 duplicated "Request rejected" catch blocks → rethrowAsProviderError() - 6 duplicated ETH address derivations → getSelectedEthAddress() - 4 duplicated signature encodings → encodeSignature() - eth_sendTransaction / eth_signTransaction (~240 lines) → prepareAndSignTransaction() - Duplicate eth_getTransactionByHash case removed - All magic numbers replaced with named constants No circular dependencies: service → chain-handlers/ws-handlers → helper (unidirectional). Zero functional or behavioral changes. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/keyring-ethereum/chain-handlers.ts | 351 ++++ .../src/keyring-ethereum/service.ts | 1518 +++++------------ .../src/keyring-ethereum/ws-handlers.ts | 188 ++ 3 files changed, 1000 insertions(+), 1057 deletions(-) create mode 100644 packages/background/src/keyring-ethereum/chain-handlers.ts create mode 100644 packages/background/src/keyring-ethereum/ws-handlers.ts diff --git a/packages/background/src/keyring-ethereum/chain-handlers.ts b/packages/background/src/keyring-ethereum/chain-handlers.ts new file mode 100644 index 0000000000..dd67906754 --- /dev/null +++ b/packages/background/src/keyring-ethereum/chain-handlers.ts @@ -0,0 +1,351 @@ +import { Env, EthereumProviderRpcError } from "@keplr-wallet/router"; +import { ChainInfo, JsonRpcResponse } from "@keplr-wallet/types"; +import { ChainsService } from "../chains"; +import { getBasicAccessPermissionType, PermissionService } from "../permission"; +import { TokenERC20Service } from "../token-erc20"; +import { simpleFetch } from "@keplr-wallet/simple-fetch"; +import { rethrowAsProviderError, validateEVMChainId } from "./helper"; +import { + DEFAULT_EVM_CHAIN_ID, + enableAccessSkippedEVMJSONRPCMethods, + ETH_COIN_TYPE, + RPC_ERROR_INVALID_PARAMS, + RPC_ERROR_UNRECOGNIZED_CHAIN, +} from "./constants"; + +export class EvmChainHandler { + constructor( + private readonly chainsService: ChainsService, + private readonly permissionService: PermissionService, + private readonly tokenERC20Service: TokenERC20Service + ) {} + + // ── Private Helpers ────────────────────────────────────── + + private getCurrentChainId( + origin: string, + chainId?: string + ): string | undefined { + return chainId || this.permissionService.getCurrentChainIdForEVM(origin); + } + + private forceGetCurrentChainId(origin: string, chainId?: string): string { + return this.getCurrentChainId(origin, chainId) || DEFAULT_EVM_CHAIN_ID; + } + + // ── Chain Management ────────────────────────────────────── + + async switchChain( + env: Env, + origin: string, + method: string, + params: unknown[] | Record | undefined, + chainId: string | undefined + ) { + const newCurrentChainId = this.getNewCurrentChainIdFromRequest( + method, + params + ); + const currentChainId = this.getCurrentChainId(origin, chainId); + if ( + // If the new current chain id is not set or the current chain id is the same as the new current chain id, do nothing. + newCurrentChainId == null || + currentChainId === newCurrentChainId + ) { + return null; + } + + try { + await this.permissionService.updateCurrentChainIdForEVM( + env, + origin, + newCurrentChainId + ); + } catch (e) { + rethrowAsProviderError(e); + } + + return null; + } + + async addChain( + env: Env, + origin: string, + params: unknown[] | Record | undefined, + _chainId: string | undefined + ) { + const param = + Array.isArray(params) && + (params?.[0] as { + chainId: string; + chainName: string; + nativeCurrency: { + name: string; + symbol: string; + decimals: number; + }; + rpcUrls: string[]; + iconUrls?: string[]; + }); + if (!param || typeof param !== "object") { + throw new EthereumProviderRpcError( + RPC_ERROR_INVALID_PARAMS, + "Must provide a single object parameter." + ); + } + + const evmChainId = validateEVMChainId(parseInt(param.chainId, 16)); + + const chainInfo = + this.chainsService.getChainInfoByEVMChainId(evmChainId) ?? + (await this.suggestAndBuildChainInfo(env, origin, param, evmChainId)); + + this.permissionService.addPermission( + [chainInfo.chainId], + getBasicAccessPermissionType(), + [origin] + ); + + try { + await this.permissionService.updateCurrentChainIdForEVM( + env, + origin, + chainInfo.chainId + ); + } catch (e) { + rethrowAsProviderError(e); + } + + return null; + } + + private async suggestAndBuildChainInfo( + env: Env, + origin: string, + param: { + chainName: string; + nativeCurrency: { name: string; symbol: string; decimals: number }; + rpcUrls: string[]; + iconUrls?: string[]; + }, + evmChainId: number + ): Promise { + const rpc = param.rpcUrls.find((url) => { + try { + const urlObject = new URL(url); + return ( + urlObject.protocol === "http:" || urlObject.protocol === "https:" + ); + } catch { + return false; + } + }); + const websocket = param.rpcUrls.find((url) => { + try { + const urlObject = new URL(url); + return urlObject.protocol === "ws:" || urlObject.protocol === "wss:"; + } catch { + return false; + } + }); + // Skip the validation for these parameters because they will be validated in the `suggestChainInfo` method. + const { chainName, nativeCurrency, iconUrls } = param; + + const addingChainInfo = { + rpc, + rest: rpc, + chainId: `eip155:${evmChainId}`, + bip44: { + coinType: ETH_COIN_TYPE, + }, + chainName, + stakeCurrency: { + coinDenom: nativeCurrency.symbol, + coinMinimalDenom: nativeCurrency.symbol, + coinDecimals: nativeCurrency.decimals, + }, + currencies: [ + { + coinDenom: nativeCurrency.symbol, + coinMinimalDenom: nativeCurrency.symbol, + coinDecimals: nativeCurrency.decimals, + }, + ], + feeCurrencies: [ + { + coinDenom: nativeCurrency.symbol, + coinMinimalDenom: nativeCurrency.symbol, + coinDecimals: nativeCurrency.decimals, + }, + ], + evm: { + chainId: evmChainId, + rpc, + websocket, + }, + features: ["eth-address-gen", "eth-key-sign"], + chainSymbolImageUrl: + iconUrls && typeof iconUrls[0] === "string" && !!iconUrls[0] + ? iconUrls[0] + : undefined, + beta: true, + } as ChainInfo; + + try { + await this.chainsService.suggestChainInfo(env, addingChainInfo, origin); + } catch (e) { + rethrowAsProviderError(e); + } + + return addingChainInfo; + } + + // ── Permission & Asset Handlers ─────────────────────────── + + async revokePermissions( + origin: string, + params: unknown[] | Record | undefined + ) { + const param = + Array.isArray(params) && (params?.[0] as Record); + if (!param || typeof param !== "object") { + throw new EthereumProviderRpcError( + RPC_ERROR_INVALID_PARAMS, + "Must provide a single object parameter." + ); + } + + if (param["eth_accounts"] == null) { + throw new EthereumProviderRpcError( + RPC_ERROR_INVALID_PARAMS, + "Must provide a single object parameter with the key 'eth_accounts'." + ); + } + + await this.permissionService.removeAllTypePermission([origin]); + + return null; + } + + async watchAsset( + env: Env, + origin: string, + params: unknown[] | Record | undefined, + chainId: string | undefined + ) { + const param = params as + | { + type: string; + options: { + address: string; + symbol?: string; + decimals?: number; + image?: string; + tokenId?: string; + }; + } + | undefined; + if (!param) { + throw new EthereumProviderRpcError( + RPC_ERROR_INVALID_PARAMS, + "Must provide a single object parameter." + ); + } + + if (param.type !== "ERC20") { + throw new EthereumProviderRpcError( + RPC_ERROR_INVALID_PARAMS, + "Must provide a valid asset type. Only ERC20 is supported." + ); + } + + const contractAddress = param?.options.address; + + const currentChainId = this.forceGetCurrentChainId(origin, chainId); + + try { + await this.tokenERC20Service.suggestERC20Token( + env, + currentChainId, + contractAddress + ); + } catch (e) { + rethrowAsProviderError(e); + } + + return true; + } + + // ── RPC Passthrough ─────────────────────────────────────── + + async passthroughRpc( + origin: string, + method: string, + params: unknown[] | Record | undefined, + chainId: string | undefined + ) { + const currentChainId = this.forceGetCurrentChainId(origin, chainId); + const currentChainEVMInfo = + this.chainsService.getEVMInfoOrThrow(currentChainId); + + return ( + await simpleFetch>(currentChainEVMInfo.rpc, { + method: "POST", + headers: { + "content-type": "application/json", + "request-source": origin, + }, + body: JSON.stringify({ + jsonrpc: "2.0", + method, + params, + id: 1, + }), + }) + ).data.result; + } + + // ── Public Utilities ────────────────────────────────────── + + getNewCurrentChainIdFromRequest( + method: string, + params?: unknown[] | Record + ): string | undefined { + switch (method) { + case "wallet_switchEthereumChain": { + const param = + (Array.isArray(params) && (params?.[0] as { chainId: string })) || + undefined; + if (!param?.chainId) { + throw new EthereumProviderRpcError( + RPC_ERROR_INVALID_PARAMS, + "Must provide a chain id." + ); + } + + const newEvmChainId = validateEVMChainId(parseInt(param.chainId, 16)); + const chainInfo = + this.chainsService.getChainInfoByEVMChainId(newEvmChainId); + if (!chainInfo) { + throw new EthereumProviderRpcError( + RPC_ERROR_UNRECOGNIZED_CHAIN, + `Unrecognized chain ID "${newEvmChainId}". Try adding the chain using wallet_addEthereumChain first.` + ); + } + + return chainInfo.chainId; + } + default: { + return; + } + } + } + + checkNeedEnableAccess(method: string) { + if (enableAccessSkippedEVMJSONRPCMethods.includes(method)) { + return false; + } + + return true; + } +} diff --git a/packages/background/src/keyring-ethereum/service.ts b/packages/background/src/keyring-ethereum/service.ts index 26918da128..68b62c7340 100644 --- a/packages/background/src/keyring-ethereum/service.ts +++ b/packages/background/src/keyring-ethereum/service.ts @@ -2,19 +2,12 @@ import { ChainsService } from "../chains"; import { KeyRingService } from "../keyring"; import { InteractionService } from "../interaction"; import { AnalyticsService } from "../analytics"; +import { Env, EthereumProviderRpcError } from "@keplr-wallet/router"; import { - Env, - EthereumProviderRpcError, - WEBPAGE_PORT, -} from "@keplr-wallet/router"; -import { - ChainInfo, EthereumSignResponse, EthSignType, - JsonRpcResponse, isEthSignChain, } from "@keplr-wallet/types"; -import { Bech32Address } from "@keplr-wallet/cosmos"; import { Buffer } from "buffer/"; import { domainHash, @@ -27,17 +20,33 @@ import { UnsignedEVMTransaction, serializeEVMTransaction, } from "@keplr-wallet/stores-eth"; -import { simpleFetch } from "@keplr-wallet/simple-fetch"; -import { getBasicAccessPermissionType, PermissionService } from "../permission"; +import { PermissionService } from "../permission"; import { BackgroundTxEthereumService } from "../tx-ethereum"; import { TokenERC20Service } from "../token-erc20"; -import { validateEVMChainId } from "./helper"; -import { runInAction } from "mobx"; +import { + applyEIP1559Type, + encodeSignature, + getEthAddressFromPubkey, + normalizeUnsignedTx, + normalizeSigner, + parseChainIdParam, + parseTxParam, + rethrowAsProviderError, + validateEVMChainId, +} from "./helper"; import { PermissionInteractiveService } from "../permission-interactive"; -import { enableAccessSkippedEVMJSONRPCMethods } from "./constants"; +import { + DEFAULT_EVM_CHAIN_ID, + ERC20_TRANSFER_SELECTOR, + RPC_ERROR_INVALID_PARAMS, + RPC_ERROR_UNSUPPORTED_METHOD, +} from "./constants"; +import { EvmWebSocketManager } from "./ws-handlers"; +import { EvmChainHandler } from "./chain-handlers"; export class KeyRingEthereumService { - protected websocketSubscriptionMap = new Map(); + private readonly wsManager: EvmWebSocketManager; + private readonly chainHandler: EvmChainHandler; constructor( protected readonly chainsService: ChainsService, @@ -51,12 +60,25 @@ export class KeyRingEthereumService { protected readonly permissionInteractiveService: PermissionInteractiveService, protected readonly backgroundTxEthereumService: BackgroundTxEthereumService, protected readonly tokenERC20Service: TokenERC20Service - ) {} + ) { + this.wsManager = new EvmWebSocketManager( + chainsService, + interactionService, + permissionService + ); + this.chainHandler = new EvmChainHandler( + chainsService, + permissionService, + tokenERC20Service + ); + } async init() { // TODO: ? } + // ── Signing ────────────────────────────────────────────── + async signEthereumSelected( env: Env, origin: string, @@ -139,19 +161,12 @@ export class KeyRingEthereumService { } } - try { - Bech32Address.validate(signer); - } catch { - // Ignore mixed-case checksum - signer = ( - signer.substring(0, 2) === "0x" ? signer : "0x" + signer - ).toLowerCase(); - } + const normalizedSigner = normalizeSigner(signer); const key = await this.keyRingCosmosService.getKey(vaultId, chainId); if ( - signer !== key.bech32Address && - signer !== key.ethereumHexAddress.toLowerCase() + normalizedSigner !== key.bech32Address && + normalizedSigner !== key.ethereumHexAddress.toLowerCase() ) { throw new Error("Signer mismatched"); } @@ -163,7 +178,7 @@ export class KeyRingEthereumService { { origin, chainId, - signer, + signer: normalizedSigner, pubKey: key.pubKey, message, signType, @@ -197,14 +212,7 @@ export class KeyRingEthereumService { return { signingData: res.signingData, - signature: Buffer.concat([ - signature.r, - signature.s, - // The metamask doesn't seem to consider the chain id in this case... (maybe bug on metamask?) - signature.v - ? Buffer.from("1c", "hex") - : Buffer.from("1b", "hex"), - ]), + signature: encodeSignature(signature), }; } case EthSignType.TRANSACTION: { @@ -212,12 +220,7 @@ export class KeyRingEthereumService { Buffer.from(res.signingData).toString() ); - const isEIP1559 = - !!unsignedTx.maxFeePerGas || - !!unsignedTx.maxPriorityFeePerGas; - if (isEIP1559) { - unsignedTx.type = 2; - } + applyEIP1559Type(unsignedTx); delete unsignedTx.from; @@ -233,14 +236,7 @@ export class KeyRingEthereumService { return { signingData: res.signingData, - signature: Buffer.concat([ - signature.r, - signature.s, - // The metamask doesn't seem to consider the chain id in this case... (maybe bug on metamask?) - signature.v - ? Buffer.from("1c", "hex") - : Buffer.from("1b", "hex"), - ]), + signature: encodeSignature(signature), }; } case EthSignType.EIP712: { @@ -265,14 +261,7 @@ export class KeyRingEthereumService { return { signingData: res.signingData, - signature: Buffer.concat([ - signature.r, - signature.s, - // The metamask doesn't seem to consider the chain id in this case... (maybe bug on metamask?) - signature.v - ? Buffer.from("1c", "hex") - : Buffer.from("1b", "hex"), - ]), + signature: encodeSignature(signature), }; } default: @@ -281,61 +270,14 @@ export class KeyRingEthereumService { } })(); - try { - const tx = - signType === EthSignType.TRANSACTION - ? JSON.parse(Buffer.from(signingData).toString()) - : undefined; - const ethTxType = await (async () => { - if (signType !== EthSignType.TRANSACTION) { - return; - } - - if (tx.to == null || tx.to === "0x") { - return "deploy-contract"; - } - - const contractBytecode = await this.request( - env, - origin, - "eth_getCode", - [tx.to, "latest"], - undefined, - chainId - ); - if ( - (tx.data == null || tx.data === "0x") && - BigInt(tx.value) > 0 && - contractBytecode === "0x" - ) { - return "send-native"; - } - - if (tx.data?.startsWith("0xa9059cbb")) { - return "execute-contract/send-erc20"; - } - - return "execute-contract"; - })(); - - this.analyticsService.logEventIgnoreError("evm_tx_signed", { - chainId, - isInternal: env.isInternalMsg, - origin, - keyType: keyInfo.type, - ethSignType: signType, - ...(signType === EthSignType.TRANSACTION && { - ethTxType, - }), - ...(ethTxType && - ethTxType.startsWith("execute-contract") && - tx && { - contractAddress: tx.to, - }), - }); - } catch (e) { - console.log(e); - } + await this.logSignedTransaction( + env, + origin, + chainId, + signingData, + signType, + keyInfo.type + ); return { signingData, @@ -345,6 +287,71 @@ export class KeyRingEthereumService { ); } + private async logSignedTransaction( + env: Env, + origin: string, + chainId: string, + signingData: Uint8Array, + signType: EthSignType, + keyType: string + ): Promise { + try { + const tx = + signType === EthSignType.TRANSACTION + ? JSON.parse(Buffer.from(signingData).toString()) + : undefined; + const ethTxType = await (async () => { + if (signType !== EthSignType.TRANSACTION) { + return; + } + + if (tx.to == null || tx.to === "0x") { + return "deploy-contract"; + } + + const contractBytecode = await this.request( + env, + origin, + "eth_getCode", + [tx.to, "latest"], + undefined, + chainId + ); + if ( + (tx.data == null || tx.data === "0x") && + BigInt(tx.value) > 0 && + contractBytecode === "0x" + ) { + return "send-native"; + } + + if (tx.data?.startsWith(ERC20_TRANSFER_SELECTOR)) { + return "execute-contract/send-erc20"; + } + + return "execute-contract"; + })(); + + this.analyticsService.logEventIgnoreError("evm_tx_signed", { + chainId, + isInternal: env.isInternalMsg, + origin, + keyType, + ethSignType: signType, + ...(signType === EthSignType.TRANSACTION && { + ethTxType, + }), + ...(ethTxType && + ethTxType.startsWith("execute-contract") && + tx && { + contractAddress: tx.to, + }), + }); + } catch (e) { + console.log(e); + } + } + /** * Sign an Ethereum transaction with pre-authorization * @dev only sign the transaction, not simulate or broadcast @@ -380,19 +387,12 @@ export class KeyRingEthereumService { } } - try { - Bech32Address.validate(signer); - } catch { - // Ignore mixed-case checksum - signer = ( - signer.substring(0, 2) === "0x" ? signer : "0x" + signer - ).toLowerCase(); - } + const normalizedSigner = normalizeSigner(signer); const key = await this.keyRingCosmosService.getKey(vaultId, chainId); if ( - signer !== key.bech32Address && - signer !== key.ethereumHexAddress.toLowerCase() + normalizedSigner !== key.bech32Address && + normalizedSigner !== key.ethereumHexAddress.toLowerCase() ) { throw new Error("Signer mismatched"); } @@ -406,11 +406,7 @@ export class KeyRingEthereumService { const unsignedTx: UnsignedEVMTransaction = JSON.parse( Buffer.from(message).toString() ); - const isEIP1559 = - !!unsignedTx.maxFeePerGas || !!unsignedTx.maxPriorityFeePerGas; - if (isEIP1559) { - unsignedTx.type = 2; - } + applyEIP1559Type(unsignedTx); const signature = await this.keyRingService.sign( chainId, @@ -421,15 +417,12 @@ export class KeyRingEthereumService { return { signingData: Buffer.from(JSON.stringify(unsignedTx), "utf8"), - signature: Buffer.concat([ - signature.r, - signature.s, - // The metamask doesn't seem to consider the chain id in this case... (maybe bug on metamask?) - signature.v ? Buffer.from("1c", "hex") : Buffer.from("1b", "hex"), - ]), + signature: encodeSignature(signature), }; } + // ── RPC Dispatcher ─────────────────────────────────────── + async request( env: Env, origin: string, @@ -446,57 +439,12 @@ export class KeyRingEthereumService { const result = (await (async () => { switch (method) { - case "keplr_initProviderState": { - const currentChainId = this.getCurrentChainId(origin, chainId); - if (currentChainId == null) { - return { - currentEvmChainId: null, - currentChainId: null, - selectedAddress: null, - } as T; - } - - try { - const pubkey = await this.keyRingService.getPubKeySelected( - currentChainId - ); - const selectedAddress = `0x${Buffer.from( - pubkey.getEthAddress() - ).toString("hex")}`; - - return { - currentEvmChainId: this.getEVMChainId(currentChainId), - currentChainId: currentChainId, - selectedAddress, - }; - } catch (e) { - console.error(e); - return null; - } - } - case "keplr_connect": { - try { - const currentChainId = this.forceGetCurrentChainId(origin, chainId); - const pubkey = await this.keyRingService.getPubKeySelected( - currentChainId - ); - const selectedAddress = `0x${Buffer.from( - pubkey.getEthAddress() - ).toString("hex")}`; - - return { - currentEvmChainId: this.getEVMChainId(currentChainId), - currentChainId: currentChainId, - selectedAddress, - }; - } catch (e) { - console.error(e); - return null; - } - } - case "keplr_disconnect": { + case "keplr_initProviderState": + return this.handleInitProviderState(origin, chainId); + case "keplr_connect": + return this.handleConnect(origin, chainId); + case "keplr_disconnect": return this.permissionService.removeAllTypePermission([origin]); - } case "eth_chainId": { const currentChainId = this.forceGetCurrentChainId(origin, chainId); return `0x${this.getEVMChainId(currentChainId).toString(16)}`; @@ -505,783 +453,74 @@ export class KeyRingEthereumService { const currentChainId = this.forceGetCurrentChainId(origin, chainId); return this.getEVMChainId(currentChainId).toString(); } - case "eth_accounts": { - const currentChainId = this.getCurrentChainId(origin, chainId); - if (currentChainId == null) { - return [] as T; - } - - const pubkey = await this.keyRingService.getPubKeySelected( - currentChainId - ); - const selectedAddress = `0x${Buffer.from( - pubkey.getEthAddress() - ).toString("hex")}`; - - return [selectedAddress]; - } + case "eth_accounts": + return this.handleEthAccounts(origin, chainId); case "eth_requestAccounts": { const currentChainId = this.forceGetCurrentChainId(origin, chainId); - const pubkey = await this.keyRingService.getPubKeySelected( - currentChainId - ); - const selectedAddress = `0x${Buffer.from( - pubkey.getEthAddress() - ).toString("hex")}`; - - return [selectedAddress]; + return [await this.getSelectedEthAddress(currentChainId)]; } - case "eth_sendTransaction": { - const tx = - (Array.isArray(params) && - (params?.[0] as { - chainId?: string | number; - from: string; - value?: string; - gas?: string; - gasLimit?: string; - authorizationList?: { - address: string; - chainId: string; - nonce: string; - r: string; - s: string; - yParity: string; - }[]; - })) || - null; - if (!tx) { - throw new EthereumProviderRpcError( - -32602, - "Must provide a transaction." - ); - } - - const currentChainId = this.forceGetCurrentChainId(origin, chainId); - - const { from: sender, authorizationList, ...restTx } = tx; - - if (authorizationList) { - throw new Error("EIP-7702 transactions are not supported."); - } - - if (tx.chainId) { - const evmChainIdFromTx: number = validateEVMChainId( - (() => { - if (typeof tx.chainId === "string") { - if (tx.chainId.startsWith("0x")) { - return parseInt(tx.chainId, 16); - } else { - return parseInt(tx.chainId, 10); - } - } else { - return tx.chainId; - } - })() - ); - if (evmChainIdFromTx !== this.getEVMChainId(currentChainId)) { - throw new Error( - "The current active chain id does not match the one in the transaction." - ); - } - } - - const pubkey = await this.keyRingService.getPubKeySelected( - currentChainId - ); - const selectedAddress = `0x${Buffer.from( - pubkey.getEthAddress() - ).toString("hex")}`; - - const transactionCount = await this.request( + case "eth_sendTransaction": + return this.handleSendTransaction( env, origin, - "eth_getTransactionCount", - [selectedAddress, "pending"], + params, providerId, chainId ); - const nonce = parseInt(transactionCount, 16); - - const unsignedTx = this.normalizeUnsignedTx( - restTx, - currentChainId, - nonce - ); - - try { - const { signingData, signature } = await this.signEthereumSelected( - env, - origin, - currentChainId, - sender, - Buffer.from(JSON.stringify(unsignedTx)), - EthSignType.TRANSACTION - ); - - const signingTx = JSON.parse(Buffer.from(signingData).toString()); - - const isEIP1559 = - !!signingTx.maxFeePerGas || !!signingTx.maxPriorityFeePerGas; - if (isEIP1559) { - signingTx.type = 2; - } - - const signedTx = Buffer.from( - serializeEVMTransaction(signingTx, signature).replace("0x", ""), - "hex" - ); - - const txHash = - await this.backgroundTxEthereumService.sendEthereumTx( - origin, - currentChainId, - signedTx, - {} - ); - - return txHash; - } catch (e) { - if ( - (e instanceof Error && e.message === "Request rejected") || - e === "Request rejected" - ) { - throw new EthereumProviderRpcError(4001, "User Rejected Request"); - } - throw e; - } - } - case "eth_signTransaction": { - const tx = - (Array.isArray(params) && - (params?.[0] as { - chainId?: string | number; - from: string; - value?: string; - gas?: string; - gasLimit?: string; - authorizationList?: { - address: string; - chainId: string; - nonce: string; - r: string; - s: string; - yParity: string; - }[]; - })) || - null; - if (!tx) { - throw new EthereumProviderRpcError( - -32602, - "Must provide a transaction." - ); - } - - const currentChainId = this.forceGetCurrentChainId(origin, chainId); - - const { from: sender, authorizationList, ...restTx } = tx; - - if (authorizationList) { - throw new Error("EIP-7702 transactions are not supported."); - } - - if (tx.chainId) { - const evmChainIdFromTx: number = validateEVMChainId( - (() => { - if (typeof tx.chainId === "string") { - if (tx.chainId.startsWith("0x")) { - return parseInt(tx.chainId, 16); - } else { - return parseInt(tx.chainId, 10); - } - } else { - return tx.chainId; - } - })() - ); - if (evmChainIdFromTx !== this.getEVMChainId(currentChainId)) { - throw new Error( - "The current active chain id does not match the one in the transaction." - ); - } - } - - const pubkey = await this.keyRingService.getPubKeySelected( - currentChainId - ); - const selectedAddress = `0x${Buffer.from( - pubkey.getEthAddress() - ).toString("hex")}`; - - const transactionCount = await this.request( + case "eth_signTransaction": + return this.handleSignTransaction( env, origin, - "eth_getTransactionCount", - [selectedAddress, "pending"], + params, providerId, chainId ); - const nonce = parseInt(transactionCount, 16); - - const unsignedTx = this.normalizeUnsignedTx( - restTx, - currentChainId, - nonce - ); - - try { - const { signingData, signature } = await this.signEthereumSelected( - env, - origin, - currentChainId, - sender, - Buffer.from(JSON.stringify(unsignedTx)), - EthSignType.TRANSACTION - ); - - const signingTx = JSON.parse(Buffer.from(signingData).toString()); - - const isEIP1559 = - !!signingTx.maxFeePerGas || !!signingTx.maxPriorityFeePerGas; - if (isEIP1559) { - signingTx.type = 2; - } - - const signedTx = serializeEVMTransaction(signingTx, signature); - - return signedTx; - } catch (e) { - if ( - (e instanceof Error && e.message === "Request rejected") || - e === "Request rejected" - ) { - throw new EthereumProviderRpcError(4001, "User Rejected Request"); - } - throw e; - } - } - case "personal_sign": { - const message = - (Array.isArray(params) && (params?.[0] as string)) || undefined; - if (!message) { - throw new EthereumProviderRpcError( - -32602, - "Must provide a stringified message." - ); - } - - const signer = - (Array.isArray(params) && (params?.[1] as string)) || undefined; - if (!signer || (signer && !signer.match(/^0x[0-9A-Fa-f]*$/))) { - throw new EthereumProviderRpcError( - -32602, - "Must provide an Ethereum address." - ); - } - - const currentChainId = this.forceGetCurrentChainId(origin, chainId); - try { - const { signature } = await this.signEthereumSelected( - env, - origin, - currentChainId, - signer, - message.startsWith("0x") - ? Buffer.from(message.slice(2), "hex") - : Buffer.from(message, "utf8"), - EthSignType.MESSAGE - ); - - return `0x${Buffer.from(signature).toString("hex")}`; - } catch (e) { - if ( - (e instanceof Error && e.message === "Request rejected") || - e === "Request rejected" - ) { - throw new EthereumProviderRpcError(4001, "User Rejected Request"); - } - throw e; - } - } + case "personal_sign": + return this.handlePersonalSign(env, origin, params, chainId); case "eth_signTypedData_v3": // NOTE: v3 is deprecated - case "eth_signTypedData_v4": { - const signer = - (Array.isArray(params) && (params?.[0] as string)) || undefined; - if (!signer || (signer && !signer.match(/^0x[0-9A-Fa-f]*$/))) { - throw new EthereumProviderRpcError( - -32602, - "Must provide an Ethereum address." - ); - } - - const typedData = - (Array.isArray(params) && (params?.[1] as any)) || undefined; - - const currentChainId = this.forceGetCurrentChainId(origin, chainId); - - const { value: validatedTypedData, error } = - EIP712MessageValidator.validate( - typeof typedData === "string" ? JSON.parse(typedData) : typedData - ); - if (error) { - throw new EthereumProviderRpcError( - -32602, - "Must provide a valid EIP712 data format" - ); - } - - const chainIdFromDomain = validatedTypedData.domain["chainId"] as - | string - | number - | undefined; - if (chainIdFromDomain !== undefined) { - const chainIdFromDomainNumber = validateEVMChainId( - typeof chainIdFromDomain === "string" - ? parseInt(chainIdFromDomain) - : chainIdFromDomain - ); - const currentChainIdNumber = this.getEVMChainId(currentChainId); - - if (chainIdFromDomainNumber !== currentChainIdNumber) { - throw new EthereumProviderRpcError( - -32602, - `The current active chain id ${currentChainIdNumber} does not match the one in the domain ${chainIdFromDomainNumber}.` - ); - } - } - - try { - const { signature } = await this.signEthereumSelected( - env, - origin, - currentChainId, - signer, - Buffer.from( - typeof typedData === "string" - ? typedData - : JSON.stringify(typedData) - ), - EthSignType.EIP712 - ); - - return `0x${Buffer.from(signature).toString("hex")}`; - } catch (e) { - if ( - (e instanceof Error && e.message === "Request rejected") || - e === "Request rejected" - ) { - throw new EthereumProviderRpcError(4001, "User Rejected Request"); - } - throw e; - } - } - case "eth_subscribe": { - const currentChainId = this.forceGetCurrentChainId(origin, chainId); - const currentChainEVMInfo = - this.chainsService.getEVMInfoOrThrow(currentChainId); - if (!currentChainEVMInfo.websocket) { - throw new Error( - `WebSocket endpoint for current chain has not been provided to Keplr.` - ); - } - - const ws = new WebSocket(currentChainEVMInfo.websocket); - const subscriptionId: string = await new Promise( - (resolve, reject) => { - const handleOpen = () => { - ws.send( - JSON.stringify({ - jsonrpc: "2.0", - id: 1, - method, - params, - }) - ); - }; - const handleMessage = (event: MessageEvent) => { - const eventData = JSON.parse(event.data); - if (eventData.error) { - ws.close(); - - reject(eventData.error); - } else { - if (eventData.method === "eth_subscription") { - this.interactionService.dispatchEvent( - WEBPAGE_PORT, - "keplr_ethSubscription", - { - origin, - providerId, - data: { - subscription: eventData.params.subscription, - result: eventData.params.result, - }, - } - ); - } else { - resolve(eventData.result); - } - } - }; - const handleError = () => { - ws.close(); - - reject( - new Error( - "Something went wrong with the WebSocket connection" - ) - ); - }; - - ws.addEventListener("open", handleOpen); - ws.addEventListener("message", handleMessage); - ws.addEventListener("error", handleError); - ws.addEventListener( - "close", - () => { - ws.removeEventListener("open", handleOpen); - ws.removeEventListener("message", handleMessage); - ws.removeEventListener("error", handleError); - }, - { once: true } - ); - } + case "eth_signTypedData_v4": + return this.handleSignTypedData(env, origin, params, chainId); + case "eth_subscribe": + return this.wsManager.subscribe( + origin, + method, + params, + providerId, + chainId ); - runInAction(() => { - const key = `${subscriptionId}/${providerId}`; - this.websocketSubscriptionMap.set(key, ws); - }); - - return subscriptionId; - } - case "eth_unsubscribe": { - const subscriptionId = - (Array.isArray(params) && (params?.[0] as string)) || undefined; - if (!subscriptionId) { - throw new EthereumProviderRpcError( - -32602, - "Must provide a subscription id." - ); - } - - const currentChainId = this.forceGetCurrentChainId(origin, chainId); - const currentChainEVMInfo = - this.chainsService.getEVMInfoOrThrow(currentChainId); - if (!currentChainEVMInfo.websocket) { - throw new Error( - `WebSocket endpoint for current chain has not been provided to Keplr.` - ); - } - - const subscribedWs = - this.websocketSubscriptionMap.get(subscriptionId); - if (!subscribedWs) { - return false; - } - - const ws = new WebSocket(currentChainEVMInfo.websocket); - const result = await new Promise((resolve, reject) => { - const handleOpen = () => { - ws.send( - JSON.stringify({ - jsonrpc: "2.0", - id: 1, - method, - params, - }) - ); - }; - const handleMessage = (event: MessageEvent) => { - ws.close(); - - const eventData = JSON.parse(event.data); - if (eventData.error) { - reject(eventData.error); - } else { - subscribedWs.close(); - runInAction(() => { - const key = `${subscriptionId}/${providerId}`; - this.websocketSubscriptionMap.delete(key); - }); - resolve(eventData.result); - } - }; - const handleError = () => { - ws.close(); - - reject( - new Error("Something went wrong with the WebSocket connection") - ); - }; - - ws.addEventListener("open", handleOpen); - ws.addEventListener("message", handleMessage); - ws.addEventListener("error", handleError); - ws.addEventListener( - "close", - () => { - ws.removeEventListener("open", handleOpen); - ws.removeEventListener("message", handleMessage); - ws.removeEventListener("error", handleError); - }, - { once: true } - ); - }); - - return result; - } - case "wallet_switchEthereumChain": { - const newCurrentChainId = this.getNewCurrentChainIdFromRequest( + case "eth_unsubscribe": + return this.wsManager.unsubscribe( + origin, method, - params + params, + providerId, + chainId ); - const currentChainId = this.getCurrentChainId(origin, chainId); - if ( - // If the new current chain id is not set or the current chain id is the same as the new current chain id, do nothing. - newCurrentChainId == null || - currentChainId === newCurrentChainId - ) { - return null; - } - - try { - await this.permissionService.updateCurrentChainIdForEVM( - env, - origin, - newCurrentChainId - ); - } catch (e) { - if ( - (e instanceof Error && e.message === "Request rejected") || - e === "Request rejected" - ) { - throw new EthereumProviderRpcError(4001, "User Rejected Request"); - } - throw e; - } - - return null; - } - case "wallet_addEthereumChain": { - const param = - Array.isArray(params) && - (params?.[0] as { - chainId: string; - chainName: string; - nativeCurrency: { - name: string; - symbol: string; - decimals: number; - }; - rpcUrls: string[]; - iconUrls?: string[]; - }); - if (!param || typeof param !== "object") { - throw new EthereumProviderRpcError( - -32602, - "Must provide a single object parameter." - ); - } - - const evmChainId = validateEVMChainId(parseInt(param.chainId, 16)); - - const chainInfo = - this.chainsService.getChainInfoByEVMChainId(evmChainId) ?? - (await (async () => { - const rpc = param.rpcUrls.find((url) => { - try { - const urlObject = new URL(url); - return ( - urlObject.protocol === "http:" || - urlObject.protocol === "https:" - ); - } catch { - return false; - } - }); - const websocket = param.rpcUrls.find((url) => { - try { - const urlObject = new URL(url); - return ( - urlObject.protocol === "ws:" || - urlObject.protocol === "wss:" - ); - } catch { - return false; - } - }); - // Skip the validation for these parameters because they will be validated in the `suggestChainInfo` method. - const { chainName, nativeCurrency, iconUrls } = param; - - const addingChainInfo = { - rpc, - rest: rpc, - chainId: `eip155:${evmChainId}`, - bip44: { - coinType: 60, - }, - chainName, - stakeCurrency: { - coinDenom: nativeCurrency.symbol, - coinMinimalDenom: nativeCurrency.symbol, - coinDecimals: nativeCurrency.decimals, - }, - currencies: [ - { - coinDenom: nativeCurrency.symbol, - coinMinimalDenom: nativeCurrency.symbol, - coinDecimals: nativeCurrency.decimals, - }, - ], - feeCurrencies: [ - { - coinDenom: nativeCurrency.symbol, - coinMinimalDenom: nativeCurrency.symbol, - coinDecimals: nativeCurrency.decimals, - }, - ], - evm: { - chainId: evmChainId, - rpc, - websocket, - }, - features: ["eth-address-gen", "eth-key-sign"], - chainSymbolImageUrl: - iconUrls && typeof iconUrls[0] === "string" && !!iconUrls[0] - ? iconUrls[0] - : undefined, - beta: true, - } as ChainInfo; - - try { - await this.chainsService.suggestChainInfo( - env, - addingChainInfo, - origin - ); - } catch (e) { - if ( - (e instanceof Error && e.message === "Request rejected") || - e === "Request rejected" - ) { - throw new EthereumProviderRpcError( - 4001, - "User Rejected Request" - ); - } - throw e; - } - - return addingChainInfo; - })()); - - this.permissionService.addPermission( - [chainInfo.chainId], - getBasicAccessPermissionType(), - [origin] + case "wallet_switchEthereumChain": + return this.chainHandler.switchChain( + env, + origin, + method, + params, + chainId ); - - try { - await this.permissionService.updateCurrentChainIdForEVM( - env, - origin, - chainInfo.chainId - ); - } catch (e) { - if ( - (e instanceof Error && e.message === "Request rejected") || - e === "Request rejected" - ) { - throw new EthereumProviderRpcError(4001, "User Rejected Request"); - } - throw e; - } - - return null; - } + case "wallet_addEthereumChain": + return this.chainHandler.addChain(env, origin, params, chainId); case "wallet_getPermissions": // This `request` method can be executed if the basic access permission is granted. // So, it's not necessary to check or grant the permission here. - case "wallet_requestPermissions": { + case "wallet_requestPermissions": return [{ parentCapability: "eth_accounts" }]; - } - case "wallet_revokePermissions": { - const param = - Array.isArray(params) && (params?.[0] as Record); - if (!param || typeof param !== "object") { - throw new EthereumProviderRpcError( - -32602, - "Must provide a single object parameter." - ); - } - - if (param["eth_accounts"] == null) { - throw new EthereumProviderRpcError( - -32602, - "Must provide a single object parameter with the key 'eth_accounts'." - ); - } - - await this.permissionService.removeAllTypePermission([origin]); - - return null; - } - case "wallet_watchAsset": { - const param = params as - | { - type: string; - options: { - address: string; - symbol?: string; - decimals?: number; - image?: string; - tokenId?: string; - }; - } - | undefined; - if (!param) { - throw new EthereumProviderRpcError( - -32602, - "Must provide a single object parameter." - ); - } - - if (param.type !== "ERC20") { - throw new EthereumProviderRpcError( - -32602, - "Must provide a valid asset type. Only ERC20 is supported." - ); - } - - const contractAddress = param?.options.address; - - const currentChainId = this.forceGetCurrentChainId(origin, chainId); - - try { - await this.tokenERC20Service.suggestERC20Token( - env, - currentChainId, - contractAddress - ); - } catch (e) { - if ( - (e instanceof Error && e.message === "Request rejected") || - e === "Request rejected" - ) { - throw new EthereumProviderRpcError(4001, "User Rejected Request"); - } - throw e; - } - - return true; - } + case "wallet_revokePermissions": + return this.chainHandler.revokePermissions(origin, params); + case "wallet_watchAsset": + return this.chainHandler.watchAsset(env, origin, params, chainId); case "eth_call": case "eth_estimateGas": case "eth_getTransactionCount": case "eth_getTransactionByHash": case "eth_getTransactionByBlockHashAndIndex": case "eth_getTransactionByBlockNumberAndIndex": - case "eth_getTransactionByHash": case "eth_getTransactionReceipt": case "eth_sendRawTransaction": case "eth_protocolVersion": @@ -1296,78 +535,257 @@ export class KeyRingEthereumService { case "eth_getBlockByNumber": case "eth_gasPrice": case "eth_feeHistory": - case "eth_maxPriorityFeePerGas": { - const currentChainId = this.forceGetCurrentChainId(origin, chainId); - const currentChainEVMInfo = - this.chainsService.getEVMInfoOrThrow(currentChainId); - - return ( - await simpleFetch>(currentChainEVMInfo.rpc, { - method: "POST", - headers: { - "content-type": "application/json", - "request-source": origin, - }, - body: JSON.stringify({ - jsonrpc: "2.0", - method, - params, - id: 1, - }), - }) - ).data.result; - } - default: { - throw new EthereumProviderRpcError(4200, `Unsupported Method`); - } + case "eth_maxPriorityFeePerGas": + return this.chainHandler.passthroughRpc( + origin, + method, + params, + chainId + ); + default: + throw new EthereumProviderRpcError( + RPC_ERROR_UNSUPPORTED_METHOD, + `Unsupported Method` + ); } })()) as T; return result; } - getNewCurrentChainIdFromRequest( - method: string, - params?: unknown[] | Record - ): string | undefined { - switch (method) { - case "wallet_switchEthereumChain": { - const param = - (Array.isArray(params) && (params?.[0] as { chainId: string })) || - undefined; - if (!param?.chainId) { - throw new EthereumProviderRpcError( - -32602, - "Must provide a chain id." - ); - } + // ── RPC Handlers ───────────────────────────────────────── - const newEvmChainId = validateEVMChainId(parseInt(param.chainId, 16)); - const chainInfo = - this.chainsService.getChainInfoByEVMChainId(newEvmChainId); - if (!chainInfo) { - throw new EthereumProviderRpcError( - 4902, - `Unrecognized chain ID "${newEvmChainId}". Try adding the chain using wallet_addEthereumChain first.` - ); - } + private async handleInitProviderState(origin: string, chainId?: string) { + const currentChainId = this.getCurrentChainId(origin, chainId); + if (currentChainId == null) { + return { + currentEvmChainId: null, + currentChainId: null, + selectedAddress: null, + }; + } - return chainInfo.chainId; - } - default: { - return; - } + try { + const selectedAddress = await this.getSelectedEthAddress(currentChainId); + + return { + currentEvmChainId: this.getEVMChainId(currentChainId), + currentChainId: currentChainId, + selectedAddress, + }; + } catch (e) { + console.error(e); + return null; } } - checkNeedEnableAccess(method: string) { - if (enableAccessSkippedEVMJSONRPCMethods.includes(method)) { - return false; + private async handleConnect(origin: string, chainId?: string) { + try { + const currentChainId = this.forceGetCurrentChainId(origin, chainId); + const selectedAddress = await this.getSelectedEthAddress(currentChainId); + + return { + currentEvmChainId: this.getEVMChainId(currentChainId), + currentChainId: currentChainId, + selectedAddress, + }; + } catch (e) { + console.error(e); + return null; + } + } + + private async handleEthAccounts(origin: string, chainId?: string) { + const currentChainId = this.getCurrentChainId(origin, chainId); + if (currentChainId == null) { + return []; } - return true; + const selectedAddress = await this.getSelectedEthAddress(currentChainId); + + return [selectedAddress]; } + private async handleSendTransaction( + env: Env, + origin: string, + params: unknown[] | Record | undefined, + providerId: string | undefined, + chainId: string | undefined + ) { + try { + const { signedTxHex } = await this.prepareAndSignTransaction( + env, + origin, + params, + providerId, + chainId + ); + + const currentChainId = this.forceGetCurrentChainId(origin, chainId); + const signedTx = Buffer.from(signedTxHex.replace("0x", ""), "hex"); + + return await this.backgroundTxEthereumService.sendEthereumTx( + origin, + currentChainId, + signedTx, + {} + ); + } catch (e) { + rethrowAsProviderError(e); + } + } + + private async handleSignTransaction( + env: Env, + origin: string, + params: unknown[] | Record | undefined, + providerId: string | undefined, + chainId: string | undefined + ) { + try { + const { signedTxHex } = await this.prepareAndSignTransaction( + env, + origin, + params, + providerId, + chainId + ); + return signedTxHex; + } catch (e) { + rethrowAsProviderError(e); + } + } + + private async handlePersonalSign( + env: Env, + origin: string, + params: unknown[] | Record | undefined, + chainId: string | undefined + ) { + const message = + (Array.isArray(params) && (params?.[0] as string)) || undefined; + if (!message) { + throw new EthereumProviderRpcError( + RPC_ERROR_INVALID_PARAMS, + "Must provide a stringified message." + ); + } + + const signer = + (Array.isArray(params) && (params?.[1] as string)) || undefined; + if (!signer || (signer && !signer.match(/^0x[0-9A-Fa-f]*$/))) { + throw new EthereumProviderRpcError( + RPC_ERROR_INVALID_PARAMS, + "Must provide an Ethereum address." + ); + } + + const currentChainId = this.forceGetCurrentChainId(origin, chainId); + try { + const { signature } = await this.signEthereumSelected( + env, + origin, + currentChainId, + signer, + message.startsWith("0x") + ? Buffer.from(message.slice(2), "hex") + : Buffer.from(message, "utf8"), + EthSignType.MESSAGE + ); + + return `0x${Buffer.from(signature).toString("hex")}`; + } catch (e) { + rethrowAsProviderError(e); + } + } + + private async handleSignTypedData( + env: Env, + origin: string, + params: unknown[] | Record | undefined, + chainId: string | undefined + ) { + const signer = + (Array.isArray(params) && (params?.[0] as string)) || undefined; + if (!signer || (signer && !signer.match(/^0x[0-9A-Fa-f]*$/))) { + throw new EthereumProviderRpcError( + RPC_ERROR_INVALID_PARAMS, + "Must provide an Ethereum address." + ); + } + + const typedData = + (Array.isArray(params) && + (params?.[1] as string | Record)) || + undefined; + + const currentChainId = this.forceGetCurrentChainId(origin, chainId); + + const { value: validatedTypedData, error } = + EIP712MessageValidator.validate( + typeof typedData === "string" ? JSON.parse(typedData) : typedData + ); + if (error) { + throw new EthereumProviderRpcError( + RPC_ERROR_INVALID_PARAMS, + "Must provide a valid EIP712 data format" + ); + } + + const chainIdFromDomain = validatedTypedData.domain["chainId"] as + | string + | number + | undefined; + if (chainIdFromDomain !== undefined) { + const chainIdFromDomainNumber = validateEVMChainId( + typeof chainIdFromDomain === "string" + ? parseInt(chainIdFromDomain) + : chainIdFromDomain + ); + const currentChainIdNumber = this.getEVMChainId(currentChainId); + + if (chainIdFromDomainNumber !== currentChainIdNumber) { + throw new EthereumProviderRpcError( + RPC_ERROR_INVALID_PARAMS, + `The current active chain id ${currentChainIdNumber} does not match the one in the domain ${chainIdFromDomainNumber}.` + ); + } + } + + try { + const { signature } = await this.signEthereumSelected( + env, + origin, + currentChainId, + signer, + Buffer.from( + typeof typedData === "string" ? typedData : JSON.stringify(typedData) + ), + EthSignType.EIP712 + ); + + return `0x${Buffer.from(signature).toString("hex")}`; + } catch (e) { + rethrowAsProviderError(e); + } + } + + // ── Public Utilities (delegated to chainHandler) ────────── + + getNewCurrentChainIdFromRequest( + method: string, + params?: unknown[] | Record + ): string | undefined { + return this.chainHandler.getNewCurrentChainIdFromRequest(method, params); + } + + checkNeedEnableAccess(method: string) { + return this.chainHandler.checkNeedEnableAccess(method); + } + + // ── Private Helpers ─────────────────────────────────────── + private getCurrentChainId(origin: string, chainId?: string) { return chainId || this.permissionService.getCurrentChainIdForEVM(origin); } @@ -1376,7 +794,7 @@ export class KeyRingEthereumService { return ( this.getCurrentChainId(origin, chainId) || // If the current chain id is not set, use Ethereum mainnet as the default chain id. - "eip155:1" + DEFAULT_EVM_CHAIN_ID ); } @@ -1386,86 +804,72 @@ export class KeyRingEthereumService { return evmInfo.chainId; } - private toHexQty(x?: string | number | bigint): string | undefined { - if (x === undefined || x === null) { - return undefined; - } + private async getSelectedEthAddress(chainId: string): Promise { + const pubkey = await this.keyRingService.getPubKeySelected(chainId); + return getEthAddressFromPubkey(pubkey); + } - if (typeof x === "bigint") { - return `0x${x.toString(16)}`; - } + private async prepareAndSignTransaction( + env: Env, + origin: string, + params: unknown[] | Record | undefined, + providerId: string | undefined, + chainId: string | undefined + ): Promise<{ signedTxHex: string }> { + const tx = parseTxParam(params); - if (typeof x === "number") { - const n = Number.isFinite(x) ? Math.max(0, Math.floor(x)) : 0; - return `0x${n.toString(16)}`; - } + const currentChainId = this.forceGetCurrentChainId(origin, chainId); - x = x.trim(); - if (x.length === 0 || x === "0x") { - return undefined; - } + const { from: sender, authorizationList, ...restTx } = tx; - let q: bigint; + if (authorizationList) { + throw new Error("EIP-7702 transactions are not supported."); + } - try { - q = BigInt(x); - } catch { - if (!x.startsWith("0x")) { - x = `0x${x}`; - } - try { - q = BigInt(x); - } catch { - return undefined; + if (tx.chainId) { + const evmChainIdFromTx: number = validateEVMChainId( + parseChainIdParam(tx.chainId) + ); + if (evmChainIdFromTx !== this.getEVMChainId(currentChainId)) { + throw new Error( + "The current active chain id does not match the one in the transaction." + ); } } - return `0x${q.toString(16)}`; - } + const selectedAddress = await this.getSelectedEthAddress(currentChainId); - private normalizeUnsignedTx( - tx: any, - chainId: string, - nonce: number - ): UnsignedEVMTransaction { - const { - value, - gas, - gasLimit, - gasPrice, - maxFeePerGas, - maxPriorityFeePerGas, - data, - ...restTx - } = tx; - - const unsignedTx: UnsignedEVMTransaction = { - ...restTx, - value: this.toHexQty(value), - gasLimit: this.toHexQty(gasLimit ?? gas), - gasPrice: this.toHexQty(gasPrice), - maxFeePerGas: this.toHexQty(maxFeePerGas), - maxPriorityFeePerGas: this.toHexQty(maxPriorityFeePerGas), - chainId: this.getEVMChainId(chainId), - nonce, - }; + const transactionCount = await this.request( + env, + origin, + "eth_getTransactionCount", + [selectedAddress, "pending"], + providerId, + chainId + ); + const nonce = parseInt(transactionCount, 16); - if (data != null && typeof data === "string") { - unsignedTx.data = data.startsWith("0x") ? data : `0x${data}`; - } + const unsignedTx = normalizeUnsignedTx( + restTx, + this.getEVMChainId(currentChainId), + nonce + ); + + const { signingData, signature } = await this.signEthereumSelected( + env, + origin, + currentChainId, + sender, + Buffer.from(JSON.stringify(unsignedTx)), + EthSignType.TRANSACTION + ); - const isLegacy = unsignedTx.gasPrice !== undefined; - const isEIP1559 = - unsignedTx.maxFeePerGas !== undefined && - unsignedTx.maxPriorityFeePerGas !== undefined; + const signingTx = JSON.parse(Buffer.from(signingData).toString()); - if (isEIP1559) { - delete unsignedTx.gasPrice; - } else if (isLegacy) { - delete unsignedTx.maxFeePerGas; - delete unsignedTx.maxPriorityFeePerGas; - } + applyEIP1559Type(signingTx); + + const signedTxHex = serializeEVMTransaction(signingTx, signature); - return unsignedTx; + return { signedTxHex }; } } diff --git a/packages/background/src/keyring-ethereum/ws-handlers.ts b/packages/background/src/keyring-ethereum/ws-handlers.ts new file mode 100644 index 0000000000..489fdf65a6 --- /dev/null +++ b/packages/background/src/keyring-ethereum/ws-handlers.ts @@ -0,0 +1,188 @@ +import { EthereumProviderRpcError, WEBPAGE_PORT } from "@keplr-wallet/router"; +import { ChainsService } from "../chains"; +import { InteractionService } from "../interaction"; +import { PermissionService } from "../permission"; +import { runInAction } from "mobx"; +import { DEFAULT_EVM_CHAIN_ID, RPC_ERROR_INVALID_PARAMS } from "./constants"; + +export class EvmWebSocketManager { + protected subscriptionMap = new Map(); + + constructor( + private readonly chainsService: ChainsService, + private readonly interactionService: InteractionService, + private readonly permissionService: PermissionService + ) {} + + // ── Private Helpers ────────────────────────────────────── + + private forceGetCurrentChainId(origin: string, chainId?: string): string { + return ( + chainId || + this.permissionService.getCurrentChainIdForEVM(origin) || + DEFAULT_EVM_CHAIN_ID + ); + } + + // ── Subscription Handlers ───────────────────────────────── + + async subscribe( + origin: string, + method: string, + params: unknown[] | Record | undefined, + providerId: string | undefined, + chainId: string | undefined + ) { + const currentChainId = this.forceGetCurrentChainId(origin, chainId); + const currentChainEVMInfo = + this.chainsService.getEVMInfoOrThrow(currentChainId); + if (!currentChainEVMInfo.websocket) { + throw new Error( + `WebSocket endpoint for current chain has not been provided to Keplr.` + ); + } + + const ws = new WebSocket(currentChainEVMInfo.websocket); + const subscriptionId: string = await new Promise((resolve, reject) => { + const handleOpen = () => { + ws.send( + JSON.stringify({ + jsonrpc: "2.0", + id: 1, + method, + params, + }) + ); + }; + const handleMessage = (event: MessageEvent) => { + const eventData = JSON.parse(event.data); + if (eventData.error) { + ws.close(); + + reject(eventData.error); + } else { + if (eventData.method === "eth_subscription") { + this.interactionService.dispatchEvent( + WEBPAGE_PORT, + "keplr_ethSubscription", + { + origin, + providerId, + data: { + subscription: eventData.params.subscription, + result: eventData.params.result, + }, + } + ); + } else { + resolve(eventData.result); + } + } + }; + const handleError = () => { + ws.close(); + + reject(new Error("Something went wrong with the WebSocket connection")); + }; + + ws.addEventListener("open", handleOpen); + ws.addEventListener("message", handleMessage); + ws.addEventListener("error", handleError); + ws.addEventListener( + "close", + () => { + ws.removeEventListener("open", handleOpen); + ws.removeEventListener("message", handleMessage); + ws.removeEventListener("error", handleError); + }, + { once: true } + ); + }); + runInAction(() => { + const key = `${subscriptionId}/${providerId}`; + this.subscriptionMap.set(key, ws); + }); + + return subscriptionId; + } + + async unsubscribe( + origin: string, + method: string, + params: unknown[] | Record | undefined, + providerId: string | undefined, + chainId: string | undefined + ) { + const subscriptionId = + (Array.isArray(params) && (params?.[0] as string)) || undefined; + if (!subscriptionId) { + throw new EthereumProviderRpcError( + RPC_ERROR_INVALID_PARAMS, + "Must provide a subscription id." + ); + } + + const currentChainId = this.forceGetCurrentChainId(origin, chainId); + const currentChainEVMInfo = + this.chainsService.getEVMInfoOrThrow(currentChainId); + if (!currentChainEVMInfo.websocket) { + throw new Error( + `WebSocket endpoint for current chain has not been provided to Keplr.` + ); + } + + const subscribedWs = this.subscriptionMap.get(subscriptionId); + if (!subscribedWs) { + return false; + } + + const ws = new WebSocket(currentChainEVMInfo.websocket); + const result = await new Promise((resolve, reject) => { + const handleOpen = () => { + ws.send( + JSON.stringify({ + jsonrpc: "2.0", + id: 1, + method, + params, + }) + ); + }; + const handleMessage = (event: MessageEvent) => { + ws.close(); + + const eventData = JSON.parse(event.data); + if (eventData.error) { + reject(eventData.error); + } else { + subscribedWs.close(); + runInAction(() => { + const key = `${subscriptionId}/${providerId}`; + this.subscriptionMap.delete(key); + }); + resolve(eventData.result); + } + }; + const handleError = () => { + ws.close(); + + reject(new Error("Something went wrong with the WebSocket connection")); + }; + + ws.addEventListener("open", handleOpen); + ws.addEventListener("message", handleMessage); + ws.addEventListener("error", handleError); + ws.addEventListener( + "close", + () => { + ws.removeEventListener("open", handleOpen); + ws.removeEventListener("message", handleMessage); + ws.removeEventListener("error", handleError); + }, + { once: true } + ); + }); + + return result; + } +} From 5bc7d73c41c3bf3d1cefa193239fe96fab8285ed Mon Sep 17 00:00:00 2001 From: jungcome7 Date: Tue, 24 Mar 2026 19:21:37 +0900 Subject: [PATCH 19/64] refactor: rename single-letter variables in toHexQty for clarity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit x → value, n → clamped, q → parsed Co-Authored-By: Claude Opus 4.6 (1M context) --- .../background/src/keyring-ethereum/helper.ts | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/background/src/keyring-ethereum/helper.ts b/packages/background/src/keyring-ethereum/helper.ts index 438b1bee35..8248eab3b8 100644 --- a/packages/background/src/keyring-ethereum/helper.ts +++ b/packages/background/src/keyring-ethereum/helper.ts @@ -124,41 +124,41 @@ export function parseTxParam( // ── Hex quantity conversion ── -export function toHexQty(x?: string | number | bigint): string | undefined { - if (x === undefined || x === null) { +export function toHexQty(value?: string | number | bigint): string | undefined { + if (value === undefined || value === null) { return undefined; } - if (typeof x === "bigint") { - return `0x${x.toString(16)}`; + if (typeof value === "bigint") { + return `0x${value.toString(16)}`; } - if (typeof x === "number") { - const n = Number.isFinite(x) ? Math.max(0, Math.floor(x)) : 0; - return `0x${n.toString(16)}`; + if (typeof value === "number") { + const clamped = Number.isFinite(value) ? Math.max(0, Math.floor(value)) : 0; + return `0x${clamped.toString(16)}`; } - x = x.trim(); - if (x.length === 0 || x === "0x") { + value = value.trim(); + if (value.length === 0 || value === "0x") { return undefined; } - let q: bigint; + let parsed: bigint; try { - q = BigInt(x); + parsed = BigInt(value); } catch { - if (!x.startsWith("0x")) { - x = `0x${x}`; + if (!value.startsWith("0x")) { + value = `0x${value}`; } try { - q = BigInt(x); + parsed = BigInt(value); } catch { return undefined; } } - return `0x${q.toString(16)}`; + return `0x${parsed.toString(16)}`; } // ── Transaction normalization ── From e592176044c7f8e9a7c250b81786fa4c420fa736 Mon Sep 17 00:00:00 2001 From: jungcome7 Date: Tue, 24 Mar 2026 19:32:05 +0900 Subject: [PATCH 20/64] feat: add EIP-7702 delegation status check (KEPLR-2026) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement wallet_getCapabilities RPC handler to check whether an EOA is delegated to a trusted smart account contract via EIP-7702. - types: DelegationStatus, GetCapabilitiesResult - helper: parseDelegation() — parse eth_getCode response - service: getSmartAccountCapabilities() + dispatch handler - constants: ALLOWED_DELEGATORS, EIP7702_DELEGATION_PREFIX - feature flag: "eip-7702" on 11 EVM chains - 7 unit tests for parseDelegation Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/extension/src/config.ts | 22 +++---- .../src/keyring-ethereum/constants.ts | 11 ++++ .../src/keyring-ethereum/helper.spec.ts | 43 +++++++++++++ .../background/src/keyring-ethereum/helper.ts | 21 +++++++ .../src/keyring-ethereum/service.ts | 63 +++++++++++++++++++ packages/chain-validator/src/feature.ts | 4 ++ packages/types/src/ethereum.ts | 4 ++ 7 files changed, 157 insertions(+), 11 deletions(-) diff --git a/apps/extension/src/config.ts b/apps/extension/src/config.ts index faea046894..86e5d9c82e 100644 --- a/apps/extension/src/config.ts +++ b/apps/extension/src/config.ts @@ -1833,7 +1833,7 @@ export const EmbedChainInfos: (ChainInfo | ModularChainInfo)[] = [ "https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/eip155:1/chain.png", }, ], - features: [], + features: ["eip-7702"], }, { rpc: "https://evm-8453.keplr.app", @@ -1870,7 +1870,7 @@ export const EmbedChainInfos: (ChainInfo | ModularChainInfo)[] = [ "https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/eip155:1/chain.png", }, ], - features: ["op-stack-l1-data-fee"], + features: ["op-stack-l1-data-fee", "eip-7702"], }, { rpc: "https://evm-10.keplr.app", @@ -1907,7 +1907,7 @@ export const EmbedChainInfos: (ChainInfo | ModularChainInfo)[] = [ "https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/eip155:1/chain.png", }, ], - features: ["op-stack-l1-data-fee"], + features: ["op-stack-l1-data-fee", "eip-7702"], }, { rpc: "https://evm-42161.keplr.app", @@ -1944,7 +1944,7 @@ export const EmbedChainInfos: (ChainInfo | ModularChainInfo)[] = [ "https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/eip155:1/chain.png", }, ], - features: ["eth-address-gen", "eth-key-sign"], + features: ["eth-address-gen", "eth-key-sign", "eip-7702"], }, { rpc: "https://evm-137.keplr.app", @@ -1981,7 +1981,7 @@ export const EmbedChainInfos: (ChainInfo | ModularChainInfo)[] = [ "https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/eip155:137/chain.png", }, ], - features: [], + features: ["eip-7702"], }, { rpc: "https://evm-56.keplr.app", @@ -2018,7 +2018,7 @@ export const EmbedChainInfos: (ChainInfo | ModularChainInfo)[] = [ "https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/eip155:56/chain.png", }, ], - features: [], + features: ["eip-7702"], }, { rpc: "https://evm-43114.keplr.app", @@ -2741,7 +2741,7 @@ export const EmbedChainInfos: (ChainInfo | ModularChainInfo)[] = [ "https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/eip155:80094/berachain-native.png", }, ], - features: [], + features: ["eip-7702"], }, { rpc: "https://evm-1514.keplr.app", @@ -2813,7 +2813,7 @@ export const EmbedChainInfos: (ChainInfo | ModularChainInfo)[] = [ coinGeckoId: "ethereum", }, ], - features: ["op-stack-l1-data-fee"], + features: ["op-stack-l1-data-fee", "eip-7702"], }, { isV2: true, @@ -3383,7 +3383,7 @@ export const EmbedChainInfos: (ChainInfo | ModularChainInfo)[] = [ "https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/eip155:57073/ink-native.png", }, ], - features: ["op-stack-l1-data-fee"], + features: ["op-stack-l1-data-fee", "eip-7702"], }, { rpc: "https://evm-59144.keplr.app", @@ -3428,7 +3428,7 @@ export const EmbedChainInfos: (ChainInfo | ModularChainInfo)[] = [ "https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/eip155:59144/linea-native.png", }, ], - features: [], + features: ["eip-7702"], }, { chainId: "union-1", @@ -3577,7 +3577,7 @@ export const EmbedChainInfos: (ChainInfo | ModularChainInfo)[] = [ "https://raw.githubusercontent.com/chainapsis/keplr-chain-registry/main/images/eip155:143/monad-native.png", }, ], - features: [], + features: ["eip-7702"], }, { chainId: "zigchain-1", diff --git a/packages/background/src/keyring-ethereum/constants.ts b/packages/background/src/keyring-ethereum/constants.ts index 633a5652aa..28b7c7754d 100644 --- a/packages/background/src/keyring-ethereum/constants.ts +++ b/packages/background/src/keyring-ethereum/constants.ts @@ -25,3 +25,14 @@ export const DEFAULT_EVM_CHAIN_ID = "eip155:1"; // BIP-44 coin type for Ethereum export const ETH_COIN_TYPE = 60; + +// eth_getCode 응답이 이 접두사로 시작하면 해당 EOA가 EIP-7702로 delegation된 상태. +// 전체 형식: 0xef0100 + 20바이트 delegator 주소 = 48자. +export const EIP7702_DELEGATION_PREFIX = "0xef0100"; + +// eth_getCode로 확인된 delegator 주소가 이 목록에 있어야 "supported"로 판정한다. +// 현재는 MetaMask StatelessDeleGator 주소만 포함. CREATE2로 모든 체인에 동일 주소로 배포됨. +// TODO: MetaMask 것을 그대로 쓸지 자체 배포할지 결정 후, 주소 추가 또는 교체. +export const ALLOWED_DELEGATORS: readonly string[] = [ + "0x63c0c19a282a1b52b07dd5a65b58948a07dae32b", +]; diff --git a/packages/background/src/keyring-ethereum/helper.spec.ts b/packages/background/src/keyring-ethereum/helper.spec.ts index 3b7d5ae7df..8280bdeb3e 100644 --- a/packages/background/src/keyring-ethereum/helper.spec.ts +++ b/packages/background/src/keyring-ethereum/helper.spec.ts @@ -7,6 +7,7 @@ import { normalizeUnsignedTx, normalizeSigner, parseChainIdParam, + parseDelegation, parseTxParam, rethrowAsProviderError, toHexQty, @@ -297,3 +298,45 @@ describe("normalizeUnsignedTx", () => { expect(result.to).toBe("0xabc"); }); }); + +// ── parseDelegation ──────────────────────────────────── + +describe("parseDelegation", () => { + const allowedDelegators = ["0x63c0c19a282a1b52b07dd5a65b58948a07dae32b"]; + + it("returns 'ready' for empty EOA (0x)", () => { + expect(parseDelegation("0x", allowedDelegators)).toBe("ready"); + }); + + it("returns 'ready' for empty EOA (0x0)", () => { + expect(parseDelegation("0x0", allowedDelegators)).toBe("ready"); + }); + + it("returns 'supported' for whitelisted delegator", () => { + const code = "0xef010063c0c19a282a1b52b07dd5a65b58948a07dae32b"; + expect(parseDelegation(code, allowedDelegators)).toBe("supported"); + }); + + it("returns 'supported' for mixed-case whitelisted delegator", () => { + const code = "0xef010063C0c19A282a1B52b07dD5a65b58948A07DAE32B"; + expect(parseDelegation(code, allowedDelegators)).toBe("supported"); + }); + + it("returns 'unsupported' for unknown delegator", () => { + const code = "0xef0100aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; + expect(parseDelegation(code, allowedDelegators)).toBe("unsupported"); + }); + + it("returns 'unsupported' for regular contract code", () => { + expect( + parseDelegation("0x608060405234801561001057600080fd", allowedDelegators) + ).toBe("unsupported"); + }); + + it("returns 'unsupported' for wrong-length delegation code", () => { + // 0xef0100 prefix but too short (missing bytes) + expect(parseDelegation("0xef010063c0c19a", allowedDelegators)).toBe( + "unsupported" + ); + }); +}); diff --git a/packages/background/src/keyring-ethereum/helper.ts b/packages/background/src/keyring-ethereum/helper.ts index 8248eab3b8..8687dc30ff 100644 --- a/packages/background/src/keyring-ethereum/helper.ts +++ b/packages/background/src/keyring-ethereum/helper.ts @@ -1,6 +1,7 @@ import { Bech32Address } from "@keplr-wallet/cosmos"; import { EthereumProviderRpcError } from "@keplr-wallet/router"; import { UnsignedEVMTransaction } from "@keplr-wallet/stores-eth"; +import type { DelegationStatus } from "@keplr-wallet/types"; import { Buffer } from "buffer/"; import { @@ -8,8 +9,28 @@ import { RPC_ERROR_USER_REJECTED, SIGNATURE_V_FALSE, SIGNATURE_V_TRUE, + EIP7702_DELEGATION_PREFIX, } from "./constants"; +// ── EIP-7702 Delegation ─────────────────────────────────── + +// eth_getCode 응답을 파싱하여 계정의 delegation 상태를 판별한다. +// 빈 코드면 "ready", 허용된 delegator로 delegation되어 있으면 "supported", 그 외는 "unsupported". +export function parseDelegation( + code: string, + allowedDelegators: readonly string[] +): DelegationStatus { + if (code === "0x" || code === "0x0") { + return "ready"; + } + const lower = code.toLowerCase(); + if (lower.startsWith(EIP7702_DELEGATION_PREFIX) && lower.length === 48) { + const delegator = "0x" + lower.slice(8); + return allowedDelegators.includes(delegator) ? "supported" : "unsupported"; + } + return "unsupported"; +} + // ── Types ── export interface EVMTransactionParam { diff --git a/packages/background/src/keyring-ethereum/service.ts b/packages/background/src/keyring-ethereum/service.ts index 68b62c7340..9a837b6a58 100644 --- a/packages/background/src/keyring-ethereum/service.ts +++ b/packages/background/src/keyring-ethereum/service.ts @@ -8,6 +8,10 @@ import { EthSignType, isEthSignChain, } from "@keplr-wallet/types"; +import type { + DelegationStatus, + GetCapabilitiesResult, +} from "@keplr-wallet/types"; import { Buffer } from "buffer/"; import { domainHash, @@ -30,6 +34,7 @@ import { normalizeUnsignedTx, normalizeSigner, parseChainIdParam, + parseDelegation, parseTxParam, rethrowAsProviderError, validateEVMChainId, @@ -37,6 +42,7 @@ import { import { PermissionInteractiveService } from "../permission-interactive"; import { DEFAULT_EVM_CHAIN_ID, + ALLOWED_DELEGATORS, ERC20_TRANSFER_SELECTOR, RPC_ERROR_INVALID_PARAMS, RPC_ERROR_UNSUPPORTED_METHOD, @@ -77,6 +83,49 @@ export class KeyRingEthereumService { // TODO: ? } + // ── Smart Account ──────────────────────────────────────── + + async getSmartAccountCapabilities( + env: Env, + origin: string, + chainId: string, + address?: string + ): Promise { + const modularChainInfo = + this.chainsService.getModularChainInfoOrThrow(chainId); + const evmInfo = this.chainsService.getEVMInfoOrThrow(chainId); + const hexChainId = `0x${evmInfo.chainId.toString(16)}`; + + const features = + modularChainInfo.type === "evm" + ? modularChainInfo.evm.features + : modularChainInfo.type === "ethermint" + ? modularChainInfo.cosmos.features + : undefined; + + if (!features?.includes("eip-7702")) { + return { [hexChainId]: { atomic: { status: "unsupported" } } }; + } + + const target = address ?? (await this.getSelectedEthAddress(chainId)); + + const code = await this.request( + env, + origin, + "eth_getCode", + [target, "latest"], + undefined, + chainId + ); + + const status: DelegationStatus = parseDelegation( + code ?? "0x", + ALLOWED_DELEGATORS + ); + + return { [hexChainId]: { atomic: { status } } }; + } + // ── Signing ────────────────────────────────────────────── async signEthereumSelected( @@ -515,6 +564,20 @@ export class KeyRingEthereumService { return this.chainHandler.revokePermissions(origin, params); case "wallet_watchAsset": return this.chainHandler.watchAsset(env, origin, params, chainId); + // TODO: ERC-5792 스펙은 모든 지원 체인의 capabilities를 한 번에 반환해야 하지만, 현재는 활성 체인만 반환. + case "wallet_getCapabilities": { + const currentChainId = this.forceGetCurrentChainId(origin, chainId); + const address = + Array.isArray(params) && typeof params[0] === "string" + ? params[0] + : undefined; + return this.getSmartAccountCapabilities( + env, + origin, + currentChainId, + address + ); + } case "eth_call": case "eth_estimateGas": case "eth_getTransactionCount": diff --git a/packages/chain-validator/src/feature.ts b/packages/chain-validator/src/feature.ts index c5368f861d..31214494f6 100644 --- a/packages/chain-validator/src/feature.ts +++ b/packages/chain-validator/src/feature.ts @@ -32,6 +32,10 @@ export const SupportedChainFeatures = [ "eth-secp256k1-initia", "eth-secp256k1-cosmos", "/cosmos.evm.types.v1.ExtensionOptionsWeb3Tx", + // EIP-7702 Smart Account 지원. delegator 컨트랙트가 배포된 체인에만 추가해야 한다. + // TODO: MetaMask delegator를 그대로 쓸지 자체 배포할지 결정 후, + // 그대로 쓴다면 @metamask/delegation-deployments 패키지에서 지원 체인을 읽어오는 방식으로 전환하여 이 플래그는 제거할 수 있다. + "eip-7702", ]; /** diff --git a/packages/types/src/ethereum.ts b/packages/types/src/ethereum.ts index c8cc3e8905..0e9225ee73 100644 --- a/packages/types/src/ethereum.ts +++ b/packages/types/src/ethereum.ts @@ -52,6 +52,10 @@ export interface EthereumSignResponse { signature: Uint8Array; } +export type DelegationStatus = "supported" | "ready" | "unsupported"; + +export type GetCapabilitiesResult = Record>; + export enum EvmGasSimulationOutcome { TX_SIMULATED = "tx-simulated", TX_BUNDLE_SIMULATED = "tx-bundle-simulated", From 34318e8c1d651b4b11f870c4a9a0772ee35e9a7e Mon Sep 17 00:00:00 2001 From: jungcome7 Date: Wed, 25 Mar 2026 14:20:55 +0900 Subject: [PATCH 21/64] feat: add ERC-5792 wallet_getCapabilities handler with address validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit EIP-7702 delegation 판별 위에 ERC-5792 응답 레이어를 추가한다. - handleGetCapabilities: 주소 형식(-32602)·소유권(4100) 검증 - getAtomicCapability: delegation 상태를 ERC-5792 atomic capability 형식으로 반환 - 미지원 체인 키 생략, RPC 실패 시 키 생략 (ERC-5792 MUST) Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/keyring-ethereum/constants.ts | 1 + .../src/keyring-ethereum/service.ts | 95 +++++++++++++------ 2 files changed, 65 insertions(+), 31 deletions(-) diff --git a/packages/background/src/keyring-ethereum/constants.ts b/packages/background/src/keyring-ethereum/constants.ts index 28b7c7754d..dbdd2ff1e1 100644 --- a/packages/background/src/keyring-ethereum/constants.ts +++ b/packages/background/src/keyring-ethereum/constants.ts @@ -13,6 +13,7 @@ export const SIGNATURE_V_TRUE = Buffer.from("1c", "hex"); // EIP-1193 / EIP-1474 RPC error codes export const RPC_ERROR_USER_REJECTED = 4001; +export const RPC_ERROR_UNAUTHORIZED = 4100; export const RPC_ERROR_UNSUPPORTED_METHOD = 4200; export const RPC_ERROR_INVALID_PARAMS = -32602; export const RPC_ERROR_UNRECOGNIZED_CHAIN = 4902; diff --git a/packages/background/src/keyring-ethereum/service.ts b/packages/background/src/keyring-ethereum/service.ts index 9a837b6a58..6a335baae8 100644 --- a/packages/background/src/keyring-ethereum/service.ts +++ b/packages/background/src/keyring-ethereum/service.ts @@ -45,6 +45,7 @@ import { ALLOWED_DELEGATORS, ERC20_TRANSFER_SELECTOR, RPC_ERROR_INVALID_PARAMS, + RPC_ERROR_UNAUTHORIZED, RPC_ERROR_UNSUPPORTED_METHOD, } from "./constants"; import { EvmWebSocketManager } from "./ws-handlers"; @@ -83,13 +84,53 @@ export class KeyRingEthereumService { // TODO: ? } - // ── Smart Account ──────────────────────────────────────── + // ── Smart Account ───────────────── + // EIP-7702 delegation 판별은 parseDelegation 유틸이 담당한다. + // 아래 핸들러들은 ERC-5792 wallet_getCapabilities 응답 형식을 구성하는 레이어이다. - async getSmartAccountCapabilities( + // 주소 형식(-32602)과 소유권(4100)을 검증한 뒤 활성 체인의 atomic capability를 반환한다. + private async handleGetCapabilities( + env: Env, + origin: string, + params: unknown[] | Record | undefined, + chainId: string | undefined + ): Promise { + const currentChainId = this.forceGetCurrentChainId(origin, chainId); + + const requestedAddress = + Array.isArray(params) && typeof params[0] === "string" + ? params[0] + : undefined; + if (!requestedAddress || !requestedAddress.match(/^0x[0-9a-fA-F]{40}$/)) { + throw new EthereumProviderRpcError( + RPC_ERROR_INVALID_PARAMS, + "Invalid parameters: must provide a valid Ethereum address." + ); + } + + const selectedAddress = await this.getSelectedEthAddress(currentChainId); + if (requestedAddress.toLowerCase() !== selectedAddress.toLowerCase()) { + throw new EthereumProviderRpcError( + RPC_ERROR_UNAUTHORIZED, + "Unauthorized" + ); + } + + return this.getAtomicCapability( + env, + origin, + currentChainId, + selectedAddress + ); + } + + // EIP-7702 delegation 상태를 판별하고 ERC-5792 atomic capability 응답 형식으로 반환한다. + // 미지원 체인은 키를 생략한다 (https://eips.ethereum.org/EIPS/eip-5792#wallet_getcapabilities). + async getAtomicCapability( env: Env, origin: string, chainId: string, - address?: string + address: string ): Promise { const modularChainInfo = this.chainsService.getModularChainInfoOrThrow(chainId); @@ -104,25 +145,28 @@ export class KeyRingEthereumService { : undefined; if (!features?.includes("eip-7702")) { - return { [hexChainId]: { atomic: { status: "unsupported" } } }; + return {}; } - const target = address ?? (await this.getSelectedEthAddress(chainId)); - - const code = await this.request( - env, - origin, - "eth_getCode", - [target, "latest"], - undefined, - chainId - ); + let code: string | undefined; + try { + code = await this.request( + env, + origin, + "eth_getCode", + [address, "latest"], + undefined, + chainId + ); + } catch { + return {}; + } - const status: DelegationStatus = parseDelegation( - code ?? "0x", - ALLOWED_DELEGATORS - ); + if (!code) { + return {}; + } + const status: DelegationStatus = parseDelegation(code, ALLOWED_DELEGATORS); return { [hexChainId]: { atomic: { status } } }; } @@ -565,19 +609,8 @@ export class KeyRingEthereumService { case "wallet_watchAsset": return this.chainHandler.watchAsset(env, origin, params, chainId); // TODO: ERC-5792 스펙은 모든 지원 체인의 capabilities를 한 번에 반환해야 하지만, 현재는 활성 체인만 반환. - case "wallet_getCapabilities": { - const currentChainId = this.forceGetCurrentChainId(origin, chainId); - const address = - Array.isArray(params) && typeof params[0] === "string" - ? params[0] - : undefined; - return this.getSmartAccountCapabilities( - env, - origin, - currentChainId, - address - ); - } + case "wallet_getCapabilities": + return this.handleGetCapabilities(env, origin, params, chainId); case "eth_call": case "eth_estimateGas": case "eth_getTransactionCount": From ac62285bb71d4226c0e57934e1d86b0df3116cb8 Mon Sep 17 00:00:00 2001 From: rowan Date: Mon, 30 Mar 2026 16:12:34 +0900 Subject: [PATCH 22/64] test: add unit tests for EIP-712 message validation and EVM transaction serialization - Introduced `eip712.spec.ts` to test EIP-712 message validation for Evmos and Injective transactions. - Added `serialize.spec.ts` to validate serialization of legacy and EIP-1559 transactions, ensuring correct handling of chainId and signature formats. - Updated `serialize.ts` to enforce chainId requirement for typed transactions, preventing silent mis-serialization. --- .../src/keyring-cosmos/eip712.spec.ts | 305 ++++++++++++++++++ packages/stores-eth/src/serialize.spec.ts | 175 ++++++++++ packages/stores-eth/src/serialize.ts | 23 +- 3 files changed, 500 insertions(+), 3 deletions(-) create mode 100644 packages/background/src/keyring-cosmos/eip712.spec.ts create mode 100644 packages/stores-eth/src/serialize.spec.ts diff --git a/packages/background/src/keyring-cosmos/eip712.spec.ts b/packages/background/src/keyring-cosmos/eip712.spec.ts new file mode 100644 index 0000000000..b5f46f5f49 --- /dev/null +++ b/packages/background/src/keyring-cosmos/eip712.spec.ts @@ -0,0 +1,305 @@ +import { sortObjectByKey } from "@keplr-wallet/common"; +import { domainHash, EIP712MessageValidator, messageHash } from "./eip712"; + +function makeMsgSendFixture( + fromAddress: string, + toAddress: string, + denom: string, + amount: string +): { type: string; value: Record } { + return { + type: "cosmos-sdk/MsgSend", + value: { + from_address: fromAddress, + to_address: toAddress, + amount: [{ denom, amount }], + }, + }; +} + +async function makeEvmosTypedData() { + const msg = makeMsgSendFixture( + "evmos1testaddress000000000000000000000000000", + "evmos1recipient0000000000000000000000000000", + "aevmos", + "1000000000000000000" + ); + + return await EIP712MessageValidator.validateAsync({ + types: { + EIP712Domain: [ + { name: "name", type: "string" }, + { name: "version", type: "string" }, + { name: "chainId", type: "uint256" }, + { name: "verifyingContract", type: "string" }, + { name: "salt", type: "string" }, + ], + Tx: [ + { name: "account_number", type: "string" }, + { name: "chain_id", type: "string" }, + { name: "fee", type: "Fee" }, + { name: "memo", type: "string" }, + { name: "msgs", type: "Msg[]" }, + { name: "sequence", type: "string" }, + ], + Fee: [ + { name: "feePayer", type: "string" }, + { name: "amount", type: "Coin[]" }, + { name: "gas", type: "string" }, + ], + Coin: [ + { name: "denom", type: "string" }, + { name: "amount", type: "string" }, + ], + Msg: [ + { name: "type", type: "string" }, + { name: "value", type: "MsgValue" }, + ], + MsgValue: [ + { name: "from_address", type: "string" }, + { name: "to_address", type: "string" }, + { name: "amount", type: "TypeAmount[]" }, + ], + TypeAmount: [ + { name: "denom", type: "string" }, + { name: "amount", type: "string" }, + ], + }, + primaryType: "Tx", + domain: { + name: "Cosmos Web3", + version: "1.0.0", + chainId: "9001", + verifyingContract: "cosmos", + salt: "0", + }, + message: sortObjectByKey({ + account_number: "7", + chain_id: "evmos_9001-2", + fee: { + amount: [{ denom: "aevmos", amount: "2000000000000000" }], + feePayer: "evmos1testaddress000000000000000000000000000", + gas: "200000", + }, + memo: "", + msgs: [msg], + sequence: "9", + }), + }); +} + +async function makeInjectiveTypedData() { + const msg = makeMsgSendFixture( + "inj1testaddress0000000000000000000000000000", + "inj1recipient00000000000000000000000000000", + "inj", + "10000000000000000" + ); + + return await EIP712MessageValidator.validateAsync({ + types: { + EIP712Domain: [ + { name: "name", type: "string" }, + { name: "version", type: "string" }, + { name: "chainId", type: "uint256" }, + { name: "verifyingContract", type: "string" }, + { name: "salt", type: "string" }, + ], + Tx: [ + { name: "account_number", type: "string" }, + { name: "chain_id", type: "string" }, + { name: "fee", type: "Fee" }, + { name: "memo", type: "string" }, + { name: "msgs", type: "Msg[]" }, + { name: "sequence", type: "string" }, + { name: "timeout_height", type: "string" }, + ], + Fee: [ + { name: "amount", type: "Coin[]" }, + { name: "gas", type: "string" }, + ], + Coin: [ + { name: "denom", type: "string" }, + { name: "amount", type: "string" }, + ], + Msg: [ + { name: "type", type: "string" }, + { name: "value", type: "MsgValue" }, + ], + MsgValue: [ + { name: "from_address", type: "string" }, + { name: "to_address", type: "string" }, + { name: "amount", type: "TypeAmount[]" }, + ], + TypeAmount: [ + { name: "denom", type: "string" }, + { name: "amount", type: "string" }, + ], + }, + primaryType: "Tx", + domain: { + name: "Injective Web3", + version: "1.0.0", + chainId: "0x1", + verifyingContract: "cosmos", + salt: "0", + }, + message: sortObjectByKey({ + account_number: "12", + chain_id: "injective-1", + fee: { + amount: [{ denom: "inj", amount: "50000000000000" }], + gas: "200000", + }, + memo: "", + msgs: [msg], + sequence: "3", + timeout_height: Number.MAX_SAFE_INTEGER.toString(), + }), + }); +} + +describe("Test EIP-712 helpers", () => { + it("matches the canonical EIP-712 mail example hashes", async () => { + const typedData = await EIP712MessageValidator.validateAsync({ + types: { + EIP712Domain: [ + { name: "name", type: "string" }, + { name: "version", type: "string" }, + { name: "chainId", type: "uint256" }, + { name: "verifyingContract", type: "address" }, + ], + Person: [ + { name: "wallet", type: "address" }, + { name: "name", type: "string" }, + ], + Mail: [ + { name: "from", type: "Person" }, + { name: "to", type: "Person" }, + { name: "contents", type: "string" }, + ], + }, + primaryType: "Mail", + domain: { + name: "Ether Mail", + version: "1", + chainId: 1, + verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC", + }, + message: { + from: { + wallet: "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + name: "Cow", + }, + to: { + wallet: "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", + name: "Bob", + }, + contents: "Hello, Bob!", + }, + }); + + expect(domainHash(typedData)).toBe( + "0xf2cee375fa42b42143804025fc449deafd50cc031ca257e0b194a650a912090f" + ); + expect(messageHash(typedData)).toBe( + "0xdde0a9fa0b3a536b59f2fd59426a15de66a5ca873cc166b6eb8021e16b91b3d3" + ); + }); + + it("keeps Ethermint-style ledger typed data stable", async () => { + const typedData = await makeEvmosTypedData(); + + expect(typedData.domain).toEqual({ + name: "Cosmos Web3", + version: "1.0.0", + chainId: "9001", + verifyingContract: "cosmos", + salt: "0", + }); + expect(typedData.types.Tx).toEqual([ + { name: "account_number", type: "string" }, + { name: "chain_id", type: "string" }, + { name: "fee", type: "Fee" }, + { name: "memo", type: "string" }, + { name: "msgs", type: "Msg[]" }, + { name: "sequence", type: "string" }, + ]); + expect(typedData.types.Fee).toEqual([ + { name: "feePayer", type: "string" }, + { name: "amount", type: "Coin[]" }, + { name: "gas", type: "string" }, + ]); + expect(domainHash(typedData)).toBe( + "0x1633cfb1cf33b8790808cb60604cfd1f261c59a367ccb3aaff568a000149ace9" + ); + expect(messageHash(typedData)).toBe( + "0x9d2fea7a4c1eb2801664062e5e9ca260dfd415b0274670b8a6f65f9e144b348b" + ); + }); + + it("keeps Injective ledger typed data stable", async () => { + const typedData = await makeInjectiveTypedData(); + + expect(typedData.domain).toEqual({ + name: "Injective Web3", + version: "1.0.0", + chainId: "0x1", + verifyingContract: "cosmos", + salt: "0", + }); + expect(typedData.types.Tx).toEqual([ + { name: "account_number", type: "string" }, + { name: "chain_id", type: "string" }, + { name: "fee", type: "Fee" }, + { name: "memo", type: "string" }, + { name: "msgs", type: "Msg[]" }, + { name: "sequence", type: "string" }, + { name: "timeout_height", type: "string" }, + ]); + expect(typedData.types.Fee).toEqual([ + { name: "amount", type: "Coin[]" }, + { name: "gas", type: "string" }, + ]); + expect(domainHash(typedData)).toBe( + "0xe976870d04d7c978d7d21058ec0b93416744f360658321bd253ddcd9483c4e00" + ); + expect(messageHash(typedData)).toBe( + "0xd8a585e24f5942f95592b01de6d341fef2654a863995e6e235d41b419b90671d" + ); + }); + + it("sorts EIP712Domain fields during validation", async () => { + const typedData = await EIP712MessageValidator.validateAsync({ + types: { + EIP712Domain: [ + { name: "salt", type: "string" }, + { name: "version", type: "string" }, + { name: "name", type: "string" }, + { name: "verifyingContract", type: "string" }, + { name: "chainId", type: "uint256" }, + ], + Tx: [{ name: "account_number", type: "string" }], + }, + primaryType: "Tx", + domain: { + name: "Cosmos Web3", + version: "1.0.0", + chainId: "9001", + verifyingContract: "cosmos", + salt: "0", + }, + message: { + account_number: "1", + }, + }); + + expect(typedData.types.EIP712Domain).toEqual([ + { name: "name", type: "string" }, + { name: "version", type: "string" }, + { name: "chainId", type: "uint256" }, + { name: "verifyingContract", type: "string" }, + { name: "salt", type: "string" }, + ]); + }); +}); diff --git a/packages/stores-eth/src/serialize.spec.ts b/packages/stores-eth/src/serialize.spec.ts new file mode 100644 index 0000000000..29a7b55992 --- /dev/null +++ b/packages/stores-eth/src/serialize.spec.ts @@ -0,0 +1,175 @@ +import { serializeEVMTransaction } from "./serialize"; +import { UnsignedEVMTransaction } from "./types"; + +// Test vectors adapted from viem's official transaction serialization tests: +// https://github.com/wevm/viem/blob/main/src/utils/transaction/serializeTransaction.test.ts + +const SIGNATURE = { + r: `0x${"60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe"}`, + s: `0x${"60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe"}`, + v: 27, +}; + +const SIGNATURE_BYTES = Uint8Array.from([ + ...Buffer.from( + "60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe", + "hex" + ), + ...Buffer.from( + "60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe", + "hex" + ), + 0x1b, +]); + +describe("serializeEVMTransaction", () => { + it("documents chainId handling for legacy transactions", () => { + const baseTx: UnsignedEVMTransaction = { + nonce: 0, + gasPrice: "2000000000", + to: "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + value: "1000000000000000000", + data: "0x", + }; + + expect(serializeEVMTransaction(baseTx)).toBe( + "0xe6808477359400809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080" + ); + expect(() => + serializeEVMTransaction({ + ...baseTx, + chainId: 0, + }) + ).toThrow('Chain ID "0" is invalid.'); + expect( + serializeEVMTransaction({ + ...baseTx, + chainId: 1, + }) + ).toBe( + "0xe9808477359400809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080018080" + ); + }); + + it("documents chainId handling for EIP-1559 transactions", () => { + const baseTx: UnsignedEVMTransaction = { + type: 2, + nonce: 0, + maxFeePerGas: "2000000000", + maxPriorityFeePerGas: "2000000000", + to: "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + value: "1000000000000000000", + data: "0x", + }; + + expect(() => serializeEVMTransaction(baseTx)).toThrow( + "Typed EVM transactions require chainId" + ); + expect(() => + serializeEVMTransaction({ + ...baseTx, + chainId: 0, + }) + ).toThrow('Chain ID "0" is invalid.'); + expect( + serializeEVMTransaction({ + ...baseTx, + chainId: 1, + }) + ).toBe( + "0x02ed018084773594008477359400809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080c0" + ); + }); + + it("serializes legacy transactions using the viem legacy vector", () => { + const tx: UnsignedEVMTransaction = { + chainId: 69, + nonce: 785, + gasPrice: "2000000000", + to: "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + value: "1000000000000000000", + data: "0x", + }; + + expect(serializeEVMTransaction(tx)).toBe( + "0xeb8203118477359400809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080458080" + ); + }); + + it("serializes EIP-1559 transactions using the viem access-list vector", () => { + const tx: UnsignedEVMTransaction = { + type: 2, + chainId: 1, + nonce: 785, + maxFeePerGas: "2000000000", + maxPriorityFeePerGas: "2000000000", + to: "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + value: "1000000000000000000", + data: "0x", + accessList: [ + { + address: "0x0000000000000000000000000000000000000000", + storageKeys: [ + "0x0000000000000000000000000000000000000000000000000000000000000001", + "0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe", + ], + }, + ], + }; + + expect(serializeEVMTransaction(tx)).toBe( + "0x02f88b0182031184773594008477359400809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080f85bf859940000000000000000000000000000000000000000f842a00000000000000000000000000000000000000000000000000000000000000001a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe" + ); + }); + + it("accepts 65-byte signatures with legacy-style v values", () => { + const tx: UnsignedEVMTransaction = { + type: 2, + chainId: 1, + nonce: 785, + maxFeePerGas: "2000000000", + maxPriorityFeePerGas: "2000000000", + to: "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + value: "1000000000000000000", + data: "0x", + }; + + expect(serializeEVMTransaction(tx, SIGNATURE_BYTES)).toBe( + "0x02f8720182031184773594008477359400809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080c080a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe" + ); + expect(serializeEVMTransaction(tx, SIGNATURE)).toBe( + "0x02f8720182031184773594008477359400809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080c080a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fea060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe" + ); + }); + + it("documents the current mismatch against viem's EIP-2930 vector", () => { + const tx: UnsignedEVMTransaction = { + type: 1, + chainId: 1, + nonce: 785, + gasPrice: "2000000000", + to: "0x70997970c51812dc3a010c7d01b50e0d17dc79c8", + value: "1000000000000000000", + data: "0x", + accessList: [ + { + address: "0x1234512345123451234512345123451234512345", + storageKeys: [ + "0x60fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe", + ], + }, + ], + }; + + const viemExpected = + "0x01f863018203118477359400809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080f838f7941234512345123451234512345123451234512345e1a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe"; + + expect(viemExpected).toBe( + "0x01f863018203118477359400809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080f838f7941234512345123451234512345123451234512345e1a060fdd29ff912ce880cd3edaf9f932dc61d3dae823ea77e0323f94adb9f6a72fe" + ); + expect(serializeEVMTransaction(tx)).toBe( + "0xeb8203118477359400809470997970c51812dc3a010c7d01b50e0d17dc79c8880de0b6b3a764000080018080" + ); + expect(serializeEVMTransaction(tx)).not.toBe(viemExpected); + }); +}); diff --git a/packages/stores-eth/src/serialize.ts b/packages/stores-eth/src/serialize.ts index bf8c0092c7..5501707f3e 100644 --- a/packages/stores-eth/src/serialize.ts +++ b/packages/stores-eth/src/serialize.ts @@ -51,10 +51,14 @@ function parseSignature( } /** - * Serialize an unsigned EVM transaction (and optionally sign it) using ox. + * Serialize a normalized EVM transaction (and optionally sign it) using ox. * * This replaces `serialize()` from `@ethersproject/transactions`. - * The output is identical RLP encoding with "0x" prefix. + * The output is identical RLP encoding with "0x" prefix for supported + * transaction types. + * + * Legacy transactions may omit `chainId`, but typed transactions must provide + * an explicit `chainId` so we don't silently serialize for the wrong network. */ export function serializeEVMTransaction( tx: UnsignedEVMTransaction, @@ -62,12 +66,25 @@ export function serializeEVMTransaction( ): string { const sig = signature ? parseSignature(signature) : undefined; + const isTypedTransaction = + tx.type != null || + tx.accessList != null || + tx.maxFeePerGas != null || + tx.maxPriorityFeePerGas != null; + + // Legacy transactions may omit chainId, but typed transactions should + // always be bound to an explicit network. We intentionally avoid + // defaulting to mainnet (1) to prevent silent mis-serialization. + if (isTypedTransaction && tx.chainId == null) { + throw new Error("Typed EVM transactions require chainId"); + } + const isEIP1559 = tx.type === 2 || tx.maxFeePerGas != null || tx.maxPriorityFeePerGas != null; if (isEIP1559) { const envelope: Parameters[0] = { - chainId: tx.chainId ?? 0, + chainId: tx.chainId as number, nonce: toBigInt(tx.nonce), gas: toBigInt(tx.gasLimit), maxFeePerGas: toBigInt(tx.maxFeePerGas), From 2c53a99e6f9fc104cce28e1f51c9466a10fb5aef Mon Sep 17 00:00:00 2001 From: rowan Date: Mon, 30 Mar 2026 16:59:43 +0900 Subject: [PATCH 23/64] test(hooks): cover ENS multicoin resolution policy Add focused ENS resolution coverage around the ox-based resolver path. - normalize mixed-case ENS names before computing the node hash - expose resolveENSWithEthCall so the resolution flow can be unit tested without RPC fixtures - document that Keplr intentionally resolves ETH addresses via ENSIP-9 multicoin records only - add hooks ENS tests for normalization, resolver absence, invalid multicoin payloads, and empty records - add a jest config for hooks-evm so equivalent ENS tests can run there as well --- packages/hooks-evm/jest.config.js | 5 + .../hooks-evm/src/tx/name-service-ens.spec.ts | 108 ++++++++++++++++++ packages/hooks-evm/src/tx/name-service-ens.ts | 16 ++- .../hooks/src/tx/name-service-ens.spec.ts | 108 ++++++++++++++++++ packages/hooks/src/tx/name-service-ens.ts | 16 ++- 5 files changed, 247 insertions(+), 6 deletions(-) create mode 100644 packages/hooks-evm/jest.config.js create mode 100644 packages/hooks-evm/src/tx/name-service-ens.spec.ts create mode 100644 packages/hooks/src/tx/name-service-ens.spec.ts diff --git a/packages/hooks-evm/jest.config.js b/packages/hooks-evm/jest.config.js new file mode 100644 index 0000000000..42eb6623c3 --- /dev/null +++ b/packages/hooks-evm/jest.config.js @@ -0,0 +1,5 @@ +module.exports = { + preset: "ts-jest", + testEnvironment: "node", + testMatch: ["**/src/**/?(*.)+(spec|test).[jt]s?(x)"], +}; diff --git a/packages/hooks-evm/src/tx/name-service-ens.spec.ts b/packages/hooks-evm/src/tx/name-service-ens.spec.ts new file mode 100644 index 0000000000..cf1a36de7d --- /dev/null +++ b/packages/hooks-evm/src/tx/name-service-ens.spec.ts @@ -0,0 +1,108 @@ +import { AbiFunction, Ens, Hex } from "ox"; +import { resolveENSWithEthCall } from "./name-service-ens"; + +const resolverAbi = AbiFunction.from( + "function resolver(bytes32 node) view returns (address)" +); +const multicoinAddrAbi = AbiFunction.from( + "function addr(bytes32 node, uint256 coinType) view returns (bytes)" +); + +describe("resolveENSWithEthCall", () => { + it("normalizes mixed-case ENS names before computing the node", async () => { + const normalizedNode = Ens.namehash("alice.eth"); + const resolver = "0x1234567890123456789012345678901234567890"; + const address = "0x1111111111111111111111111111111111111111"; + + const result = await resolveENSWithEthCall( + "Alice.eth", + async (_to, data) => { + if (data.startsWith(AbiFunction.getSelector(resolverAbi))) { + expect(data).toBe( + AbiFunction.encodeData(resolverAbi, [normalizedNode]) + ); + return AbiFunction.encodeResult(resolverAbi, resolver); + } + + expect(data).toBe( + AbiFunction.encodeData(multicoinAddrAbi, [normalizedNode, BigInt(60)]) + ); + return AbiFunction.encodeResult(multicoinAddrAbi, address as Hex.Hex); + } + ); + + expect(result).toBe(address); + }); + + it("resolves the ENSIP-9 multicoin ETH record", async () => { + const calls: Array<{ to: string; data: string }> = []; + const node = Ens.namehash("alice.eth"); + const resolver = "0x1234567890123456789012345678901234567890"; + const address = "0x1111111111111111111111111111111111111111"; + + const result = await resolveENSWithEthCall( + "alice.eth", + async (to, data) => { + calls.push({ to, data }); + + if (calls.length === 1) { + expect(data).toBe(AbiFunction.encodeData(resolverAbi, [node])); + return AbiFunction.encodeResult(resolverAbi, resolver); + } + + expect(data).toBe( + AbiFunction.encodeData(multicoinAddrAbi, [node, BigInt(60)]) + ); + return AbiFunction.encodeResult(multicoinAddrAbi, address as Hex.Hex); + } + ); + + expect(result).toBe(address); + expect(calls).toHaveLength(2); + }); + + it("returns undefined if the resolver address is unset", async () => { + const result = await resolveENSWithEthCall("alice.eth", async () => { + return AbiFunction.encodeResult( + resolverAbi, + "0x0000000000000000000000000000000000000000" + ); + }); + + expect(result).toBeUndefined(); + }); + + it("returns undefined if the multicoin record is not a 20-byte ETH address", async () => { + const resolver = "0x1234567890123456789012345678901234567890"; + + const result = await resolveENSWithEthCall( + "alice.eth", + async (_to, data) => { + if (data.startsWith(AbiFunction.getSelector(resolverAbi))) { + return AbiFunction.encodeResult(resolverAbi, resolver); + } + + return AbiFunction.encodeResult(multicoinAddrAbi, "0x1234"); + } + ); + + expect(result).toBeUndefined(); + }); + + it("returns undefined if the multicoin record is empty", async () => { + const resolver = "0x1234567890123456789012345678901234567890"; + + const result = await resolveENSWithEthCall( + "alice.eth", + async (_to, data) => { + if (data.startsWith(AbiFunction.getSelector(resolverAbi))) { + return AbiFunction.encodeResult(resolverAbi, resolver); + } + + return AbiFunction.encodeResult(multicoinAddrAbi, "0x"); + } + ); + + expect(result).toBeUndefined(); + }); +}); diff --git a/packages/hooks-evm/src/tx/name-service-ens.ts b/packages/hooks-evm/src/tx/name-service-ens.ts index b56580779b..994fc005d9 100644 --- a/packages/hooks-evm/src/tx/name-service-ens.ts +++ b/packages/hooks-evm/src/tx/name-service-ens.ts @@ -17,10 +17,17 @@ async function resolveENS( rpcUrl: string, name: string ): Promise { - const node = Ens.namehash(name); + return resolveENSWithEthCall(name, (to, data) => ethCall(rpcUrl, to, data)); +} + +export async function resolveENSWithEthCall( + name: string, + ethCallFn: (to: string, data: string) => Promise +): Promise { + const node = Ens.namehash(Ens.normalize(name)); const resolverCalldata = AbiFunction.encodeData(resolverAbi, [node]); - const resolverResult = await ethCall(rpcUrl, ENS_REGISTRY, resolverCalldata); + const resolverResult = await ethCallFn(ENS_REGISTRY, resolverCalldata); const resolverAddress = AbiFunction.decodeResult(resolverAbi, resolverResult); if ( @@ -30,8 +37,11 @@ async function resolveENS( return undefined; } + // Resolve ETH addresses through ENSIP-9 multicoin records only. + // We intentionally do not fall back to legacy addr(bytes32), since Keplr + // treats ENS resolution here as a modern mainnet resolver integration. const addrCalldata = AbiFunction.encodeData(addrAbi, [node, BigInt(60)]); - const addrResult = await ethCall(rpcUrl, resolverAddress, addrCalldata); + const addrResult = await ethCallFn(resolverAddress, addrCalldata); const addrBytes = AbiFunction.decodeResult(addrAbi, addrResult); if (!addrBytes || Hex.size(addrBytes as Hex.Hex) !== 20) { diff --git a/packages/hooks/src/tx/name-service-ens.spec.ts b/packages/hooks/src/tx/name-service-ens.spec.ts new file mode 100644 index 0000000000..cf1a36de7d --- /dev/null +++ b/packages/hooks/src/tx/name-service-ens.spec.ts @@ -0,0 +1,108 @@ +import { AbiFunction, Ens, Hex } from "ox"; +import { resolveENSWithEthCall } from "./name-service-ens"; + +const resolverAbi = AbiFunction.from( + "function resolver(bytes32 node) view returns (address)" +); +const multicoinAddrAbi = AbiFunction.from( + "function addr(bytes32 node, uint256 coinType) view returns (bytes)" +); + +describe("resolveENSWithEthCall", () => { + it("normalizes mixed-case ENS names before computing the node", async () => { + const normalizedNode = Ens.namehash("alice.eth"); + const resolver = "0x1234567890123456789012345678901234567890"; + const address = "0x1111111111111111111111111111111111111111"; + + const result = await resolveENSWithEthCall( + "Alice.eth", + async (_to, data) => { + if (data.startsWith(AbiFunction.getSelector(resolverAbi))) { + expect(data).toBe( + AbiFunction.encodeData(resolverAbi, [normalizedNode]) + ); + return AbiFunction.encodeResult(resolverAbi, resolver); + } + + expect(data).toBe( + AbiFunction.encodeData(multicoinAddrAbi, [normalizedNode, BigInt(60)]) + ); + return AbiFunction.encodeResult(multicoinAddrAbi, address as Hex.Hex); + } + ); + + expect(result).toBe(address); + }); + + it("resolves the ENSIP-9 multicoin ETH record", async () => { + const calls: Array<{ to: string; data: string }> = []; + const node = Ens.namehash("alice.eth"); + const resolver = "0x1234567890123456789012345678901234567890"; + const address = "0x1111111111111111111111111111111111111111"; + + const result = await resolveENSWithEthCall( + "alice.eth", + async (to, data) => { + calls.push({ to, data }); + + if (calls.length === 1) { + expect(data).toBe(AbiFunction.encodeData(resolverAbi, [node])); + return AbiFunction.encodeResult(resolverAbi, resolver); + } + + expect(data).toBe( + AbiFunction.encodeData(multicoinAddrAbi, [node, BigInt(60)]) + ); + return AbiFunction.encodeResult(multicoinAddrAbi, address as Hex.Hex); + } + ); + + expect(result).toBe(address); + expect(calls).toHaveLength(2); + }); + + it("returns undefined if the resolver address is unset", async () => { + const result = await resolveENSWithEthCall("alice.eth", async () => { + return AbiFunction.encodeResult( + resolverAbi, + "0x0000000000000000000000000000000000000000" + ); + }); + + expect(result).toBeUndefined(); + }); + + it("returns undefined if the multicoin record is not a 20-byte ETH address", async () => { + const resolver = "0x1234567890123456789012345678901234567890"; + + const result = await resolveENSWithEthCall( + "alice.eth", + async (_to, data) => { + if (data.startsWith(AbiFunction.getSelector(resolverAbi))) { + return AbiFunction.encodeResult(resolverAbi, resolver); + } + + return AbiFunction.encodeResult(multicoinAddrAbi, "0x1234"); + } + ); + + expect(result).toBeUndefined(); + }); + + it("returns undefined if the multicoin record is empty", async () => { + const resolver = "0x1234567890123456789012345678901234567890"; + + const result = await resolveENSWithEthCall( + "alice.eth", + async (_to, data) => { + if (data.startsWith(AbiFunction.getSelector(resolverAbi))) { + return AbiFunction.encodeResult(resolverAbi, resolver); + } + + return AbiFunction.encodeResult(multicoinAddrAbi, "0x"); + } + ); + + expect(result).toBeUndefined(); + }); +}); diff --git a/packages/hooks/src/tx/name-service-ens.ts b/packages/hooks/src/tx/name-service-ens.ts index 39cac4a127..13b5845614 100644 --- a/packages/hooks/src/tx/name-service-ens.ts +++ b/packages/hooks/src/tx/name-service-ens.ts @@ -17,10 +17,17 @@ async function resolveENS( rpcUrl: string, name: string ): Promise { - const node = Ens.namehash(name); + return resolveENSWithEthCall(name, (to, data) => ethCall(rpcUrl, to, data)); +} + +export async function resolveENSWithEthCall( + name: string, + ethCallFn: (to: string, data: string) => Promise +): Promise { + const node = Ens.namehash(Ens.normalize(name)); const resolverCalldata = AbiFunction.encodeData(resolverAbi, [node]); - const resolverResult = await ethCall(rpcUrl, ENS_REGISTRY, resolverCalldata); + const resolverResult = await ethCallFn(ENS_REGISTRY, resolverCalldata); const resolverAddress = AbiFunction.decodeResult(resolverAbi, resolverResult); if ( @@ -30,8 +37,11 @@ async function resolveENS( return undefined; } + // Resolve ETH addresses through ENSIP-9 multicoin records only. + // We intentionally do not fall back to legacy addr(bytes32), since Keplr + // treats ENS resolution here as a modern mainnet resolver integration. const addrCalldata = AbiFunction.encodeData(addrAbi, [node, BigInt(60)]); - const addrResult = await ethCall(rpcUrl, resolverAddress, addrCalldata); + const addrResult = await ethCallFn(resolverAddress, addrCalldata); const addrBytes = AbiFunction.decodeResult(addrAbi, addrResult); if (!addrBytes || Hex.size(addrBytes as Hex.Hex) !== 20) { From 39f56a05f71d0e2cdd179bd0cd72d2910d41a256 Mon Sep 17 00:00:00 2001 From: rowan Date: Mon, 30 Mar 2026 17:10:06 +0900 Subject: [PATCH 24/64] fix(stores-eth): preserve fee precision when building tx hex values Avoid converting gas fee fields through JavaScript Number before serializing transaction inputs. - build maxFeePerGas and maxPriorityFeePerGas from BigInt-backed values - handle gasPrice the same way so decimal and 0x-prefixed integer strings share one path - keep ox Hex serialization while eliminating precision loss for large fee values --- packages/stores-eth/src/account/base.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/stores-eth/src/account/base.ts b/packages/stores-eth/src/account/base.ts index 740f21953d..93ba571a66 100644 --- a/packages/stores-eth/src/account/base.ts +++ b/packages/stores-eth/src/account/base.ts @@ -432,12 +432,12 @@ export class EthereumAccountBase { const feeObject = maxFeePerGas && maxPriorityFeePerGas ? { - maxFeePerGas: Hex.fromNumber(Number(maxFeePerGas)), - maxPriorityFeePerGas: Hex.fromNumber(Number(maxPriorityFeePerGas)), + maxFeePerGas: Hex.fromNumber(BigInt(maxFeePerGas)), + maxPriorityFeePerGas: Hex.fromNumber(BigInt(maxPriorityFeePerGas)), gasLimit: Hex.fromNumber(gasLimit), } : { - gasPrice: Hex.fromNumber(Number(gasPrice ?? "0")), + gasPrice: Hex.fromNumber(BigInt(gasPrice ?? "0")), gasLimit: Hex.fromNumber(gasLimit), }; From 9d86c0f0651018f1b0a6abf96ff4048b0c0aee02 Mon Sep 17 00:00:00 2001 From: rowan Date: Mon, 30 Mar 2026 17:15:05 +0900 Subject: [PATCH 25/64] chore(tooling): harden post-install patching and align unit lint rules Tighten TypeScript upgrade follow-up tooling so failures surface earlier and package overrides stay consistent with the root ESLint config. - make miscreant patch steps distinguish between already-patched, patchable, and stale upstream source - throw with a targeted error when an expected patch anchor disappears instead of silently no-oping - update packages/unit to disable the base no-loss-of-precision rule name used by the root config --- packages/unit/.eslintrc.json | 2 +- scripts/post-install.mjs | 72 +++++++++++++++++++++++------------- 2 files changed, 48 insertions(+), 26 deletions(-) diff --git a/packages/unit/.eslintrc.json b/packages/unit/.eslintrc.json index 8f56043e04..aab9d2206f 100644 --- a/packages/unit/.eslintrc.json +++ b/packages/unit/.eslintrc.json @@ -11,7 +11,7 @@ { "files": ["**/*.spec.ts", "**/*.spec.js"], "rules": { - "@typescript-eslint/no-loss-of-precision": "off" + "no-loss-of-precision": "off" } } ] diff --git a/scripts/post-install.mjs b/scripts/post-install.mjs index 85d9f770d4..39c512e2ca 100644 --- a/scripts/post-install.mjs +++ b/scripts/post-install.mjs @@ -53,20 +53,38 @@ async function patchMiscreant() { return null; }; + const replaceOrThrow = async ({ filePath, from, to, description }) => { + let data = await fs.readFile(filePath, "utf8"); + + if (data.includes(to)) { + return; + } + + if (!data.includes(from)) { + throw new Error( + `Failed to patch miscreant (${description}): expected source snippet was not found in ${filePath}` + ); + } + + data = data.replace(from, to); + await fs.writeFile(filePath, data); + }; + // 1) polyfill/aes.ts: writeUint32BE의 out 파라미터에 명시적 Uint8Array 타입 추가 const polyfillAes = findNodeModulesFile( "miscreant/src/providers/polyfill/aes.ts" ); if (polyfillAes) { - let data = await fs.readFile(polyfillAes, "utf8"); const oldSig = "function writeUint32BE(value: number, out = new Uint8Array(4), offset = 0): Uint8Array {"; const newSig = "function writeUint32BE(value: number, out: Uint8Array = new Uint8Array(4), offset = 0): Uint8Array {"; - if (data.includes(oldSig)) { - data = data.replace(oldSig, newSig); - await fs.writeFile(polyfillAes, data); - } + await replaceOrThrow({ + filePath: polyfillAes, + from: oldSig, + to: newSig, + description: "polyfill/aes.ts writeUint32BE signature", + }); } // 2) webcrypto/aes.ts: importKey와 encrypt 호출에서 Uint8Array를 BufferSource로 캐스트 @@ -74,16 +92,18 @@ async function patchMiscreant() { "miscreant/src/providers/webcrypto/aes.ts" ); if (webcryptoAes) { - let data = await fs.readFile(webcryptoAes, "utf8"); - data = data.replace( - 'const key = await crypto.subtle.importKey("raw", keyData, "AES-CBC", false, ["encrypt"]);', - 'const key = await crypto.subtle.importKey("raw", keyData as unknown as BufferSource, "AES-CBC", false, ["encrypt"]);' - ); - data = data.replace( - "const ctBlock = await this._crypto.subtle.encrypt(params, this._key, block.data);", - "const ctBlock = await this._crypto.subtle.encrypt(params, this._key, block.data as unknown as BufferSource);" - ); - await fs.writeFile(webcryptoAes, data); + await replaceOrThrow({ + filePath: webcryptoAes, + from: 'const key = await crypto.subtle.importKey("raw", keyData, "AES-CBC", false, ["encrypt"]);', + to: 'const key = await crypto.subtle.importKey("raw", keyData as unknown as BufferSource, "AES-CBC", false, ["encrypt"]);', + description: "webcrypto/aes.ts importKey BufferSource cast", + }); + await replaceOrThrow({ + filePath: webcryptoAes, + from: "const ctBlock = await this._crypto.subtle.encrypt(params, this._key, block.data);", + to: "const ctBlock = await this._crypto.subtle.encrypt(params, this._key, block.data as unknown as BufferSource);", + description: "webcrypto/aes.ts encrypt BufferSource cast", + }); } // 3) webcrypto/aes_ctr.ts: importKey와 encrypt 호출에서 동일 수정 @@ -91,16 +111,18 @@ async function patchMiscreant() { "miscreant/src/providers/webcrypto/aes_ctr.ts" ); if (webcryptoAesCtr) { - let data = await fs.readFile(webcryptoAesCtr, "utf8"); - data = data.replace( - 'const key = await crypto.subtle.importKey("raw", keyData, "AES-CTR", false, ["encrypt"]);', - 'const key = await crypto.subtle.importKey("raw", keyData as unknown as BufferSource, "AES-CTR", false, ["encrypt"]);' - ); - data = data.replace( - 'const ciphertext = await this.crypto.subtle.encrypt(\n { name: "AES-CTR", counter: iv, length: 16 },\n this.key,\n plaintext,\n );', - 'const ciphertext = await this.crypto.subtle.encrypt(\n { name: "AES-CTR", counter: iv as unknown as BufferSource, length: 16 },\n this.key,\n plaintext as unknown as BufferSource,\n );' - ); - await fs.writeFile(webcryptoAesCtr, data); + await replaceOrThrow({ + filePath: webcryptoAesCtr, + from: 'const key = await crypto.subtle.importKey("raw", keyData, "AES-CTR", false, ["encrypt"]);', + to: 'const key = await crypto.subtle.importKey("raw", keyData as unknown as BufferSource, "AES-CTR", false, ["encrypt"]);', + description: "webcrypto/aes_ctr.ts importKey BufferSource cast", + }); + await replaceOrThrow({ + filePath: webcryptoAesCtr, + from: 'const ciphertext = await this.crypto.subtle.encrypt(\n { name: "AES-CTR", counter: iv, length: 16 },\n this.key,\n plaintext,\n );', + to: 'const ciphertext = await this.crypto.subtle.encrypt(\n { name: "AES-CTR", counter: iv as unknown as BufferSource, length: 16 },\n this.key,\n plaintext as unknown as BufferSource,\n );', + description: "webcrypto/aes_ctr.ts encrypt BufferSource cast", + }); } } From 1367850b7d49823e023fa4cfb7438385db52842e Mon Sep 17 00:00:00 2001 From: rowan Date: Mon, 30 Mar 2026 17:17:30 +0900 Subject: [PATCH 26/64] fix(extension): harden ETH token send preview parsing Improve the signing preview renderer for ERC20 transfers by accepting case-insensitive calldata selectors and preserving large token amounts without forcing them through JavaScript Number. - normalize the 4-byte function selector before comparing it with the ERC20 transfer selector - construct Dec directly from the amount string to avoid precision loss on large values --- .../src/pages/sign/components/eth-tx/render/send-token.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/extension/src/pages/sign/components/eth-tx/render/send-token.tsx b/apps/extension/src/pages/sign/components/eth-tx/render/send-token.tsx index c5d25b22a2..344e168eb2 100644 --- a/apps/extension/src/pages/sign/components/eth-tx/render/send-token.tsx +++ b/apps/extension/src/pages/sign/components/eth-tx/render/send-token.tsx @@ -19,7 +19,7 @@ export const EthSendTokenTx: IEthTxRenderer = { process(chainId, unsignedTx) { if (unsignedTx.data && unsignedTx.data !== "0x" && unsignedTx.to) { try { - const selector = unsignedTx.data.slice(0, 10); + const selector = unsignedTx.data.slice(0, 10).toLowerCase(); if (selector !== AbiFunction.getSelector(erc20TransferFunction)) { return undefined; } @@ -159,7 +159,7 @@ export const EthSendTokenTxPretty: React.FunctionComponent<{ `Unexpected chain type "${u.type}" for EthSendTokenTxPretty (${mcInfo2.chainId})` ); })(); - const amountCoinPretty = new CoinPretty(currency, new Dec(Number(amount))); + const amountCoinPretty = new CoinPretty(currency, new Dec(amount)); const theme = useTheme(); From 10905dc631925ebef638f4e07faa41c375c0d8c7 Mon Sep 17 00:00:00 2001 From: rowan Date: Mon, 30 Mar 2026 17:40:18 +0900 Subject: [PATCH 27/64] fix(unit): update ESLint rule for precision loss handling --- packages/unit/.eslintrc.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/unit/.eslintrc.json b/packages/unit/.eslintrc.json index aab9d2206f..8f56043e04 100644 --- a/packages/unit/.eslintrc.json +++ b/packages/unit/.eslintrc.json @@ -11,7 +11,7 @@ { "files": ["**/*.spec.ts", "**/*.spec.js"], "rules": { - "no-loss-of-precision": "off" + "@typescript-eslint/no-loss-of-precision": "off" } } ] From 9edf23d0e0ee3225664a32fdf677706cf1c9e145 Mon Sep 17 00:00:00 2001 From: rowan Date: Mon, 30 Mar 2026 17:51:13 +0900 Subject: [PATCH 28/64] fix(extension): normalize ETH token display amounts --- .../sign/components/eth-tx/render/send-token.tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/apps/extension/src/pages/sign/components/eth-tx/render/send-token.tsx b/apps/extension/src/pages/sign/components/eth-tx/render/send-token.tsx index 344e168eb2..4d8617aee9 100644 --- a/apps/extension/src/pages/sign/components/eth-tx/render/send-token.tsx +++ b/apps/extension/src/pages/sign/components/eth-tx/render/send-token.tsx @@ -15,6 +15,10 @@ import { Stack } from "../../../../../components/stack"; import { useTheme } from "styled-components"; import { FormattedMessage } from "react-intl"; +function normalizeDisplayAmount(amount: string): string { + return amount.startsWith("0x") ? BigInt(amount).toString() : amount; +} + export const EthSendTokenTx: IEthTxRenderer = { process(chainId, unsignedTx) { if (unsignedTx.data && unsignedTx.data !== "0x" && unsignedTx.to) { @@ -73,7 +77,7 @@ export const EthSendTokenTx: IEthTxRenderer = { ), @@ -129,7 +133,7 @@ export const EthSendTokenTx: IEthTxRenderer = { ), }; @@ -159,7 +163,10 @@ export const EthSendTokenTxPretty: React.FunctionComponent<{ `Unexpected chain type "${u.type}" for EthSendTokenTxPretty (${mcInfo2.chainId})` ); })(); - const amountCoinPretty = new CoinPretty(currency, new Dec(amount)); + const amountCoinPretty = new CoinPretty( + currency, + new Dec(normalizeDisplayAmount(amount)) + ); const theme = useTheme(); From dd3f30acc91480bbe9b3fdac016f782eafd51132 Mon Sep 17 00:00:00 2001 From: jungcome7 Date: Mon, 30 Mar 2026 18:19:28 +0900 Subject: [PATCH 29/64] fix: use consistent key format for WebSocket subscription map lookup subscriptionMap.set() uses `${subscriptionId}/${providerId}` as key, but subscriptionMap.get() was using `subscriptionId` alone, causing unsubscribe to always miss and leak WebSocket connections. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/background/src/keyring-ethereum/ws-handlers.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/background/src/keyring-ethereum/ws-handlers.ts b/packages/background/src/keyring-ethereum/ws-handlers.ts index 489fdf65a6..9d7c2f67b0 100644 --- a/packages/background/src/keyring-ethereum/ws-handlers.ts +++ b/packages/background/src/keyring-ethereum/ws-handlers.ts @@ -131,7 +131,8 @@ export class EvmWebSocketManager { ); } - const subscribedWs = this.subscriptionMap.get(subscriptionId); + const key = `${subscriptionId}/${providerId}`; + const subscribedWs = this.subscriptionMap.get(key); if (!subscribedWs) { return false; } From cdc14c9ba0db49014f85313812d8649cc32cf1af Mon Sep 17 00:00:00 2001 From: jungcome7 Date: Mon, 30 Mar 2026 18:05:51 +0900 Subject: [PATCH 30/64] feat: add EIP-7702 Type 4 serialization and authorization signing (KEPLR-2043) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Infrastructure for EIP-7702 Smart Account upgrade/downgrade. No behavior change — all 7702 paths remain blocked in service.ts until KEPLR-2044. types.ts: - Add authorizationList field to UnsignedEVMTransaction serialize.ts: - Add Type 4 (EIP-7702) serialization via ox TxEnvelopeEip7702 - Placed before EIP-1559 check since Type 4 also has maxFeePerGas helper.ts: - signAuthorizationList(): signs authorization tuples using ox Authorization.getSignPayload() + "noop" digest (already hashed). Includes chainId=0 guard against cross-chain replay attacks. No callers yet — wired up in KEPLR-2044's sendEip7702Tx(). - applyTxType(): detects Type 4 from authorizationList presence, falls back to existing applyEIP1559Type(). Replaces applyEIP1559Type calls in KEPLR-2044. - parseDelegation(): hardened allowedDelegators comparison to be case-insensitive. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../background/src/keyring-ethereum/helper.ts | 57 ++++++++++++++++++- packages/stores-eth/src/serialize.ts | 38 ++++++++++++- packages/stores-eth/src/types.ts | 8 +++ 3 files changed, 100 insertions(+), 3 deletions(-) diff --git a/packages/background/src/keyring-ethereum/helper.ts b/packages/background/src/keyring-ethereum/helper.ts index 8687dc30ff..3d5a00346b 100644 --- a/packages/background/src/keyring-ethereum/helper.ts +++ b/packages/background/src/keyring-ethereum/helper.ts @@ -3,6 +3,7 @@ import { EthereumProviderRpcError } from "@keplr-wallet/router"; import { UnsignedEVMTransaction } from "@keplr-wallet/stores-eth"; import type { DelegationStatus } from "@keplr-wallet/types"; import { Buffer } from "buffer/"; +import { Authorization } from "ox"; import { RPC_ERROR_INVALID_PARAMS, @@ -14,6 +15,48 @@ import { // ── EIP-7702 Delegation ─────────────────────────────────── +export async function signAuthorizationList( + keyRingService: { + sign( + chainId: string, + vaultId: string, + data: Uint8Array, + digestMethod: string + ): Promise<{ r: Uint8Array; s: Uint8Array; v: number | null }>; + }, + chainId: string, + vaultId: string, + authList: Array<{ address: string; chainId: number; nonce: bigint }> +): Promise> { + const signed: NonNullable = []; + for (const auth of authList) { + if (auth.chainId === 0) { + throw new Error("Cross-chain authorization (chainId=0) is not allowed"); + } + + const payload = Authorization.getSignPayload({ + address: auth.address as `0x${string}`, + chainId: auth.chainId, + nonce: auth.nonce, + }); + const sig = await keyRingService.sign( + chainId, + vaultId, + Buffer.from(payload.slice(2), "hex"), + "noop" + ); + signed.push({ + address: auth.address, + chainId: auth.chainId, + nonce: "0x" + auth.nonce.toString(16), + r: "0x" + Buffer.from(sig.r).toString("hex"), + s: "0x" + Buffer.from(sig.s).toString("hex"), + yParity: sig.v === 1 ? 1 : 0, + }); + } + return signed; +} + // eth_getCode 응답을 파싱하여 계정의 delegation 상태를 판별한다. // 빈 코드면 "ready", 허용된 delegator로 delegation되어 있으면 "supported", 그 외는 "unsupported". export function parseDelegation( @@ -26,7 +69,9 @@ export function parseDelegation( const lower = code.toLowerCase(); if (lower.startsWith(EIP7702_DELEGATION_PREFIX) && lower.length === 48) { const delegator = "0x" + lower.slice(8); - return allowedDelegators.includes(delegator) ? "supported" : "unsupported"; + return allowedDelegators.some((d) => d.toLowerCase() === delegator) + ? "supported" + : "unsupported"; } return "unsupported"; } @@ -104,7 +149,7 @@ export function getEthAddressFromPubkey(pubkey: { return `0x${Buffer.from(pubkey.getEthAddress()).toString("hex")}`; } -// ── EIP-1559 type detection ── +// ── Transaction type detection ── export function applyEIP1559Type(tx: UnsignedEVMTransaction): void { const isEIP1559 = !!tx.maxFeePerGas || !!tx.maxPriorityFeePerGas; @@ -113,6 +158,14 @@ export function applyEIP1559Type(tx: UnsignedEVMTransaction): void { } } +export function applyTxType(tx: UnsignedEVMTransaction): void { + if (tx.authorizationList && tx.authorizationList.length > 0) { + tx.type = 4; + return; + } + applyEIP1559Type(tx); +} + // ── Chain ID parsing ── export function parseChainIdParam(chainId: string | number): number { diff --git a/packages/stores-eth/src/serialize.ts b/packages/stores-eth/src/serialize.ts index bf8c0092c7..69ed8c6917 100644 --- a/packages/stores-eth/src/serialize.ts +++ b/packages/stores-eth/src/serialize.ts @@ -1,4 +1,4 @@ -import { TxEnvelopeEip1559, TxEnvelopeLegacy } from "ox"; +import { TxEnvelopeEip1559, TxEnvelopeEip7702, TxEnvelopeLegacy } from "ox"; import { UnsignedEVMTransaction } from "./types"; /** @@ -62,6 +62,42 @@ export function serializeEVMTransaction( ): string { const sig = signature ? parseSignature(signature) : undefined; + const isEIP7702 = + tx.type === 4 || + (tx.authorizationList != null && tx.authorizationList.length > 0); + + if (isEIP7702) { + const envelope: Parameters[0] = { + chainId: tx.chainId ?? 0, + nonce: toBigInt(tx.nonce), + gas: toBigInt(tx.gasLimit), + maxFeePerGas: toBigInt(tx.maxFeePerGas), + maxPriorityFeePerGas: toBigInt(tx.maxPriorityFeePerGas), + to: tx.to as `0x${string}`, + value: toBigInt(tx.value), + data: tx.data as `0x${string}` | undefined, + accessList: tx.accessList as + | readonly { + address: `0x${string}`; + storageKeys: readonly `0x${string}`[]; + }[] + | undefined, + authorizationList: (tx.authorizationList ?? []).map((a) => ({ + address: a.address as `0x${string}`, + chainId: a.chainId, + nonce: toBigInt(a.nonce) ?? BigInt(0), + r: toBigInt(a.r) ?? BigInt(0), + s: toBigInt(a.s) ?? BigInt(0), + yParity: a.yParity ?? 0, + })), + }; + + return TxEnvelopeEip7702.serialize( + envelope, + sig ? { signature: sig } : undefined + ); + } + const isEIP1559 = tx.type === 2 || tx.maxFeePerGas != null || tx.maxPriorityFeePerGas != null; diff --git a/packages/stores-eth/src/types.ts b/packages/stores-eth/src/types.ts index d217962ee5..5ef0a30537 100644 --- a/packages/stores-eth/src/types.ts +++ b/packages/stores-eth/src/types.ts @@ -13,6 +13,14 @@ export type UnsignedEVMTransaction = { accessList?: Array<{ address: string; storageKeys: Array }>; maxPriorityFeePerGas?: string | number; maxFeePerGas?: string | number; + authorizationList?: Array<{ + address: string; + chainId: number; + nonce: string | number; + r?: string; + s?: string; + yParity?: number; + }>; }; export type UnsignedEVMTransactionWithErc20Approvals = From ee1bfde979e6b4a68f1e84b14280ded48e83db03 Mon Sep 17 00:00:00 2001 From: jungcome7 Date: Tue, 31 Mar 2026 14:49:53 +0900 Subject: [PATCH 31/64] fix: include authorizationList in typed transaction chainId guard Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/stores-eth/src/serialize.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/stores-eth/src/serialize.ts b/packages/stores-eth/src/serialize.ts index 95a61a5bf5..771b004a69 100644 --- a/packages/stores-eth/src/serialize.ts +++ b/packages/stores-eth/src/serialize.ts @@ -70,7 +70,8 @@ export function serializeEVMTransaction( tx.type != null || tx.accessList != null || tx.maxFeePerGas != null || - tx.maxPriorityFeePerGas != null; + tx.maxPriorityFeePerGas != null || + tx.authorizationList != null; // Legacy transactions may omit chainId, but typed transactions should // always be bound to an explicit network. We intentionally avoid From acb5d5f078e2d203b5111aef2552842399243649 Mon Sep 17 00:00:00 2001 From: jungcome7 Date: Tue, 31 Mar 2026 19:00:53 +0900 Subject: [PATCH 32/64] refactor: pass authorizationList to eth_estimateGas and use applyTxType in tx-executor Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/background/src/tx-executor/service.ts | 8 ++------ packages/background/src/tx-executor/utils/evm.ts | 3 +++ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/background/src/tx-executor/service.ts b/packages/background/src/tx-executor/service.ts index 3bfa31c468..57053a0a5f 100644 --- a/packages/background/src/tx-executor/service.ts +++ b/packages/background/src/tx-executor/service.ts @@ -35,6 +35,7 @@ import { EthTxStatus, } from "@keplr-wallet/types"; import { serializeEVMTransaction } from "@keplr-wallet/stores-eth"; +import { applyTxType } from "../keyring-ethereum/helper"; import { BaseAccount } from "@keplr-wallet/cosmos"; import { Any } from "@keplr-wallet/proto-types/google/protobuf/any"; import { TxRaw } from "@keplr-wallet/proto-types/cosmos/tx/v1beta1/tx"; @@ -607,12 +608,7 @@ export class BackgroundTxExecutorService { ); const signedTxData = JSON.parse(Buffer.from(result.signingData).toString()); - const isEIP1559 = - !!signedTxData.maxFeePerGas || !!signedTxData.maxPriorityFeePerGas; - if (isEIP1559) { - signedTxData.type = 2; - } - + applyTxType(signedTxData); delete signedTxData.from; return serializeEVMTransaction(signedTxData, result.signature); diff --git a/packages/background/src/tx-executor/utils/evm.ts b/packages/background/src/tx-executor/utils/evm.ts index 9c9c7032d5..a1865ea141 100644 --- a/packages/background/src/tx-executor/utils/evm.ts +++ b/packages/background/src/tx-executor/utils/evm.ts @@ -104,6 +104,9 @@ export async function fillUnsignedEVMTx( to: tx.to, value: tx.value, data: tx.data, + ...(tx.authorizationList + ? { authorizationList: tx.authorizationList } + : {}), }, ], id: ESTIMATE_GAS_ID, From 0406f1cb876a8f1b5fb0cc148fa2e362f135cfb6 Mon Sep 17 00:00:00 2001 From: jungcome7 Date: Tue, 31 Mar 2026 19:00:54 +0900 Subject: [PATCH 33/64] feat: add smart account message classes and handler routing Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/keyring-ethereum/handler.ts | 28 ++++ .../background/src/keyring-ethereum/init.ts | 8 + .../src/keyring-ethereum/messages.ts | 141 ++++++++++++++++++ 3 files changed, 177 insertions(+) diff --git a/packages/background/src/keyring-ethereum/handler.ts b/packages/background/src/keyring-ethereum/handler.ts index f363b87866..d0dab96c1d 100644 --- a/packages/background/src/keyring-ethereum/handler.ts +++ b/packages/background/src/keyring-ethereum/handler.ts @@ -10,6 +10,10 @@ import { RequestJsonRpcToEvmMsg, GetNewCurrentChainIdForEVMMsg, CheckNeedEnableAccessForEVMMsg, + UpgradeSmartAccountMsg, + DowngradeSmartAccountMsg, + GetSmartAccountDelegationStatusMsg, + EstimateSmartAccountFeeMsg, } from "./messages"; import { KeyRingEthereumService } from "./service"; import { PermissionInteractiveService } from "../permission-interactive"; @@ -43,6 +47,30 @@ export const getHandler: ( env, msg as CheckNeedEnableAccessForEVMMsg ); + case UpgradeSmartAccountMsg: + return service.upgradeToSmartAccount( + env, + (msg as UpgradeSmartAccountMsg).chainId, + (msg as UpgradeSmartAccountMsg).vaultId + ); + case DowngradeSmartAccountMsg: + return service.downgradeSmartAccount( + env, + (msg as DowngradeSmartAccountMsg).chainId, + (msg as DowngradeSmartAccountMsg).vaultId + ); + case GetSmartAccountDelegationStatusMsg: + return service.getDelegationStatus( + env, + (msg as GetSmartAccountDelegationStatusMsg).chainId, + (msg as GetSmartAccountDelegationStatusMsg).vaultId + ); + case EstimateSmartAccountFeeMsg: + return service.estimateSmartAccountFee( + env, + (msg as EstimateSmartAccountFeeMsg).chainId, + (msg as EstimateSmartAccountFeeMsg).vaultId + ); default: throw new KeplrError("keyring", 221, "Unknown msg type"); } diff --git a/packages/background/src/keyring-ethereum/init.ts b/packages/background/src/keyring-ethereum/init.ts index 8b2210d402..231e679686 100644 --- a/packages/background/src/keyring-ethereum/init.ts +++ b/packages/background/src/keyring-ethereum/init.ts @@ -5,6 +5,10 @@ import { RequestSignEthereumMsg, GetNewCurrentChainIdForEVMMsg, CheckNeedEnableAccessForEVMMsg, + UpgradeSmartAccountMsg, + DowngradeSmartAccountMsg, + GetSmartAccountDelegationStatusMsg, + EstimateSmartAccountFeeMsg, } from "./messages"; import { ROUTE } from "./constants"; import { getHandler } from "./handler"; @@ -19,6 +23,10 @@ export function init( router.registerMessage(RequestJsonRpcToEvmMsg); router.registerMessage(GetNewCurrentChainIdForEVMMsg); router.registerMessage(CheckNeedEnableAccessForEVMMsg); + router.registerMessage(UpgradeSmartAccountMsg); + router.registerMessage(DowngradeSmartAccountMsg); + router.registerMessage(GetSmartAccountDelegationStatusMsg); + router.registerMessage(EstimateSmartAccountFeeMsg); router.addHandler(ROUTE, getHandler(service, permissionInteractionService)); } diff --git a/packages/background/src/keyring-ethereum/messages.ts b/packages/background/src/keyring-ethereum/messages.ts index 1db7ad42ba..3e1efe4784 100644 --- a/packages/background/src/keyring-ethereum/messages.ts +++ b/packages/background/src/keyring-ethereum/messages.ts @@ -111,6 +111,147 @@ export class GetNewCurrentChainIdForEVMMsg extends Message { } } +export class UpgradeSmartAccountMsg extends Message { + public static type() { + return "upgrade-smart-account"; + } + + constructor( + public readonly chainId: string, + public readonly vaultId: string + ) { + super(); + } + + validateBasic(): void { + if (!this.chainId) { + throw new Error("chain id not set"); + } + if (!this.vaultId) { + throw new Error("vault id not set"); + } + } + + override approveExternal(): boolean { + return false; + } + + route(): string { + return ROUTE; + } + + type(): string { + return UpgradeSmartAccountMsg.type(); + } +} + +export class DowngradeSmartAccountMsg extends Message { + public static type() { + return "downgrade-smart-account"; + } + + constructor( + public readonly chainId: string, + public readonly vaultId: string + ) { + super(); + } + + validateBasic(): void { + if (!this.chainId) { + throw new Error("chain id not set"); + } + if (!this.vaultId) { + throw new Error("vault id not set"); + } + } + + override approveExternal(): boolean { + return false; + } + + route(): string { + return ROUTE; + } + + type(): string { + return DowngradeSmartAccountMsg.type(); + } +} + +export class GetSmartAccountDelegationStatusMsg extends Message { + public static type() { + return "get-smart-account-delegation-status"; + } + + constructor( + public readonly chainId: string, + public readonly vaultId: string + ) { + super(); + } + + validateBasic(): void { + if (!this.chainId) { + throw new Error("chain id not set"); + } + if (!this.vaultId) { + throw new Error("vault id not set"); + } + } + + override approveExternal(): boolean { + return false; + } + + route(): string { + return ROUTE; + } + + type(): string { + return GetSmartAccountDelegationStatusMsg.type(); + } +} + +export class EstimateSmartAccountFeeMsg extends Message<{ + gasLimit: string; + maxFeePerGas: string; + maxPriorityFeePerGas: string; + estimatedFeeWei: string; +}> { + public static type() { + return "estimate-smart-account-fee"; + } + + constructor( + public readonly chainId: string, + public readonly vaultId: string + ) { + super(); + } + + validateBasic(): void { + if (!this.chainId) { + throw new Error("chain id not set"); + } + if (!this.vaultId) { + throw new Error("vault id not set"); + } + } + + override approveExternal(): boolean { + return false; + } + + route(): string { + return ROUTE; + } + + type(): string { + return EstimateSmartAccountFeeMsg.type(); + } +} + export class CheckNeedEnableAccessForEVMMsg extends Message { public static type() { return "check-need-enable-access-for-evm"; From 6c74327ae29053c535f8a259e2051a109f93a018 Mon Sep 17 00:00:00 2001 From: jungcome7 Date: Tue, 31 Mar 2026 19:00:54 +0900 Subject: [PATCH 34/64] feat: add EIP-7702 validation helpers and constants Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/keyring-ethereum/constants.ts | 2 + .../background/src/keyring-ethereum/helper.ts | 41 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/packages/background/src/keyring-ethereum/constants.ts b/packages/background/src/keyring-ethereum/constants.ts index dbdd2ff1e1..909691fff6 100644 --- a/packages/background/src/keyring-ethereum/constants.ts +++ b/packages/background/src/keyring-ethereum/constants.ts @@ -37,3 +37,5 @@ export const EIP7702_DELEGATION_PREFIX = "0xef0100"; export const ALLOWED_DELEGATORS: readonly string[] = [ "0x63c0c19a282a1b52b07dd5a65b58948a07dae32b", ]; + +export const EVM_ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; diff --git a/packages/background/src/keyring-ethereum/helper.ts b/packages/background/src/keyring-ethereum/helper.ts index 3d5a00346b..951cc989bf 100644 --- a/packages/background/src/keyring-ethereum/helper.ts +++ b/packages/background/src/keyring-ethereum/helper.ts @@ -11,10 +11,35 @@ import { SIGNATURE_V_FALSE, SIGNATURE_V_TRUE, EIP7702_DELEGATION_PREFIX, + EVM_ZERO_ADDRESS, } from "./constants"; // ── EIP-7702 Delegation ─────────────────────────────────── +export function validateAuthorizationList( + authList: Array<{ chainId: string | number; address: string }>, + allowedDelegators: readonly string[] +): void { + for (const auth of authList) { + const numericChainId = + typeof auth.chainId === "string" + ? parseInt(auth.chainId, auth.chainId.startsWith("0x") ? 16 : 10) + : Number(auth.chainId); + if (numericChainId === 0 || !Number.isFinite(numericChainId)) { + throw new Error("chain_id=0 authorization is not allowed."); + } + const addr = ( + typeof auth.address === "string" ? auth.address : "" + ).toLowerCase(); + if ( + addr !== EVM_ZERO_ADDRESS && + !allowedDelegators.some((d) => d.toLowerCase() === addr) + ) { + throw new Error("Unauthorized delegator address."); + } + } +} + export async function signAuthorizationList( keyRingService: { sign( @@ -76,6 +101,22 @@ export function parseDelegation( return "unsupported"; } +export function buildDummyAuthorizationList( + address: string, + chainId: number +): NonNullable { + return [ + { + address, + chainId, + nonce: "0x1", + r: "0x" + "1".repeat(64), + s: "0x" + "1".repeat(64), + yParity: 1, + }, + ]; +} + // ── Types ── export interface EVMTransactionParam { From e2e4d3e18b1ba9407f22fffb83cc66dcc89e96b7 Mon Sep 17 00:00:00 2001 From: jungcome7 Date: Tue, 31 Mar 2026 19:00:54 +0900 Subject: [PATCH 35/64] feat: implement EIP-7702 smart account upgrade/downgrade service Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/keyring-ethereum/service.ts | 305 ++++++++++++++++-- 1 file changed, 283 insertions(+), 22 deletions(-) diff --git a/packages/background/src/keyring-ethereum/service.ts b/packages/background/src/keyring-ethereum/service.ts index 6a335baae8..24e48e000a 100644 --- a/packages/background/src/keyring-ethereum/service.ts +++ b/packages/background/src/keyring-ethereum/service.ts @@ -13,6 +13,7 @@ import type { GetCapabilitiesResult, } from "@keplr-wallet/types"; import { Buffer } from "buffer/"; +import { retry } from "@keplr-wallet/common"; import { domainHash, EIP712MessageValidator, @@ -28,7 +29,8 @@ import { PermissionService } from "../permission"; import { BackgroundTxEthereumService } from "../tx-ethereum"; import { TokenERC20Service } from "../token-erc20"; import { - applyEIP1559Type, + applyTxType, + buildDummyAuthorizationList, encodeSignature, getEthAddressFromPubkey, normalizeUnsignedTx, @@ -37,6 +39,8 @@ import { parseDelegation, parseTxParam, rethrowAsProviderError, + signAuthorizationList, + validateAuthorizationList, validateEVMChainId, } from "./helper"; import { PermissionInteractiveService } from "../permission-interactive"; @@ -44,12 +48,14 @@ import { DEFAULT_EVM_CHAIN_ID, ALLOWED_DELEGATORS, ERC20_TRANSFER_SELECTOR, + EVM_ZERO_ADDRESS, RPC_ERROR_INVALID_PARAMS, RPC_ERROR_UNAUTHORIZED, RPC_ERROR_UNSUPPORTED_METHOD, } from "./constants"; import { EvmWebSocketManager } from "./ws-handlers"; import { EvmChainHandler } from "./chain-handlers"; +import { fillUnsignedEVMTx } from "../tx-executor/utils/evm"; export class KeyRingEthereumService { private readonly wsManager: EvmWebSocketManager; @@ -132,19 +138,10 @@ export class KeyRingEthereumService { chainId: string, address: string ): Promise { - const modularChainInfo = - this.chainsService.getModularChainInfoOrThrow(chainId); const evmInfo = this.chainsService.getEVMInfoOrThrow(chainId); const hexChainId = `0x${evmInfo.chainId.toString(16)}`; - const features = - modularChainInfo.type === "evm" - ? modularChainInfo.evm.features - : modularChainInfo.type === "ethermint" - ? modularChainInfo.cosmos.features - : undefined; - - if (!features?.includes("eip-7702")) { + if (!this.getChainFeatures(chainId)?.includes("eip-7702")) { return {}; } @@ -170,6 +167,246 @@ export class KeyRingEthereumService { return { [hexChainId]: { atomic: { status } } }; } + // ── Smart Account Upgrade/Downgrade ───────────────────── + + async getDelegationStatus( + env: Env, + chainId: string, + vaultId: string + ): Promise { + if (!this.getChainFeatures(chainId)?.includes("eip-7702")) { + return "unsupported"; + } + const address = await this.getEthAddressForVault(chainId, vaultId); + try { + const code = await this.request( + env, + "", + "eth_getCode", + [address, "latest"], + undefined, + chainId + ); + return parseDelegation(code ?? "0x", ALLOWED_DELEGATORS); + } catch { + return "unsupported"; + } + } + + async upgradeToSmartAccount( + env: Env, + chainId: string, + vaultId: string + ): Promise { + return this.sendEip7702Tx( + env, + chainId, + ALLOWED_DELEGATORS[0], + "ready", + vaultId + ); + } + + async downgradeSmartAccount( + env: Env, + chainId: string, + vaultId: string + ): Promise { + return this.sendEip7702Tx( + env, + chainId, + EVM_ZERO_ADDRESS, + "supported", + vaultId + ); + } + + async estimateSmartAccountFee( + _env: Env, + chainId: string, + vaultId: string + ): Promise<{ + gasLimit: string; + maxFeePerGas: string; + maxPriorityFeePerGas: string; + estimatedFeeWei: string; + }> { + const evmInfo = this.chainsService.getEVMInfoOrThrow(chainId); + const address = await this.getEthAddressForVault(chainId, vaultId); + + const filled = await fillUnsignedEVMTx("", evmInfo, address, { + to: address, + value: "0x0", + data: "0x", + authorizationList: buildDummyAuthorizationList( + ALLOWED_DELEGATORS[0], + evmInfo.chainId + ), + }); + + const gasLimit = `0x${BigInt(filled.gasLimit ?? 0).toString(16)}`; + const maxFeePerGas = `0x${BigInt(filled.maxFeePerGas ?? 0).toString(16)}`; + const maxPriorityFeePerGas = `0x${BigInt( + filled.maxPriorityFeePerGas ?? 0 + ).toString(16)}`; + + const estimatedFeeWei = + "0x" + (BigInt(gasLimit) * BigInt(maxFeePerGas)).toString(16); + + return { gasLimit, maxFeePerGas, maxPriorityFeePerGas, estimatedFeeWei }; + } + + private async sendEip7702Tx( + env: Env, + chainId: string, + delegatorAddress: string, + requiredStatus: "ready" | "supported", + vaultId: string + ): Promise { + // 1. Chain validation + const evmInfo = this.chainsService.getEVMInfoOrThrow(chainId); + if (!this.getChainFeatures(chainId)?.includes("eip-7702")) { + throw new Error("This chain does not support EIP-7702."); + } + + // 2. Key type validation + const keyInfo = this.keyRingService.getKeyInfo(vaultId); + if (!keyInfo) { + throw new Error("No key selected."); + } + if (keyInfo.type === "ledger" || keyInfo.type === "keystone") { + throw new Error("Smart Account is not supported for hardware wallets."); + } + + // 3. Check current delegation status + const selectedAddress = await this.getEthAddressForVault(chainId, vaultId); + let code: string; + try { + code = await this.request( + env, + "", + "eth_getCode", + [selectedAddress, "latest"], + undefined, + chainId + ); + } catch { + throw new Error("Failed to check account delegation status."); + } + const status = parseDelegation(code ?? "0x", ALLOWED_DELEGATORS); + if (status !== requiredStatus) { + if (requiredStatus === "ready") { + throw new Error( + status === "supported" + ? "Account is already upgraded." + : "Account has an unsupported delegation." + ); + } else { + throw new Error( + status === "ready" + ? "Account is not upgraded." + : "Account has an unsupported delegation." + ); + } + } + + const evmChainId = evmInfo.chainId; + + // 4. Estimate gas + fee + nonce in one batch (dummy auth for estimation) + const filled = await fillUnsignedEVMTx("", evmInfo, selectedAddress, { + to: selectedAddress, + value: "0x0", + data: "0x", + authorizationList: buildDummyAuthorizationList( + delegatorAddress, + evmChainId + ), + }); + + const txNonce = Number(filled.nonce); + + // 5. Sign authorization with real nonce (self-sponsoring: auth nonce = tx nonce + 1) + const signedAuthList = await signAuthorizationList( + this.keyRingService, + chainId, + keyInfo.id, + [ + { + address: delegatorAddress, + chainId: evmChainId, + nonce: BigInt(txNonce + 1), + }, + ] + ); + + // 6. Construct unsigned tx (gas+fee from fillUnsignedEVMTx, real auth from step 5) + const unsignedTx: UnsignedEVMTransaction = { + ...filled, + from: selectedAddress, + chainId: evmChainId, + type: 4, + authorizationList: signedAuthList, + }; + + // 7. Sign tx (pre-authorized — UI confirm page serves as user consent) + const { signature } = await this.signEthereumPreAuthorized( + vaultId, + chainId, + selectedAddress, + Buffer.from(JSON.stringify(unsignedTx)), + EthSignType.TRANSACTION + ); + + // 8. Serialize + broadcast + delete unsignedTx.from; + const signedTxHex = serializeEVMTransaction(unsignedTx, signature); + const signedTx = Buffer.from(signedTxHex.replace("0x", ""), "hex"); + + const txHash = await this.backgroundTxEthereumService.sendEthereumTx( + "", + chainId, + signedTx, + { skipTracingTxResult: true } + ); + + // 9. Poll for confirmation (same pattern as BackgroundTxEthereumService) + let reverted = false; + try { + await retry( + async () => { + const receipt = await this.request<{ status?: string } | null>( + env, + "", + "eth_getTransactionReceipt", + [txHash], + undefined, + chainId + ); + if (receipt) { + if (receipt.status === "0x0") { + reverted = true; + } + return; + } + throw new Error("No tx receipt yet"); + }, + { + maxRetries: 50, + waitMsAfterError: 500, + maxWaitMsAfterError: 15000, + } + ); + } catch { + // Timeout — return hash without confirmation + } + + if (reverted) { + throw new Error("EIP-7702 transaction reverted on chain."); + } + + return txHash; + } + // ── Signing ────────────────────────────────────────────── async signEthereumSelected( @@ -239,7 +476,13 @@ export class KeyRingEthereumService { if (signType === EthSignType.TRANSACTION) { const unsignedTx = JSON.parse(Buffer.from(message).toString()); if (unsignedTx.authorizationList) { - throw new Error("EIP-7702 transactions are not supported."); + if (!env.isInternalMsg) { + throw new Error("External EIP-7702 transactions are not supported."); + } + validateAuthorizationList( + unsignedTx.authorizationList, + ALLOWED_DELEGATORS + ); } const hasRequiredErc20Approvals = @@ -313,7 +556,7 @@ export class KeyRingEthereumService { Buffer.from(res.signingData).toString() ); - applyEIP1559Type(unsignedTx); + applyTxType(unsignedTx); delete unsignedTx.from; @@ -473,13 +716,6 @@ export class KeyRingEthereumService { ); } - if (signType === EthSignType.TRANSACTION) { - const unsignedTx = JSON.parse(Buffer.from(message).toString()); - if (unsignedTx.authorizationList) { - throw new Error("EIP-7702 transactions are not supported."); - } - } - const normalizedSigner = normalizeSigner(signer); const key = await this.keyRingCosmosService.getKey(vaultId, chainId); @@ -499,7 +735,15 @@ export class KeyRingEthereumService { const unsignedTx: UnsignedEVMTransaction = JSON.parse( Buffer.from(message).toString() ); - applyEIP1559Type(unsignedTx); + + if (unsignedTx.authorizationList) { + validateAuthorizationList( + unsignedTx.authorizationList, + ALLOWED_DELEGATORS + ); + } + + applyTxType(unsignedTx); const signature = await this.keyRingService.sign( chainId, @@ -962,10 +1206,27 @@ export class KeyRingEthereumService { const signingTx = JSON.parse(Buffer.from(signingData).toString()); - applyEIP1559Type(signingTx); + applyTxType(signingTx); const signedTxHex = serializeEVMTransaction(signingTx, signature); return { signedTxHex }; } + + // ── Private Helpers ──────────────────────────────────── + + private getChainFeatures(chainId: string): string[] | undefined { + const info = this.chainsService.getModularChainInfoOrThrow(chainId); + if (info.type === "evm") return info.evm.features; + if (info.type === "ethermint") return info.cosmos.features; + return undefined; + } + + private async getEthAddressForVault( + chainId: string, + vaultId: string + ): Promise { + const pubkey = await this.keyRingService.getPubKey(chainId, vaultId); + return getEthAddressFromPubkey(pubkey); + } } From fcd94ff0e991fa905375865cbd4752f603f2dda0 Mon Sep 17 00:00:00 2001 From: jungcome7 Date: Tue, 31 Mar 2026 19:00:54 +0900 Subject: [PATCH 36/64] test: add validateAuthorizationList tests Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/keyring-ethereum/helper.spec.ts | 111 +++++++++++++++++- 1 file changed, 110 insertions(+), 1 deletion(-) diff --git a/packages/background/src/keyring-ethereum/helper.spec.ts b/packages/background/src/keyring-ethereum/helper.spec.ts index 8280bdeb3e..005fab46b8 100644 --- a/packages/background/src/keyring-ethereum/helper.spec.ts +++ b/packages/background/src/keyring-ethereum/helper.spec.ts @@ -11,9 +11,14 @@ import { parseTxParam, rethrowAsProviderError, toHexQty, + validateAuthorizationList, validateEVMChainId, } from "./helper"; -import { SIGNATURE_V_FALSE, SIGNATURE_V_TRUE } from "./constants"; +import { + SIGNATURE_V_FALSE, + SIGNATURE_V_TRUE, + EVM_ZERO_ADDRESS, +} from "./constants"; // ── validateEVMChainId ───────────────────────────────────── @@ -340,3 +345,107 @@ describe("parseDelegation", () => { ); }); }); + +// ── validateAuthorizationList ────────────────────────── + +describe("validateAuthorizationList", () => { + const allowed = ["0x63c0c19a282a1b52b07dd5a65b58948a07dae32b"]; + + it("passes for allowed delegator with numeric chainId", () => { + expect(() => + validateAuthorizationList([{ chainId: 1, address: allowed[0] }], allowed) + ).not.toThrow(); + }); + + it("passes for allowed delegator with hex string chainId", () => { + expect(() => + validateAuthorizationList( + [{ chainId: "0x1", address: allowed[0] }], + allowed + ) + ).not.toThrow(); + }); + + it("passes for allowed delegator with decimal string chainId", () => { + expect(() => + validateAuthorizationList( + [{ chainId: "137", address: allowed[0] }], + allowed + ) + ).not.toThrow(); + }); + + it("passes for zero address (downgrade)", () => { + expect(() => + validateAuthorizationList( + [{ chainId: 1, address: EVM_ZERO_ADDRESS }], + allowed + ) + ).not.toThrow(); + }); + + it("throws for chainId = 0 (number)", () => { + expect(() => + validateAuthorizationList([{ chainId: 0, address: allowed[0] }], allowed) + ).toThrow("chain_id=0"); + }); + + it("throws for chainId = '0' (string)", () => { + expect(() => + validateAuthorizationList( + [{ chainId: "0", address: allowed[0] }], + allowed + ) + ).toThrow("chain_id=0"); + }); + + it("throws for chainId = '0x0' (hex zero)", () => { + expect(() => + validateAuthorizationList( + [{ chainId: "0x0", address: allowed[0] }], + allowed + ) + ).toThrow("chain_id=0"); + }); + + it("throws for unauthorized delegator address", () => { + expect(() => + validateAuthorizationList( + [{ chainId: 1, address: "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" }], + allowed + ) + ).toThrow("Unauthorized delegator"); + }); + + it("is case-insensitive for address matching", () => { + const upperCase = allowed[0].toUpperCase().replace("0X", "0x"); + expect(() => + validateAuthorizationList([{ chainId: 1, address: upperCase }], allowed) + ).not.toThrow(); + }); + + it("throws for NaN chainId", () => { + expect(() => + validateAuthorizationList( + [{ chainId: "notanumber", address: allowed[0] }], + allowed + ) + ).toThrow("chain_id=0"); + }); + + it("validates all entries in the list", () => { + expect(() => + validateAuthorizationList( + [ + { chainId: 1, address: allowed[0] }, + { chainId: 0, address: allowed[0] }, + ], + allowed + ) + ).toThrow("chain_id=0"); + }); + + it("passes for empty list", () => { + expect(() => validateAuthorizationList([], allowed)).not.toThrow(); + }); +}); From 5a7b1167a82ba168377d0dfe5445baab76a9834e Mon Sep 17 00:00:00 2001 From: jungcome7 Date: Tue, 31 Mar 2026 20:34:06 +0900 Subject: [PATCH 37/64] fix: use `as number` instead of `?? 0` for EIP-7702 chainId Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/stores-eth/src/serialize.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stores-eth/src/serialize.ts b/packages/stores-eth/src/serialize.ts index 771b004a69..d8d1e3c97f 100644 --- a/packages/stores-eth/src/serialize.ts +++ b/packages/stores-eth/src/serialize.ts @@ -86,7 +86,7 @@ export function serializeEVMTransaction( if (isEIP7702) { const envelope: Parameters[0] = { - chainId: tx.chainId ?? 0, + chainId: tx.chainId as number, nonce: toBigInt(tx.nonce), gas: toBigInt(tx.gasLimit), maxFeePerGas: toBigInt(tx.maxFeePerGas), From 7f17586585b73c343395b9f0b47ad4aba5ef3471 Mon Sep 17 00:00:00 2001 From: rowan Date: Wed, 1 Apr 2026 11:02:56 +0900 Subject: [PATCH 38/64] feat: define EIP-5792 types and error codes Add EthSignType.EIP5792, WalletSendCallsRequest, InternalSendCallsRequest, BatchSigningData, EthereumBatchSignResponse, BatchCallStatusCode, and WalletGetCallStatusResponse types. Add EIP-5792 error code constants (5700, 5710, 5730, 5760). --- .../src/keyring-ethereum/constants.ts | 6 ++ packages/types/src/ethereum.ts | 64 +++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/packages/background/src/keyring-ethereum/constants.ts b/packages/background/src/keyring-ethereum/constants.ts index dbdd2ff1e1..fc599b11a4 100644 --- a/packages/background/src/keyring-ethereum/constants.ts +++ b/packages/background/src/keyring-ethereum/constants.ts @@ -18,6 +18,12 @@ export const RPC_ERROR_UNSUPPORTED_METHOD = 4200; export const RPC_ERROR_INVALID_PARAMS = -32602; export const RPC_ERROR_UNRECOGNIZED_CHAIN = 4902; +// EIP-5792 error codes +export const EIP5792_ERROR_UNSUPPORTED_CAPABILITY = 5700; +export const EIP5792_ERROR_UNSUPPORTED_CHAIN = 5710; +export const EIP5792_ERROR_UNKNOWN_BUNDLE = 5730; +export const EIP5792_ERROR_ATOMICITY_NOT_SUPPORTED = 5760; + // ERC-20 transfer(address,uint256) function selector export const ERC20_TRANSFER_SELECTOR = "0xa9059cbb"; diff --git a/packages/types/src/ethereum.ts b/packages/types/src/ethereum.ts index 0e9225ee73..24711f40cd 100644 --- a/packages/types/src/ethereum.ts +++ b/packages/types/src/ethereum.ts @@ -2,6 +2,7 @@ export enum EthSignType { MESSAGE = "message", TRANSACTION = "transaction", EIP712 = "eip-712", + EIP5792 = "eip-5792", } export interface EthTxLog { @@ -56,6 +57,69 @@ export type DelegationStatus = "supported" | "ready" | "unsupported"; export type GetCapabilitiesResult = Record>; +// ── EIP-5792: wallet_sendCalls ────────────────────────────── + +// dApp → Background: raw request +export type WalletSendCallsRequest = { + atomicRequired: boolean; + calls: EIP5792Call[]; + chainId: string; // hex + version: string; // "2.0.0" + from?: string; + capabilities?: Record; +}; + +export type EIP5792Call = { + to?: string; + data?: string; + value?: string; +}; + +// Background → Frontend: interaction data +export type InternalSendCallsRequest = { + batchId: string; + calls: EIP5792Call[]; + apiVersion: string; + nonce: number; + chainCapabilities: { atomic: { status: DelegationStatus } }; +}; + +// Frontend → Background: signing data +export type BatchStrategy = "single" | "atomic" | "unavailable"; + +export interface BatchSigningData { + strategy: BatchStrategy; + batchId: string; + unsignedTxs: Record[]; +} + +// signEthereumBatch response +export interface EthereumBatchSignResponse { + strategy: BatchStrategy; + batchId: string; + signedTxs: string[]; +} + +// wallet_getCallsStatus response +export enum BatchCallStatusCode { + Pending = 100, + Confirmed = 200, + OffchainFailed = 400, + ChainRulesFailed = 500, + PartialFailed = 600, +} + +export type WalletGetCallStatusResponse = { + version: string; + chainId: string; + id: string; + status: BatchCallStatusCode; + atomic: boolean; + receipts: EthTxReceipt[]; +}; + +// ── EVM Gas Simulation ────────────────────────────────────── + export enum EvmGasSimulationOutcome { TX_SIMULATED = "tx-simulated", TX_BUNDLE_SIMULATED = "tx-bundle-simulated", From 903384dccdd49ed8e7e88019bd2ba4d4fd7d8044 Mon Sep 17 00:00:00 2001 From: rowan Date: Wed, 1 Apr 2026 11:05:07 +0900 Subject: [PATCH 39/64] feat: add EIP-5792 request validation helpers with tests Extract validateSendCallsRequest and validateEIP5792Call as pure functions for testability. Validates chainId, version, non-optional capabilities, call address/value/data format using ox library. Add 18 unit tests covering edge cases: bare 0x data, 0x0 value, mixed-case addresses, simple ETH transfer without data, non-hex values, unsupported versions, optional vs non-optional capabilities. --- .../src/keyring-ethereum/helper.spec.ts | 172 ++++++++++++++++++ .../background/src/keyring-ethereum/helper.ts | 101 +++++++++- 2 files changed, 272 insertions(+), 1 deletion(-) diff --git a/packages/background/src/keyring-ethereum/helper.spec.ts b/packages/background/src/keyring-ethereum/helper.spec.ts index 8280bdeb3e..ec711ee2c8 100644 --- a/packages/background/src/keyring-ethereum/helper.spec.ts +++ b/packages/background/src/keyring-ethereum/helper.spec.ts @@ -11,7 +11,9 @@ import { parseTxParam, rethrowAsProviderError, toHexQty, + validateEIP5792Call, validateEVMChainId, + validateSendCallsRequest, } from "./helper"; import { SIGNATURE_V_FALSE, SIGNATURE_V_TRUE } from "./constants"; @@ -340,3 +342,173 @@ describe("parseDelegation", () => { ); }); }); + +// ── validateSendCallsRequest ────────────────────────────── + +describe("validateSendCallsRequest", () => { + const hexChainId = "0x1"; + + const validRequest = { + atomicRequired: false, + calls: [{ data: "0xdeadbeef" }], + chainId: "0x1", + version: "1.0", + }; + + it("returns null for a valid request", () => { + expect(validateSendCallsRequest(validRequest, hexChainId)).toBeNull(); + }); + + it("rejects empty calls", () => { + const err = validateSendCallsRequest( + { ...validRequest, calls: [] }, + hexChainId + ); + expect(err).not.toBeNull(); + expect(err!.code).toBe(-32602); + }); + + it("rejects mismatched chainId", () => { + const err = validateSendCallsRequest( + { ...validRequest, chainId: "0xa" }, + hexChainId + ); + expect(err).not.toBeNull(); + expect(err!.code).toBe(4902); + }); + + it("rejects unsupported version", () => { + const err = validateSendCallsRequest( + { ...validRequest, version: "3.0" }, + hexChainId + ); + expect(err).not.toBeNull(); + expect(err!.message).toContain("Unsupported version"); + }); + + it("accepts version 1.0 and 2.0.0", () => { + expect( + validateSendCallsRequest({ ...validRequest, version: "1.0" }, hexChainId) + ).toBeNull(); + expect( + validateSendCallsRequest( + { ...validRequest, version: "2.0.0" }, + hexChainId + ) + ).toBeNull(); + }); + + it("rejects non-optional unsupported capability", () => { + const err = validateSendCallsRequest( + { + ...validRequest, + capabilities: { paymasterService: { url: "https://x.com" } }, + }, + hexChainId + ); + expect(err).not.toBeNull(); + expect(err!.code).toBe(5700); + }); + + it("allows optional unsupported capability", () => { + const err = validateSendCallsRequest( + { + ...validRequest, + capabilities: { + paymasterService: { url: "https://x.com", optional: true }, + }, + }, + hexChainId + ); + expect(err).toBeNull(); + }); + + it("allows atomic capability", () => { + const err = validateSendCallsRequest( + { + ...validRequest, + capabilities: { atomic: { status: "supported" } }, + }, + hexChainId + ); + expect(err).toBeNull(); + }); +}); + +describe("validateEIP5792Call", () => { + it("accepts valid call with to, value, data", () => { + expect( + validateEIP5792Call( + { + to: "0x7099793F8A0FA7EE4a941d1F0df4E55179d8f799", + value: "0x10f2c", + data: "0xdeadbeef", + }, + 0 + ) + ).toBeNull(); + }); + + it("accepts call without to (contract deploy)", () => { + expect(validateEIP5792Call({ data: "0x6060604052" }, 0)).toBeNull(); + }); + + it("accepts call without data (simple ETH transfer)", () => { + expect( + validateEIP5792Call( + { to: "0x7099793f8a0fa7ee4a941d1f0df4e55179d8f799", value: "0x1" }, + 0 + ) + ).toBeNull(); + }); + + it("accepts bare 0x data", () => { + expect(validateEIP5792Call({ data: "0x" }, 0)).toBeNull(); + }); + + it("accepts 0x0 value", () => { + expect(validateEIP5792Call({ data: "0x", value: "0x0" }, 0)).toBeNull(); + }); + + it("rejects invalid to address", () => { + const err = validateEIP5792Call({ to: "not_an_address", data: "0x" }, 0); + expect(err).not.toBeNull(); + expect(err!.message).toContain("invalid 'to' address"); + }); + + it("rejects non-hex value", () => { + const err = validateEIP5792Call({ data: "0x", value: "12345" }, 0); + expect(err).not.toBeNull(); + expect(err!.message).toContain("invalid 'value'"); + }); + + it("rejects non-hex data", () => { + const err = validateEIP5792Call({ data: "not_hex" }, 0); + expect(err).not.toBeNull(); + expect(err!.message).toContain("invalid 'data'"); + }); + + it("accepts lowercase address", () => { + expect( + validateEIP5792Call( + { + to: "0x7099793f8a0fa7ee4a941d1f0df4e55179d8f799", + data: "0x", + }, + 0 + ) + ).toBeNull(); + }); + + it("accepts mixed-case address (no strict checksum)", () => { + expect( + validateEIP5792Call( + { + to: "0x7099793F8a0fa7ee4a941d1f0df4e55179d8f799", + data: "0x", + }, + 0 + ) + ).toBeNull(); + }); +}); diff --git a/packages/background/src/keyring-ethereum/helper.ts b/packages/background/src/keyring-ethereum/helper.ts index 8687dc30ff..6bc4710b8e 100644 --- a/packages/background/src/keyring-ethereum/helper.ts +++ b/packages/background/src/keyring-ethereum/helper.ts @@ -1,11 +1,18 @@ import { Bech32Address } from "@keplr-wallet/cosmos"; import { EthereumProviderRpcError } from "@keplr-wallet/router"; import { UnsignedEVMTransaction } from "@keplr-wallet/stores-eth"; -import type { DelegationStatus } from "@keplr-wallet/types"; +import { + DelegationStatus, + EIP5792Call, + WalletSendCallsRequest, +} from "@keplr-wallet/types"; +import { Address, Hex } from "ox"; import { Buffer } from "buffer/"; import { + EIP5792_ERROR_UNSUPPORTED_CAPABILITY, RPC_ERROR_INVALID_PARAMS, + RPC_ERROR_UNRECOGNIZED_CHAIN, RPC_ERROR_USER_REJECTED, SIGNATURE_V_FALSE, SIGNATURE_V_TRUE, @@ -244,3 +251,95 @@ export function rethrowAsProviderError(e: unknown): never { } throw e; } + +// ── EIP-5792 wallet_sendCalls Validation ────────────────── + +const SUPPORTED_VERSIONS = ["1.0", "2.0.0"]; + +export type SendCallsValidationError = { + code: number; + message: string; +}; + +/** + * Validate a wallet_sendCalls request (pure, no RPC calls). + * Returns null if valid, or an error object if invalid. + */ +export function validateSendCallsRequest( + request: WalletSendCallsRequest, + hexChainId: string +): SendCallsValidationError | null { + if (!request.calls || request.calls.length === 0) { + return { + code: RPC_ERROR_INVALID_PARAMS, + message: "Must provide at least one call.", + }; + } + + if (request.chainId && request.chainId !== hexChainId) { + return { + code: RPC_ERROR_UNRECOGNIZED_CHAIN, + message: `Unmatched chain ID: ${request.chainId}`, + }; + } + + if (request.version && !SUPPORTED_VERSIONS.includes(request.version)) { + return { + code: RPC_ERROR_INVALID_PARAMS, + message: `Unsupported version: ${ + request.version + }. Supported: ${SUPPORTED_VERSIONS.join(", ")}`, + }; + } + + if (request.capabilities) { + for (const [key, cap] of Object.entries(request.capabilities)) { + if (key === "atomic") continue; + const isOptional = + cap != null && + typeof cap === "object" && + (cap as Record)["optional"] === true; + if (!isOptional) { + return { + code: EIP5792_ERROR_UNSUPPORTED_CAPABILITY, + message: `Unsupported non-optional capability: ${key}`, + }; + } + } + } + + for (let i = 0; i < request.calls.length; i++) { + const err = validateEIP5792Call(request.calls[i], i); + if (err) return err; + } + + return null; +} + +/** + * Validate a single EIP-5792 call object. + */ +export function validateEIP5792Call( + call: EIP5792Call, + index: number +): SendCallsValidationError | null { + if (call.to && !Address.validate(call.to, { strict: false })) { + return { + code: RPC_ERROR_INVALID_PARAMS, + message: `calls[${index}]: invalid 'to' address: ${call.to}`, + }; + } + if (call.value != null && !Hex.validate(call.value)) { + return { + code: RPC_ERROR_INVALID_PARAMS, + message: `calls[${index}]: invalid 'value', must be hex string`, + }; + } + if (call.data != null && !Hex.validate(call.data)) { + return { + code: RPC_ERROR_INVALID_PARAMS, + message: `calls[${index}]: invalid 'data', must be hex string`, + }; + } + return null; +} From 5b0533b2772a5cfec632162d4e5f46d770f79ad5 Mon Sep 17 00:00:00 2001 From: rowan Date: Wed, 1 Apr 2026 11:05:26 +0900 Subject: [PATCH 40/64] feat: implement wallet_sendCalls and wallet_getCallsStatus RPC handlers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit handleSendCalls: validate via validateSendCallsRequest, query atomic capability, open signEthereumBatch interaction, broadcast signed tx, store batchId→txHash mapping in-memory, return { id } immediately. signEthereumBatch: reuse waitApproveV2 interaction pattern with EthSignType.EIP5792, cross-verify batchId, assert single unsigned tx. handleGetCallsStatus: lookup batchId, fetch eth_getTransactionReceipt, return EIP-5792 status code (100=pending, 200=confirmed, 500=reverted). Register wallet_sendCalls in provider side panel methods. --- .../src/keyring-ethereum/service.ts | 330 +++++++++++++++++- packages/provider/src/core.ts | 1 + 2 files changed, 327 insertions(+), 4 deletions(-) diff --git a/packages/background/src/keyring-ethereum/service.ts b/packages/background/src/keyring-ethereum/service.ts index 6a335baae8..d52ba2904e 100644 --- a/packages/background/src/keyring-ethereum/service.ts +++ b/packages/background/src/keyring-ethereum/service.ts @@ -4,13 +4,15 @@ import { InteractionService } from "../interaction"; import { AnalyticsService } from "../analytics"; import { Env, EthereumProviderRpcError } from "@keplr-wallet/router"; import { + BatchSigningData, + DelegationStatus, + EthereumBatchSignResponse, EthereumSignResponse, EthSignType, - isEthSignChain, -} from "@keplr-wallet/types"; -import type { - DelegationStatus, GetCapabilitiesResult, + InternalSendCallsRequest, + isEthSignChain, + WalletSendCallsRequest, } from "@keplr-wallet/types"; import { Buffer } from "buffer/"; import { @@ -38,11 +40,14 @@ import { parseTxParam, rethrowAsProviderError, validateEVMChainId, + validateSendCallsRequest, } from "./helper"; import { PermissionInteractiveService } from "../permission-interactive"; import { DEFAULT_EVM_CHAIN_ID, ALLOWED_DELEGATORS, + EIP5792_ERROR_ATOMICITY_NOT_SUPPORTED, + EIP5792_ERROR_UNKNOWN_BUNDLE, ERC20_TRANSFER_SELECTOR, RPC_ERROR_INVALID_PARAMS, RPC_ERROR_UNAUTHORIZED, @@ -55,6 +60,12 @@ export class KeyRingEthereumService { private readonly wsManager: EvmWebSocketManager; private readonly chainHandler: EvmChainHandler; + // EIP-5792: batchId → txHash mapping (in-memory, no persistence needed) + private readonly batchCallsMap = new Map< + string, + { txHash: string; chainId: string } + >(); + constructor( protected readonly chainsService: ChainsService, protected readonly keyRingService: KeyRingService, @@ -514,6 +525,117 @@ export class KeyRingEthereumService { }; } + private async signEthereumBatch( + env: Env, + origin: string, + vaultId: string, + chainId: string, + signer: string, + message: Uint8Array + ): Promise { + const modularChainInfo = + this.chainsService.getModularChainInfoOrThrow(chainId); + if (modularChainInfo.hideInUI) { + throw new Error("Can't sign for hidden chain"); + } + if (!isEthSignChain(modularChainInfo)) { + throw new Error("Not ethermint like and EVM chain"); + } + + const keyInfo = this.keyRingService.getKeyInfo(vaultId); + if (!keyInfo) { + throw new Error("Null key info"); + } + + if (keyInfo.type === "ledger" || keyInfo.type === "keystone") { + throw new Error( + "Ledger and Keystone are not supported for batch transactions" + ); + } + + const normalizedSigner = normalizeSigner(signer); + + const key = await this.keyRingCosmosService.getKey(vaultId, chainId); + if ( + normalizedSigner !== key.bech32Address && + normalizedSigner !== key.ethereumHexAddress.toLowerCase() + ) { + throw new Error("Signer mismatched"); + } + + return await this.interactionService.waitApproveV2( + env, + "/sign-ethereum", + "request-sign-ethereum", + { + origin, + chainId, + signer: normalizedSigner, + pubKey: key.pubKey, + message, + signType: EthSignType.EIP5792, + keyType: keyInfo.type, + keyInsensitive: keyInfo.insensitive, + }, + async (res: { signingData: Uint8Array }) => { + const batchData: BatchSigningData = JSON.parse( + Buffer.from(res.signingData).toString() + ); + + // Cross-verify batchId with original request + const originalRequest: InternalSendCallsRequest = JSON.parse( + Buffer.from(message).toString() + ); + if (batchData.batchId !== originalRequest.batchId) { + throw new Error("Batch ID mismatch between request and signing data"); + } + + // Without sequential strategy, atomic and single both produce exactly one tx. + if (batchData.unsignedTxs.length !== 1) { + throw new Error( + `Expected exactly 1 unsigned tx, got ${batchData.unsignedTxs.length}` + ); + } + + const tx = batchData.unsignedTxs[0] as Record; + + // TODO: Use signAuthorizationList + applyTxType after PR #1905 merge + applyEIP1559Type(tx as Parameters[0]); + delete tx["from"]; + + const serializedUnsigned = serializeEVMTransaction( + tx as Parameters[0] + ); + + const sig = await this.keyRingService.sign( + chainId, + vaultId, + Buffer.from(serializedUnsigned.replace("0x", ""), "hex"), + "keccak256" + ); + + const signedTxHex = serializeEVMTransaction( + tx as Parameters[0], + encodeSignature(sig) + ); + + this.analyticsService.logEventIgnoreError("evm_batch_tx_signed", { + chainId, + isInternal: env.isInternalMsg, + origin, + keyType: keyInfo.type, + strategy: batchData.strategy, + }); + + return { + strategy: batchData.strategy, + batchId: batchData.batchId, + signedTxs: [signedTxHex], + }; + } + ); + } + // ── RPC Dispatcher ─────────────────────────────────────── async request( @@ -611,6 +733,15 @@ export class KeyRingEthereumService { // TODO: ERC-5792 스펙은 모든 지원 체인의 capabilities를 한 번에 반환해야 하지만, 현재는 활성 체인만 반환. case "wallet_getCapabilities": return this.handleGetCapabilities(env, origin, params, chainId); + case "wallet_sendCalls": + return this.handleSendCalls(env, origin, params, providerId, chainId); + case "wallet_getCallsStatus": + return this.handleGetCallsStatus(env, origin, params, chainId); + case "wallet_showCallsStatus": + throw new EthereumProviderRpcError( + RPC_ERROR_UNSUPPORTED_METHOD, + "Not implemented" + ); case "eth_call": case "eth_estimateGas": case "eth_getTransactionCount": @@ -968,4 +1099,195 @@ export class KeyRingEthereumService { return { signedTxHex }; } + + // ── EIP-5792: wallet_sendCalls ────────────────────────────── + + private async handleSendCalls( + env: Env, + origin: string, + params: unknown[] | Record | undefined, + providerId: string | undefined, + chainId: string | undefined + ) { + const currentChainId = this.forceGetCurrentChainId(origin, chainId); + const evmInfo = this.chainsService.getEVMInfoOrThrow(currentChainId); + const hexChainId = `0x${evmInfo.chainId.toString(16)}`; + + const request = (Array.isArray(params) ? params[0] : undefined) as + | WalletSendCallsRequest + | undefined; + + if (!request) { + throw new EthereumProviderRpcError( + RPC_ERROR_INVALID_PARAMS, + "Must provide request params." + ); + } + + const validationError = validateSendCallsRequest(request, hexChainId); + if (validationError) { + throw new EthereumProviderRpcError( + validationError.code, + validationError.message + ); + } + + // Validate sender address + const selectedAddress = await this.getSelectedEthAddress(currentChainId); + if ( + request.from && + request.from.toLowerCase() !== selectedAddress.toLowerCase() + ) { + throw new EthereumProviderRpcError( + RPC_ERROR_INVALID_PARAMS, + `Unmatched from address: ${request.from}` + ); + } + + // Query capabilities and validate atomicRequired + const capabilities = await this.getAtomicCapability( + env, + origin, + currentChainId, + selectedAddress + ); + const chainCapabilities = capabilities[hexChainId] as + | { atomic: { status: DelegationStatus } } + | undefined; + + // Absent capability = chain does not support batching (EIP-5792 spec) + if (!chainCapabilities) { + throw new EthereumProviderRpcError( + EIP5792_ERROR_ATOMICITY_NOT_SUPPORTED, + "Batch calls are not supported on this chain." + ); + } + + if ( + request.atomicRequired && + chainCapabilities.atomic.status === "unsupported" + ) { + throw new EthereumProviderRpcError( + EIP5792_ERROR_ATOMICITY_NOT_SUPPORTED, + "Atomic batch is not supported on this chain." + ); + } + + // Fetch nonce + const transactionCount = await this.request( + env, + origin, + "eth_getTransactionCount", + [selectedAddress, "pending"], + providerId, + currentChainId + ); + + const batchId = crypto.randomUUID(); + + const internalRequest: InternalSendCallsRequest = { + batchId, + calls: request.calls.map((call) => ({ + ...call, + data: call.data ?? "0x", + })), + apiVersion: request.version || "1.0", + nonce: parseInt(transactionCount, 16), + chainCapabilities, + }; + + // Sign batch via interaction + const batchResponse = await this.signEthereumBatch( + env, + origin, + this.keyRingService.selectedVaultId, + currentChainId, + selectedAddress, + Buffer.from(JSON.stringify(internalRequest)) + ); + + // Post-signing atomicRequired enforcement + if (request.atomicRequired && batchResponse.strategy !== "atomic") { + throw new EthereumProviderRpcError( + EIP5792_ERROR_ATOMICITY_NOT_SUPPORTED, + "Atomic execution was required but could not be fulfilled." + ); + } + + // Broadcast signed tx and store mapping + try { + const txHash = await this.backgroundTxEthereumService.sendEthereumTx( + origin, + currentChainId, + Buffer.from(batchResponse.signedTxs[0].replace("0x", ""), "hex"), + {} + ); + + this.batchCallsMap.set(batchId, { txHash, chainId: currentChainId }); + } catch (error) { + console.error(`Batch broadcast failed for ${batchId}:`, error); + rethrowAsProviderError(error); + } + + return { id: batchId }; + } + + // ── EIP-5792: wallet_getCallsStatus ──────────────────────── + + private async handleGetCallsStatus( + env: Env, + origin: string, + params: unknown[] | Record | undefined, + _chainId: string | undefined + ) { + const batchId = + (Array.isArray(params) && (params[0] as string)) || undefined; + if (!batchId) { + throw new EthereumProviderRpcError( + RPC_ERROR_INVALID_PARAMS, + "Must provide a batch ID." + ); + } + + const entry = this.batchCallsMap.get(batchId); + if (!entry) { + throw new EthereumProviderRpcError( + EIP5792_ERROR_UNKNOWN_BUNDLE, + "Unknown bundle ID" + ); + } + + const evmInfo = this.chainsService.getEVMInfoOrThrow(entry.chainId); + + // Fetch receipt to determine status + let status = 100; // pending + const receipts: unknown[] = []; + + try { + const receipt = await this.request( + env, + origin, + "eth_getTransactionReceipt", + [entry.txHash], + undefined, + entry.chainId + ); + if (receipt) { + const txStatus = (receipt as Record)["status"]; + status = txStatus === "0x1" ? 200 : 500; + receipts.push(receipt); + } + } catch { + // Receipt not yet available — stays pending + } + + return { + version: "1.0", + chainId: `0x${evmInfo.chainId.toString(16)}`, + id: batchId, + status, + atomic: true, + receipts, + }; + } } diff --git a/packages/provider/src/core.ts b/packages/provider/src/core.ts index 26b43ff150..2eb4a1079b 100644 --- a/packages/provider/src/core.ts +++ b/packages/provider/src/core.ts @@ -1712,6 +1712,7 @@ const sidePanelOpenNeededJSONRPCMethods = [ "wallet_addEthereumChain", "wallet_switchEthereumChain", "wallet_watchAsset", + "wallet_sendCalls", ]; class EthereumProvider extends EventEmitter implements IEthereumProvider { From 6c3de00cfb9491b5dfe723964d021bf42a9b1db6 Mon Sep 17 00:00:00 2001 From: rowan Date: Wed, 1 Apr 2026 11:05:39 +0900 Subject: [PATCH 41/64] feat: handle EthSignType.EIP5792 in sign page routing Add EIP5792 case to sign page wrapper (placeholder, throws until batch sign view is implemented), Ledger sign handler, and Keystone encoder. All three throw explicit unsupported errors for hardware wallet batch signing. --- apps/extension/src/pages/sign/ethereum/wrapper.tsx | 3 +++ apps/extension/src/pages/sign/utils/handle-eth-sign.ts | 2 ++ apps/extension/src/pages/sign/utils/keystone.ts | 4 ++++ 3 files changed, 9 insertions(+) diff --git a/apps/extension/src/pages/sign/ethereum/wrapper.tsx b/apps/extension/src/pages/sign/ethereum/wrapper.tsx index a58b560337..cd78c13727 100644 --- a/apps/extension/src/pages/sign/ethereum/wrapper.tsx +++ b/apps/extension/src/pages/sign/ethereum/wrapper.tsx @@ -43,6 +43,9 @@ export const SignEthereumTxPage: FunctionComponent = observer(() => { interactionData={signEthereumInteractionStore.waitingData} /> ); + case EthSignType.EIP5792: + // TODO: Implement EIP-5792 batch sign view + throw new Error("EIP-5792 batch sign view is not yet implemented"); default: throw new Error( `Unknown sign type: ${signEthereumInteractionStore.waitingData.data.signType}` diff --git a/apps/extension/src/pages/sign/utils/handle-eth-sign.ts b/apps/extension/src/pages/sign/utils/handle-eth-sign.ts index 236116ac67..8b1e4fa1df 100644 --- a/apps/extension/src/pages/sign/utils/handle-eth-sign.ts +++ b/apps/extension/src/pages/sign/utils/handle-eth-sign.ts @@ -303,6 +303,8 @@ export const connectAndSignEthWithLedger = async ( ) ); } + case EthSignType.EIP5792: + throw new Error("EIP-5792 batch signing is not supported on Ledger"); default: throw new Error("Invalid sign type"); } diff --git a/apps/extension/src/pages/sign/utils/keystone.ts b/apps/extension/src/pages/sign/utils/keystone.ts index ffcf5b64f3..246347cc0a 100644 --- a/apps/extension/src/pages/sign/utils/keystone.ts +++ b/apps/extension/src/pages/sign/utils/keystone.ts @@ -52,6 +52,8 @@ export function getEthDataTypeFromSignType( return KeystoneEthereumSDK.DataType.personalMessage; case EthSignType.EIP712: return KeystoneEthereumSDK.DataType.typedData; + case EthSignType.EIP5792: + throw new Error("EIP-5792 batch signing is not supported on Keystone"); } } @@ -73,6 +75,8 @@ export function encodeEthMessage( case EthSignType.MESSAGE: case EthSignType.EIP712: return Buffer.from(message); + case EthSignType.EIP5792: + throw new Error("EIP-5792 batch signing is not supported on Keystone"); } } From 28a899e5cea0b5c532a7b4a3fb8de498c9e7af61 Mon Sep 17 00:00:00 2001 From: rowan Date: Wed, 1 Apr 2026 11:26:35 +0900 Subject: [PATCH 42/64] feat: integrate EIP-7702 authorization signing in batch flow Replace TODO stub with signAuthorizationList + applyTxType. Assert exactly one authorization per tx. Remove unnecessary type casts by using UnsignedEVMTransaction directly. --- .../src/keyring-ethereum/service.ts | 39 +++++++++++++------ 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/packages/background/src/keyring-ethereum/service.ts b/packages/background/src/keyring-ethereum/service.ts index d52ba2904e..3b0c7aece9 100644 --- a/packages/background/src/keyring-ethereum/service.ts +++ b/packages/background/src/keyring-ethereum/service.ts @@ -31,6 +31,7 @@ import { BackgroundTxEthereumService } from "../tx-ethereum"; import { TokenERC20Service } from "../token-erc20"; import { applyEIP1559Type, + applyTxType, encodeSignature, getEthAddressFromPubkey, normalizeUnsignedTx, @@ -39,6 +40,7 @@ import { parseDelegation, parseTxParam, rethrowAsProviderError, + signAuthorizationList, validateEVMChainId, validateSendCallsRequest, } from "./helper"; @@ -597,15 +599,33 @@ export class KeyRingEthereumService { ); } - const tx = batchData.unsignedTxs[0] as Record; + const tx = batchData.unsignedTxs[0] as UnsignedEVMTransaction; - // TODO: Use signAuthorizationList + applyTxType after PR #1905 merge - applyEIP1559Type(tx as Parameters[0]); - delete tx["from"]; + // Sign EIP-7702 authorization if present (exactly one delegation per tx) + if (tx.authorizationList && tx.authorizationList.length > 0) { + if (tx.authorizationList.length !== 1) { + throw new Error( + `Expected exactly 1 authorization, got ${tx.authorizationList.length}` + ); + } + const auth = tx.authorizationList[0]; + tx.authorizationList = await signAuthorizationList( + this.keyRingService, + chainId, + vaultId, + [ + { + address: auth.address, + chainId: auth.chainId, + nonce: BigInt(auth.nonce), + }, + ] + ); + } + applyTxType(tx); + delete tx.from; - const serializedUnsigned = serializeEVMTransaction( - tx as Parameters[0] - ); + const serializedUnsigned = serializeEVMTransaction(tx); const sig = await this.keyRingService.sign( chainId, @@ -614,10 +634,7 @@ export class KeyRingEthereumService { "keccak256" ); - const signedTxHex = serializeEVMTransaction( - tx as Parameters[0], - encodeSignature(sig) - ); + const signedTxHex = serializeEVMTransaction(tx, encodeSignature(sig)); this.analyticsService.logEventIgnoreError("evm_batch_tx_signed", { chainId, From 5340ab9c879d8cc1f21ef7d1d3bc21449c27d562 Mon Sep 17 00:00:00 2001 From: jungcome7 Date: Wed, 1 Apr 2026 22:16:43 +0900 Subject: [PATCH 43/64] feat(stores-eth): add ObservableQuery for smart account delegation status MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MobX ObservableQuery 기반으로 delegation 상태를 조회합니다. 기존 메시지 기반 조회 대비: 자동 캐시, MobX 관찰 시 자동 fetch, 라이프사이클 관리를 제공합니다. - ObservableQuerySmartAccountDelegationStatusInner: eth_getCode → @computed parseDelegation - HasMapStore 래퍼로 주소별 쿼리 인스턴스 자동 관리 - allowedDelegators 옵션을 EthereumQueries에 추가하고 root.tsx에서 ALLOWED_DELEGATORS 전달 - ALLOWED_DELEGATORS를 background/index.ts에서 export Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/extension/src/stores/root.tsx | 2 + .../background/src/keyring-ethereum/index.ts | 1 + .../src/queries/delegation-status.ts | 76 +++++++++++++++++++ packages/stores-eth/src/queries/index.ts | 17 ++++- packages/stores-eth/src/serialize.ts | 5 +- packages/stores-eth/src/types.ts | 4 +- 6 files changed, 99 insertions(+), 6 deletions(-) create mode 100644 packages/stores-eth/src/queries/delegation-status.ts diff --git a/apps/extension/src/stores/root.tsx b/apps/extension/src/stores/root.tsx index 020a5b4264..14a39c8e73 100644 --- a/apps/extension/src/stores/root.tsx +++ b/apps/extension/src/stores/root.tsx @@ -101,6 +101,7 @@ import { setInteractionDataHref } from "../utils"; import { InteractionIdPingMsg, InteractionPingMsg, + ALLOWED_DELEGATORS, } from "@keplr-wallet/background"; import { StarknetAccountStore, @@ -396,6 +397,7 @@ export class RootStore { EthereumQueries.use({ coingeckoAPIBaseURL: CoinGeckoAPIEndPoint, coingeckoAPIURI: CoinGeckoCoinDataByTokenAddress, + allowedDelegators: ALLOWED_DELEGATORS, forceNativeERC20Query: ( chainId, _chainGetter, diff --git a/packages/background/src/keyring-ethereum/index.ts b/packages/background/src/keyring-ethereum/index.ts index 31b4cbd481..c0e3380a87 100644 --- a/packages/background/src/keyring-ethereum/index.ts +++ b/packages/background/src/keyring-ethereum/index.ts @@ -1,2 +1,3 @@ export * from "./messages"; export * from "./service"; +export { ALLOWED_DELEGATORS } from "./constants"; diff --git a/packages/stores-eth/src/queries/delegation-status.ts b/packages/stores-eth/src/queries/delegation-status.ts new file mode 100644 index 0000000000..699f8691c8 --- /dev/null +++ b/packages/stores-eth/src/queries/delegation-status.ts @@ -0,0 +1,76 @@ +import { + ChainGetter, + HasMapStore, + QuerySharedContext, +} from "@keplr-wallet/stores"; +import type { DelegationStatus } from "@keplr-wallet/types"; +import { computed, makeObservable } from "mobx"; +import { ObservableEvmChainJsonRpcQuery } from "./evm-chain-json-rpc"; + +const EIP7702_DELEGATION_PREFIX = "0xef0100"; + +function parseDelegation( + code: string, + allowedDelegators: readonly string[] +): DelegationStatus { + if (code === "0x" || code === "0x0") return "ready"; + const lower = code.toLowerCase(); + if (lower.startsWith(EIP7702_DELEGATION_PREFIX) && lower.length === 48) { + const delegator = "0x" + lower.slice(8); + return allowedDelegators.some((d) => d.toLowerCase() === delegator) + ? "supported" + : "unsupported"; + } + return "unsupported"; +} + +export class ObservableQuerySmartAccountDelegationStatusInner extends ObservableEvmChainJsonRpcQuery { + constructor( + sharedContext: QuerySharedContext, + chainId: string, + chainGetter: ChainGetter, + protected readonly ethereumHexAddress: string, + protected readonly allowedDelegators: readonly string[] + ) { + super(sharedContext, chainId, chainGetter, "eth_getCode", [ + ethereumHexAddress, + "latest", + ]); + makeObservable(this); + } + + protected override canFetch(): boolean { + return this.ethereumHexAddress.length > 0; + } + + @computed + get delegationStatus(): DelegationStatus { + if (!this.response || !this.response.data) return "ready"; + return parseDelegation(this.response.data, this.allowedDelegators); + } +} + +export class ObservableQuerySmartAccountDelegationStatus extends HasMapStore { + constructor( + protected readonly sharedContext: QuerySharedContext, + protected readonly chainId: string, + protected readonly chainGetter: ChainGetter, + protected readonly allowedDelegators: readonly string[] + ) { + super((ethereumHexAddress: string) => { + return new ObservableQuerySmartAccountDelegationStatusInner( + sharedContext, + chainId, + chainGetter, + ethereumHexAddress, + allowedDelegators + ); + }); + } + + getQueryByAddress( + ethereumHexAddress: string + ): ObservableQuerySmartAccountDelegationStatusInner { + return this.get(ethereumHexAddress); + } +} diff --git a/packages/stores-eth/src/queries/index.ts b/packages/stores-eth/src/queries/index.ts index 7427220f5f..f52936b0c7 100644 --- a/packages/stores-eth/src/queries/index.ts +++ b/packages/stores-eth/src/queries/index.ts @@ -15,6 +15,7 @@ import { ObservableQueryCoingeckoTokenInfo } from "./coingecko-token-info"; import { ObservableQueryEthereumERC20BalanceRegistry } from "./erc20-balance"; import { ObservableQueryEthereumGasPrice } from "./gas-price"; import { ObservableQueryEthereumTxReceipt } from "./tx-receipt"; +import { ObservableQuerySmartAccountDelegationStatus } from "./delegation-status"; export interface EthereumQueries { ethereum: EthereumQueriesImpl; @@ -24,6 +25,7 @@ export const EthereumQueries = { use(options: { coingeckoAPIBaseURL: string; coingeckoAPIURI: string; + allowedDelegators?: readonly string[]; forceNativeERC20Query: ( chainId: string, chainGetter: ChainGetter, @@ -50,7 +52,8 @@ export const EthereumQueries = { chainId, chainGetter, options.coingeckoAPIBaseURL, - options.coingeckoAPIURI + options.coingeckoAPIURI, + options.allowedDelegators ?? [] ), }; }; @@ -66,6 +69,7 @@ export class EthereumQueriesImpl { public readonly queryEthereumCoingeckoTokenInfo: DeepReadonly; public readonly queryEthereumGasPrice: DeepReadonly; public readonly queryEthereumTxReceipt: DeepReadonly; + public readonly querySmartAccountDelegationStatus: DeepReadonly; constructor( base: QueriesSetBase, @@ -79,7 +83,8 @@ export class EthereumQueriesImpl { protected chainId: string, protected chainGetter: ChainGetter, protected coingeckoAPIBaseURL: string, - protected coingeckoAPIURI: string + protected coingeckoAPIURI: string, + protected allowedDelegators: readonly string[] ) { base.queryBalances.addBalanceRegistry( new ObservableQueryEthereumERC20BalanceRegistry(sharedContext) @@ -145,5 +150,13 @@ export class EthereumQueriesImpl { chainId, chainGetter ); + + this.querySmartAccountDelegationStatus = + new ObservableQuerySmartAccountDelegationStatus( + sharedContext, + chainId, + chainGetter, + allowedDelegators + ); } } diff --git a/packages/stores-eth/src/serialize.ts b/packages/stores-eth/src/serialize.ts index d8d1e3c97f..62e42cc222 100644 --- a/packages/stores-eth/src/serialize.ts +++ b/packages/stores-eth/src/serialize.ts @@ -102,11 +102,12 @@ export function serializeEVMTransaction( | undefined, authorizationList: (tx.authorizationList ?? []).map((a) => ({ address: a.address as `0x${string}`, - chainId: a.chainId, + chainId: typeof a.chainId === "string" ? Number(a.chainId) : a.chainId, nonce: toBigInt(a.nonce) ?? BigInt(0), r: toBigInt(a.r) ?? BigInt(0), s: toBigInt(a.s) ?? BigInt(0), - yParity: a.yParity ?? 0, + yParity: + typeof a.yParity === "string" ? Number(a.yParity) : a.yParity ?? 0, })), }; diff --git a/packages/stores-eth/src/types.ts b/packages/stores-eth/src/types.ts index 5ef0a30537..240f5c5848 100644 --- a/packages/stores-eth/src/types.ts +++ b/packages/stores-eth/src/types.ts @@ -15,11 +15,11 @@ export type UnsignedEVMTransaction = { maxFeePerGas?: string | number; authorizationList?: Array<{ address: string; - chainId: number; + chainId: number | string; nonce: string | number; r?: string; s?: string; - yParity?: number; + yParity?: number | string; }>; }; From 7e43d45158c0b0e0e71b140a49bdc9f8d9304aa8 Mon Sep 17 00:00:00 2001 From: jungcome7 Date: Wed, 1 Apr 2026 22:16:55 +0900 Subject: [PATCH 44/64] refactor(background): remove legacy delegation status message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ObservableQuery로 마이그레이션 완료에 따라 메시지 기반 delegation 상태 조회를 제거합니다. - GetSmartAccountDelegationStatusMsg 클래스 삭제 - handler.ts에서 해당 case 제거 - init.ts에서 메시지 등록 제거 - service.ts에서 getDelegationStatus 메서드 삭제 Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/keyring-ethereum/handler.ts | 7 ---- .../background/src/keyring-ethereum/init.ts | 2 -- .../src/keyring-ethereum/messages.ts | 34 ------------------- .../src/keyring-ethereum/service.ts | 24 ------------- 4 files changed, 67 deletions(-) diff --git a/packages/background/src/keyring-ethereum/handler.ts b/packages/background/src/keyring-ethereum/handler.ts index d0dab96c1d..da8da61ce1 100644 --- a/packages/background/src/keyring-ethereum/handler.ts +++ b/packages/background/src/keyring-ethereum/handler.ts @@ -12,7 +12,6 @@ import { CheckNeedEnableAccessForEVMMsg, UpgradeSmartAccountMsg, DowngradeSmartAccountMsg, - GetSmartAccountDelegationStatusMsg, EstimateSmartAccountFeeMsg, } from "./messages"; import { KeyRingEthereumService } from "./service"; @@ -59,12 +58,6 @@ export const getHandler: ( (msg as DowngradeSmartAccountMsg).chainId, (msg as DowngradeSmartAccountMsg).vaultId ); - case GetSmartAccountDelegationStatusMsg: - return service.getDelegationStatus( - env, - (msg as GetSmartAccountDelegationStatusMsg).chainId, - (msg as GetSmartAccountDelegationStatusMsg).vaultId - ); case EstimateSmartAccountFeeMsg: return service.estimateSmartAccountFee( env, diff --git a/packages/background/src/keyring-ethereum/init.ts b/packages/background/src/keyring-ethereum/init.ts index 231e679686..f44c9789ee 100644 --- a/packages/background/src/keyring-ethereum/init.ts +++ b/packages/background/src/keyring-ethereum/init.ts @@ -7,7 +7,6 @@ import { CheckNeedEnableAccessForEVMMsg, UpgradeSmartAccountMsg, DowngradeSmartAccountMsg, - GetSmartAccountDelegationStatusMsg, EstimateSmartAccountFeeMsg, } from "./messages"; import { ROUTE } from "./constants"; @@ -25,7 +24,6 @@ export function init( router.registerMessage(CheckNeedEnableAccessForEVMMsg); router.registerMessage(UpgradeSmartAccountMsg); router.registerMessage(DowngradeSmartAccountMsg); - router.registerMessage(GetSmartAccountDelegationStatusMsg); router.registerMessage(EstimateSmartAccountFeeMsg); router.addHandler(ROUTE, getHandler(service, permissionInteractionService)); diff --git a/packages/background/src/keyring-ethereum/messages.ts b/packages/background/src/keyring-ethereum/messages.ts index 3e1efe4784..b0cd3c5cbe 100644 --- a/packages/background/src/keyring-ethereum/messages.ts +++ b/packages/background/src/keyring-ethereum/messages.ts @@ -179,40 +179,6 @@ export class DowngradeSmartAccountMsg extends Message { } } -export class GetSmartAccountDelegationStatusMsg extends Message { - public static type() { - return "get-smart-account-delegation-status"; - } - - constructor( - public readonly chainId: string, - public readonly vaultId: string - ) { - super(); - } - - validateBasic(): void { - if (!this.chainId) { - throw new Error("chain id not set"); - } - if (!this.vaultId) { - throw new Error("vault id not set"); - } - } - - override approveExternal(): boolean { - return false; - } - - route(): string { - return ROUTE; - } - - type(): string { - return GetSmartAccountDelegationStatusMsg.type(); - } -} - export class EstimateSmartAccountFeeMsg extends Message<{ gasLimit: string; maxFeePerGas: string; diff --git a/packages/background/src/keyring-ethereum/service.ts b/packages/background/src/keyring-ethereum/service.ts index 24e48e000a..54af5c452c 100644 --- a/packages/background/src/keyring-ethereum/service.ts +++ b/packages/background/src/keyring-ethereum/service.ts @@ -169,30 +169,6 @@ export class KeyRingEthereumService { // ── Smart Account Upgrade/Downgrade ───────────────────── - async getDelegationStatus( - env: Env, - chainId: string, - vaultId: string - ): Promise { - if (!this.getChainFeatures(chainId)?.includes("eip-7702")) { - return "unsupported"; - } - const address = await this.getEthAddressForVault(chainId, vaultId); - try { - const code = await this.request( - env, - "", - "eth_getCode", - [address, "latest"], - undefined, - chainId - ); - return parseDelegation(code ?? "0x", ALLOWED_DELEGATORS); - } catch { - return "unsupported"; - } - } - async upgradeToSmartAccount( env: Env, chainId: string, From dd082f0efb643e8f27cf30c3a40c97aa641df0a6 Mon Sep 17 00:00:00 2001 From: jungcome7 Date: Wed, 1 Apr 2026 22:17:08 +0900 Subject: [PATCH 45/64] feat(extension): add smart account shared hooks and utils MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 양 페이지(네트워크 목록, 확인)에서 공유하는 훅과 유틸리티를 추가합니다. - useEip7702Chains: chainStore에서 eip-7702 feature 플래그 체인 필터링 - useRedirectIfInvalid: 유효하지 않은 접근 시 홈으로 리다이렉트 - utils.ts: truncateAddress, formatFee, DELEGATOR_DISPLAY, 딜레이 상수 Co-Authored-By: Claude Opus 4.6 (1M context) --- .../smart-account/hooks/use-eip7702-chains.ts | 28 +++++++++++++++++++ .../hooks/use-redirect-if-invalid.ts | 12 ++++++++ .../src/pages/wallet/smart-account/index.ts | 2 ++ .../src/pages/wallet/smart-account/utils.ts | 27 ++++++++++++++++++ 4 files changed, 69 insertions(+) create mode 100644 apps/extension/src/pages/wallet/smart-account/hooks/use-eip7702-chains.ts create mode 100644 apps/extension/src/pages/wallet/smart-account/hooks/use-redirect-if-invalid.ts create mode 100644 apps/extension/src/pages/wallet/smart-account/index.ts create mode 100644 apps/extension/src/pages/wallet/smart-account/utils.ts diff --git a/apps/extension/src/pages/wallet/smart-account/hooks/use-eip7702-chains.ts b/apps/extension/src/pages/wallet/smart-account/hooks/use-eip7702-chains.ts new file mode 100644 index 0000000000..dceb7db3a3 --- /dev/null +++ b/apps/extension/src/pages/wallet/smart-account/hooks/use-eip7702-chains.ts @@ -0,0 +1,28 @@ +import { useMemo } from "react"; +import { useStore } from "../../../../stores"; + +export function useEip7702Chains() { + const { chainStore } = useStore(); + + return useMemo( + () => + chainStore.modularChainInfos + .map((info) => { + const unwrapped = info.unwrapped; + if (unwrapped.type === "evm") { + if (unwrapped.evm.features?.includes("eip-7702")) { + return { chainId: info.chainId, chainName: info.chainName }; + } + } else if (unwrapped.type === "ethermint") { + if (unwrapped.cosmos.features?.includes("eip-7702")) { + return { chainId: info.chainId, chainName: info.chainName }; + } + } + return null; + }) + .filter( + (item): item is { chainId: string; chainName: string } => item != null + ), + [chainStore.modularChainInfos] + ); +} diff --git a/apps/extension/src/pages/wallet/smart-account/hooks/use-redirect-if-invalid.ts b/apps/extension/src/pages/wallet/smart-account/hooks/use-redirect-if-invalid.ts new file mode 100644 index 0000000000..43dd19f764 --- /dev/null +++ b/apps/extension/src/pages/wallet/smart-account/hooks/use-redirect-if-invalid.ts @@ -0,0 +1,12 @@ +import { useEffect } from "react"; +import { useNavigate } from "react-router"; + +export function useRedirectIfInvalid(isValid: boolean, to = "/") { + const navigate = useNavigate(); + + useEffect(() => { + if (!isValid) { + navigate(to, { replace: true }); + } + }, [isValid, navigate, to]); +} diff --git a/apps/extension/src/pages/wallet/smart-account/index.ts b/apps/extension/src/pages/wallet/smart-account/index.ts new file mode 100644 index 0000000000..ea51c5e54a --- /dev/null +++ b/apps/extension/src/pages/wallet/smart-account/index.ts @@ -0,0 +1,2 @@ +export { SmartAccountPage } from "./network-list"; +export { SmartAccountConfirmPage } from "./confirm"; diff --git a/apps/extension/src/pages/wallet/smart-account/utils.ts b/apps/extension/src/pages/wallet/smart-account/utils.ts new file mode 100644 index 0000000000..573d8ef0ca --- /dev/null +++ b/apps/extension/src/pages/wallet/smart-account/utils.ts @@ -0,0 +1,27 @@ +import { ALLOWED_DELEGATORS } from "@keplr-wallet/background"; + +export const TOGGLE_ANIMATION_DELAY_MS = 200; +export const STATUS_UPDATE_DELAY_MS = 100; + +export const truncateAddress = (addr: string): string => + addr ? `${addr.slice(0, 10)}...${addr.slice(-8)}` : ""; + +export const DELEGATOR_DISPLAY = ALLOWED_DELEGATORS[0] + ? truncateAddress(ALLOWED_DELEGATORS[0]) + : ""; + +export const formatFee = ( + weiStr: string, + symbol: string, + decimals = 18 +): string => { + const wei = BigInt(weiStr); + const divisor = BigInt(10) ** BigInt(decimals); + const whole = wei / divisor; + const frac = wei % divisor; + const fracStr = frac.toString().padStart(decimals, "0").slice(0, 6); + if (whole === BigInt(0) && frac === BigInt(0)) return `0 ${symbol}`; + if (whole === BigInt(0) && BigInt(fracStr) === BigInt(0)) + return `< 0.000001 ${symbol}`; + return `~${whole}.${fracStr} ${symbol}`; +}; From 86557ec24c4b000be94a8badbeb8d91328938a85 Mon Sep 17 00:00:00 2001 From: jungcome7 Date: Wed, 1 Apr 2026 22:17:20 +0900 Subject: [PATCH 46/64] feat(extension): add smart account network list page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 체인별 Smart Account 토글 목록 페이지를 추가합니다. - network-list.tsx: 체인 이미지 + 이름 + 토글 스위치, 200ms 애니메이션 후 confirm 이동 - useChainStatuses: ObservableQuery 기반 delegation 상태 조회 + confirm 복귀 시 선택적 애니메이션 - ChainRowItem: 개별 체인 행 컴포넌트 Co-Authored-By: Claude Opus 4.6 (1M context) --- .../components/chain-row-item.tsx | 81 ++++++++++++ .../network-list/hooks/use-chain-statuses.ts | 54 ++++++++ .../smart-account/network-list/index.ts | 1 + .../network-list/network-list.tsx | 117 ++++++++++++++++++ 4 files changed, 253 insertions(+) create mode 100644 apps/extension/src/pages/wallet/smart-account/network-list/components/chain-row-item.tsx create mode 100644 apps/extension/src/pages/wallet/smart-account/network-list/hooks/use-chain-statuses.ts create mode 100644 apps/extension/src/pages/wallet/smart-account/network-list/index.ts create mode 100644 apps/extension/src/pages/wallet/smart-account/network-list/network-list.tsx diff --git a/apps/extension/src/pages/wallet/smart-account/network-list/components/chain-row-item.tsx b/apps/extension/src/pages/wallet/smart-account/network-list/components/chain-row-item.tsx new file mode 100644 index 0000000000..7e5afd1a72 --- /dev/null +++ b/apps/extension/src/pages/wallet/smart-account/network-list/components/chain-row-item.tsx @@ -0,0 +1,81 @@ +import React, { FunctionComponent } from "react"; +import { useIntl } from "react-intl"; +import styled, { css } from "styled-components"; +import { observer } from "mobx-react-lite"; +import type { DelegationStatus } from "@keplr-wallet/types"; +import { DSColor, DSTypography } from "@keplr-wallet/design-system"; +import { XAxis } from "../../../../../components/axis"; +import { Gutter } from "../../../../../components/gutter"; +import { ChainImageFallback } from "../../../../../components/image"; +import { Toggle } from "../../../../../components/toggle"; +import { useStore } from "../../../../../stores"; + +export const ChainRowItem: FunctionComponent<{ + chainId: string; + chainName: string; + status: DelegationStatus; + isAnimating: boolean; + onToggle: () => void; +}> = observer(({ chainId, chainName, status, isAnimating, onToggle }) => { + const intl = useIntl(); + const { chainStore } = useStore(); + + let chainInfo; + try { + chainInfo = chainStore.getModularChain(chainId); + } catch { + // noop + } + + return ( + + + {chainInfo && } + + + {chainName} + + {status === "unsupported" && ( + + + + {intl.formatMessage({ + id: "page.wallet.smart-account.status.unsupported", + })} + + + )} + + + + + + ); +}); + +const ChainRow = styled(XAxis)` + padding: 0.75rem 0; + align-items: center; + justify-content: space-between; +`; + +const ToggleTransitionWrapper = styled.div<{ + $enableTransition: boolean; +}>` + ${(props) => + !props.$enableTransition && + css` + * { + transition: none !important; + } + `} +`; diff --git a/apps/extension/src/pages/wallet/smart-account/network-list/hooks/use-chain-statuses.ts b/apps/extension/src/pages/wallet/smart-account/network-list/hooks/use-chain-statuses.ts new file mode 100644 index 0000000000..fd468773b1 --- /dev/null +++ b/apps/extension/src/pages/wallet/smart-account/network-list/hooks/use-chain-statuses.ts @@ -0,0 +1,54 @@ +import { useEffect, useRef, useState } from "react"; +import { useSearchParams } from "react-router-dom"; +import type { DelegationStatus } from "@keplr-wallet/types"; +import { useStore } from "../../../../../stores"; +import { useEip7702Chains } from "../../hooks/use-eip7702-chains"; +import { STATUS_UPDATE_DELAY_MS } from "../../utils"; + +export interface ChainStatus { + chainId: string; + chainName: string; + status: DelegationStatus; +} + +export function useChainStatuses(hexAddress: string) { + const [searchParams] = useSearchParams(); + const { queriesStore } = useStore(); + const eip7702Chains = useEip7702Chains(); + + const updatedChainId = searchParams.get("updatedChainId"); + const updatedDirection = searchParams.get("updatedDirection"); + + const chainStatuses: ChainStatus[] = eip7702Chains.map((chain) => { + const query = queriesStore + .get(chain.chainId) + .ethereum?.querySmartAccountDelegationStatus?.getQueryByAddress( + hexAddress + ); + return { + chainId: chain.chainId, + chainName: chain.chainName, + status: query?.delegationStatus ?? "ready", + }; + }); + + const [animatingChainId, setAnimatingChainId] = useState(null); + const appliedRef = useRef(null); + + useEffect(() => { + if ( + !updatedChainId || + !updatedDirection || + appliedRef.current === updatedChainId + updatedDirection + ) + return; + appliedRef.current = updatedChainId + updatedDirection; + const timer = setTimeout( + () => setAnimatingChainId(updatedChainId), + STATUS_UPDATE_DELAY_MS + ); + return () => clearTimeout(timer); + }, [updatedChainId, updatedDirection]); + + return { chainStatuses, animatingChainId }; +} diff --git a/apps/extension/src/pages/wallet/smart-account/network-list/index.ts b/apps/extension/src/pages/wallet/smart-account/network-list/index.ts new file mode 100644 index 0000000000..eca81b93e1 --- /dev/null +++ b/apps/extension/src/pages/wallet/smart-account/network-list/index.ts @@ -0,0 +1 @@ +export { SmartAccountPage } from "./network-list"; diff --git a/apps/extension/src/pages/wallet/smart-account/network-list/network-list.tsx b/apps/extension/src/pages/wallet/smart-account/network-list/network-list.tsx new file mode 100644 index 0000000000..fe89af719e --- /dev/null +++ b/apps/extension/src/pages/wallet/smart-account/network-list/network-list.tsx @@ -0,0 +1,117 @@ +import React, { + FunctionComponent, + useCallback, + useMemo, + useRef, + useState, +} from "react"; +import { observer } from "mobx-react-lite"; +import { useSearchParams } from "react-router-dom"; +import { useNavigate } from "react-router"; +import { useIntl } from "react-intl"; +import type { DelegationStatus } from "@keplr-wallet/types"; +import { DSColor, DSTypography } from "@keplr-wallet/design-system"; +import { BackButton } from "../../../../layouts/header/components"; +import { HeaderLayout } from "../../../../layouts/header"; +import { Box } from "../../../../components/box"; +import { useStore } from "../../../../stores"; +import { useRedirectIfInvalid } from "../hooks/use-redirect-if-invalid"; +import { useEip7702Chains } from "../hooks/use-eip7702-chains"; +import { TOGGLE_ANIMATION_DELAY_MS } from "../utils"; +import { useChainStatuses } from "./hooks/use-chain-statuses"; +import { ChainRowItem } from "./components/chain-row-item"; + +export const SmartAccountPage: FunctionComponent = observer(() => { + const [searchParams] = useSearchParams(); + const navigate = useNavigate(); + const intl = useIntl(); + const { accountStore } = useStore(); + + const vaultId = searchParams.get("id"); + + useRedirectIfInvalid(!!vaultId); + + const eip7702Chains = useEip7702Chains(); + const hexAddress = useMemo(() => { + const firstChain = eip7702Chains[0]; + if (!firstChain) return ""; + try { + return accountStore.getAccount(firstChain.chainId).ethereumHexAddress; + } catch { + return ""; + } + }, [accountStore, eip7702Chains]); + + const { chainStatuses, animatingChainId } = useChainStatuses(hexAddress); + + const navigatingRef = useRef(false); + const [pendingToggleChainId, setPendingToggleChainId] = useState< + string | null + >(null); + + const handleToggle = useCallback( + (chainId: string, chainName: string, currentStatus: DelegationStatus) => { + if (currentStatus === "unsupported" || navigatingRef.current) return; + navigatingRef.current = true; + setPendingToggleChainId(chainId); + const direction = currentStatus === "ready" ? "upgrade" : "downgrade"; + setTimeout(() => { + navigatingRef.current = false; + setPendingToggleChainId(null); + navigate( + `/wallet/smart-account/confirm?id=${vaultId}&chainId=${encodeURIComponent( + chainId + )}&chainName=${encodeURIComponent(chainName)}&direction=${direction}`, + { replace: true } + ); + }, TOGGLE_ANIMATION_DELAY_MS); + }, + [navigate, vaultId] + ); + + if (!vaultId) return null; + + return ( + } + > + + + {intl.formatMessage({ + id: "page.wallet.smart-account.description", + })} + + + + {chainStatuses.map((chain) => { + const isPending = pendingToggleChainId === chain.chainId; + const displayStatus = isPending + ? chain.status === "ready" + ? "supported" + : "ready" + : chain.status; + + return ( + + handleToggle(chain.chainId, chain.chainName, chain.status) + } + /> + ); + })} + + + + ); +}); From cee3d5df6a92a04a69c0773d1afa935d6163c1d9 Mon Sep 17 00:00:00 2001 From: jungcome7 Date: Wed, 1 Apr 2026 22:17:34 +0900 Subject: [PATCH 47/64] feat(extension): add smart account confirm page with fee estimation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 트랜잭션 확인 페이지와 수수료 추정 훅을 추가합니다. - confirm.tsx: Summary + Detail + Fee 카드 구성, 더블클릭 방지, 성공/실패 토스트 - useConfirmRoute: URL 파라미터 파싱 + 네비게이션 (라우트 전담) - useSmartAccountFee: discriminated union 상태 머신, USD 환산, 잔액 부족 체크 - SummaryCard/DetailCard/FeeCard: 프레젠테이셔널 컴포넌트 Co-Authored-By: Claude Opus 4.6 (1M context) --- .../smart-account/confirm/components/card.tsx | 8 + .../confirm/components/detail-card.tsx | 135 ++++++++++++ .../confirm/components/fee-card.tsx | 111 ++++++++++ .../confirm/components/summary-card.tsx | 66 ++++++ .../wallet/smart-account/confirm/confirm.tsx | 198 ++++++++++++++++++ .../confirm/hooks/use-confirm-route.ts | 51 +++++ .../confirm/hooks/use-smart-account-fee.ts | 119 +++++++++++ .../wallet/smart-account/confirm/index.ts | 1 + 8 files changed, 689 insertions(+) create mode 100644 apps/extension/src/pages/wallet/smart-account/confirm/components/card.tsx create mode 100644 apps/extension/src/pages/wallet/smart-account/confirm/components/detail-card.tsx create mode 100644 apps/extension/src/pages/wallet/smart-account/confirm/components/fee-card.tsx create mode 100644 apps/extension/src/pages/wallet/smart-account/confirm/components/summary-card.tsx create mode 100644 apps/extension/src/pages/wallet/smart-account/confirm/confirm.tsx create mode 100644 apps/extension/src/pages/wallet/smart-account/confirm/hooks/use-confirm-route.ts create mode 100644 apps/extension/src/pages/wallet/smart-account/confirm/hooks/use-smart-account-fee.ts create mode 100644 apps/extension/src/pages/wallet/smart-account/confirm/index.ts diff --git a/apps/extension/src/pages/wallet/smart-account/confirm/components/card.tsx b/apps/extension/src/pages/wallet/smart-account/confirm/components/card.tsx new file mode 100644 index 0000000000..e084df6448 --- /dev/null +++ b/apps/extension/src/pages/wallet/smart-account/confirm/components/card.tsx @@ -0,0 +1,8 @@ +import styled from "styled-components"; +import { DSColor } from "@keplr-wallet/design-system"; + +export const Card = styled.div` + border-radius: 0.375rem; + background-color: ${DSColor.background.surface.elevated}; + padding: 1rem; +`; diff --git a/apps/extension/src/pages/wallet/smart-account/confirm/components/detail-card.tsx b/apps/extension/src/pages/wallet/smart-account/confirm/components/detail-card.tsx new file mode 100644 index 0000000000..95b7be1c8e --- /dev/null +++ b/apps/extension/src/pages/wallet/smart-account/confirm/components/detail-card.tsx @@ -0,0 +1,135 @@ +import React, { FunctionComponent } from "react"; +import { useIntl } from "react-intl"; +import styled from "styled-components"; +import { DSColor, DSTypography } from "@keplr-wallet/design-system"; +import { XAxis, YAxis } from "../../../../../components/axis"; +import { ChainImageFallback } from "../../../../../components/image"; +import { DELEGATOR_DISPLAY, truncateAddress } from "../../utils"; +import type { IModularChainInfoImpl } from "@keplr-wallet/stores"; +import { Card } from "./card"; + +export const DetailCard: FunctionComponent<{ + accountName: string; + hexAddress: string; + isUpgrade: boolean; + chainName: string; + chainInfo?: IModularChainInfoImpl; +}> = ({ accountName, hexAddress, isUpgrade, chainName, chainInfo }) => { + const intl = useIntl(); + + return ( + + + + + + + {accountName} + + {hexAddress && ( + + {truncateAddress(hexAddress)} + + )} + + + + + + + + {intl.formatMessage({ + id: isUpgrade + ? "page.wallet.smart-account.confirm.current.eoa" + : "page.wallet.smart-account.confirm.current.smart", + })} + + + + + + + + {intl.formatMessage({ + id: isUpgrade + ? "page.wallet.smart-account.confirm.current.smart" + : "page.wallet.smart-account.confirm.current.eoa", + })} + + + + + + + + {DELEGATOR_DISPLAY} + + + + + + + + {chainInfo && ( + + )} + + {chainName} + + + + + + ); +}; + +const Label: FunctionComponent<{ children: React.ReactNode }> = ({ + children, +}) => ( + + {children} + +); + +const InfoRow = styled(XAxis)` + justify-content: space-between; + align-items: center; +`; + +const Divider = styled.div` + height: 1px; + background-color: ${DSColor.stroke.separator.primary}; +`; diff --git a/apps/extension/src/pages/wallet/smart-account/confirm/components/fee-card.tsx b/apps/extension/src/pages/wallet/smart-account/confirm/components/fee-card.tsx new file mode 100644 index 0000000000..f4c96046c9 --- /dev/null +++ b/apps/extension/src/pages/wallet/smart-account/confirm/components/fee-card.tsx @@ -0,0 +1,111 @@ +import React, { Fragment, FunctionComponent } from "react"; +import { useIntl } from "react-intl"; +import styled from "styled-components"; +import { DSColor, DSTypography } from "@keplr-wallet/design-system"; +import { Card } from "./card"; +import { XAxis, YAxis } from "../../../../../components/axis"; +import { Gutter } from "../../../../../components/gutter"; +import { Skeleton } from "../../../../../components/skeleton"; + +export const FeeCard: FunctionComponent<{ + feeDisplay: string | null; + feeUsd: string | null; + isEstimating: boolean; + isFailed: boolean; + insufficientBalance: boolean; + onRetry: () => void; +}> = ({ + feeDisplay, + feeUsd, + isEstimating, + isFailed, + insufficientBalance, + onRetry, +}) => { + const intl = useIntl(); + + return ( + + + + {intl.formatMessage({ + id: "page.wallet.smart-account.confirm.fee", + })} + +
+ + + + {feeUsd + ? feeUsd + : feeDisplay === null + ? intl.formatMessage({ + id: "page.wallet.smart-account.confirm.fee.failed", + }) + : feeDisplay} + + {feeUsd && feeDisplay && ( + + {feeDisplay} + + )} + + + + + {insufficientBalance && ( + + + + {intl.formatMessage({ + id: "page.wallet.smart-account.confirm.fee.insufficient", + })} + + + )} + + {isFailed && ( + + + + {intl.formatMessage({ + id: "page.wallet.smart-account.confirm.fee.retry", + })} + + + )} + + ); +}; + +const FeeCardContainer = styled(Card)` + padding: 0.875rem 1rem; + margin-bottom: 0.75rem; +`; diff --git a/apps/extension/src/pages/wallet/smart-account/confirm/components/summary-card.tsx b/apps/extension/src/pages/wallet/smart-account/confirm/components/summary-card.tsx new file mode 100644 index 0000000000..4d07752df6 --- /dev/null +++ b/apps/extension/src/pages/wallet/smart-account/confirm/components/summary-card.tsx @@ -0,0 +1,66 @@ +import React, { FunctionComponent } from "react"; +import { useIntl } from "react-intl"; +import styled from "styled-components"; +import { + DSColor, + DSTypography, + CheckmarkCircleIcon, + XCloseIcon, +} from "@keplr-wallet/design-system"; +import { XAxis, YAxis } from "../../../../../components/axis"; +import { Gutter } from "../../../../../components/gutter"; +import { Card } from "./card"; + +export const SummaryCard: FunctionComponent<{ isUpgrade: boolean }> = ({ + isUpgrade, +}) => { + const intl = useIntl(); + + return ( + + + + {isUpgrade ? ( + + ) : ( + + )} + + + + + {intl.formatMessage({ + id: isUpgrade + ? "page.wallet.smart-account.confirm.summary.upgrade" + : "page.wallet.smart-account.confirm.summary.downgrade", + })} + + + + {intl.formatMessage({ + id: isUpgrade + ? "page.wallet.smart-account.confirm.summary.upgrade.desc" + : "page.wallet.smart-account.confirm.summary.downgrade.desc", + })} + + + + + ); +}; + +const IconCircle = styled.div` + width: 2.75rem; + min-width: 2.75rem; + height: 2.75rem; + border-radius: 50%; + background-color: ${DSColor.button.primaryTransparent}; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +`; diff --git a/apps/extension/src/pages/wallet/smart-account/confirm/confirm.tsx b/apps/extension/src/pages/wallet/smart-account/confirm/confirm.tsx new file mode 100644 index 0000000000..06a26760ca --- /dev/null +++ b/apps/extension/src/pages/wallet/smart-account/confirm/confirm.tsx @@ -0,0 +1,198 @@ +import React, { + FunctionComponent, + useCallback, + useMemo, + useRef, + useState, +} from "react"; +import { observer } from "mobx-react-lite"; +import { useIntl } from "react-intl"; +import { + UpgradeSmartAccountMsg, + DowngradeSmartAccountMsg, +} from "@keplr-wallet/background"; +import { InExtensionMessageRequester } from "@keplr-wallet/router-extension"; +import { BACKGROUND_PORT } from "@keplr-wallet/router"; +import { DSColor, DSTypography } from "@keplr-wallet/design-system"; +import { BackButton } from "../../../../layouts/header/components"; +import { HeaderLayout } from "../../../../layouts/header"; +import { CancelIcon } from "../../../../components/button"; +import { Gutter } from "../../../../components/gutter"; +import { Box } from "../../../../components/box"; +import { useStore } from "../../../../stores"; +import { useNotification } from "../../../../hooks/notification"; +import { useRedirectIfInvalid } from "../hooks/use-redirect-if-invalid"; +import { useConfirmRoute } from "./hooks/use-confirm-route"; +import { useSmartAccountFee } from "./hooks/use-smart-account-fee"; +import { SummaryCard } from "./components/summary-card"; +import { DetailCard } from "./components/detail-card"; +import { FeeCard } from "./components/fee-card"; + +export const SmartAccountConfirmPage: FunctionComponent = observer(() => { + const intl = useIntl(); + const notification = useNotification(); + const { chainStore, accountStore, keyRingStore } = useStore(); + + const { + vaultId, + chainId, + chainName, + isValid, + isUpgrade, + goBack, + goBackAfterApprove, + } = useConfirmRoute(); + + useRedirectIfInvalid(isValid); + + const hexAddress = useMemo(() => { + try { + return accountStore.getAccount(chainId).ethereumHexAddress; + } catch { + return ""; + } + }, [accountStore, chainId]); + + const accountName = useMemo(() => { + const keyInfo = keyRingStore.keyInfos.find((info) => info.id === vaultId); + return keyInfo?.name || "Account"; + }, [keyRingStore.keyInfos, vaultId]); + + const chainInfo = useMemo(() => { + try { + return chainStore.getModularChain(chainId); + } catch { + return undefined; + } + }, [chainStore, chainId]); + + const { + feeDisplay, + feeUsd, + isEstimating, + isFailed, + insufficientBalance, + balanceLoaded, + retry, + } = useSmartAccountFee(chainId, vaultId, hexAddress, isValid); + + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const submittingRef = useRef(false); + + const handleApprove = useCallback(async () => { + if (submittingRef.current) return; + submittingRef.current = true; + setIsLoading(true); + setError(null); + try { + const requester = new InExtensionMessageRequester(); + const Msg = isUpgrade ? UpgradeSmartAccountMsg : DowngradeSmartAccountMsg; + await requester.sendMessage(BACKGROUND_PORT, new Msg(chainId, vaultId)); + notification.show( + "success", + intl.formatMessage({ id: "notification.transaction-success" }), + "" + ); + goBackAfterApprove(); + } catch (e) { + notification.show( + "failed", + intl.formatMessage({ id: "error.transaction-failed" }), + "" + ); + setError(intl.formatMessage({ id: "error.transaction-failed" })); + setIsLoading(false); + submittingRef.current = false; + } + }, [isUpgrade, chainId, vaultId, goBackAfterApprove, notification, intl]); + + if (!isValid) return null; + + const approveDisabled = + isLoading || + insufficientBalance || + isFailed || + !balanceLoaded || + isEstimating; + + return ( + } + bottomButtons={[ + { + textOverrideIcon: , + color: "secondary", + size: "large", + style: { width: "3.25rem" }, + onClick: goBack, + disabled: isLoading, + }, + { + isSpecial: true, + text: intl.formatMessage({ id: "button.approve" }), + size: "large", + isLoading, + disabled: approveDisabled, + onClick: handleApprove, + }, + ]} + > + + + + {intl.formatMessage({ + id: "page.wallet.smart-account.confirm.section-title", + })} + + + + + + + +
+ + + + {error && ( + + + {error} + + + )} + + + ); +}); diff --git a/apps/extension/src/pages/wallet/smart-account/confirm/hooks/use-confirm-route.ts b/apps/extension/src/pages/wallet/smart-account/confirm/hooks/use-confirm-route.ts new file mode 100644 index 0000000000..22bc199aa3 --- /dev/null +++ b/apps/extension/src/pages/wallet/smart-account/confirm/hooks/use-confirm-route.ts @@ -0,0 +1,51 @@ +import { useCallback } from "react"; +import { useSearchParams } from "react-router-dom"; +import { useNavigate } from "react-router"; + +export function useConfirmRoute() { + const [searchParams] = useSearchParams(); + const navigate = useNavigate(); + + const vaultId = searchParams.get("id") ?? ""; + const chainId = searchParams.get("chainId") ?? ""; + const chainName = searchParams.get("chainName") ?? ""; + const direction = searchParams.get("direction"); + + const isValid = + !!vaultId && + !!chainId && + (direction === "upgrade" || direction === "downgrade"); + + const isUpgrade = direction === "upgrade"; + + const goBack = useCallback( + () => + navigate(`/wallet/smart-account?id=${encodeURIComponent(vaultId)}`, { + replace: true, + }), + [navigate, vaultId] + ); + + const goBackAfterApprove = useCallback( + () => + navigate( + `/wallet/smart-account?id=${encodeURIComponent( + vaultId + )}&updatedChainId=${encodeURIComponent( + chainId + )}&updatedDirection=${direction}`, + { replace: true } + ), + [navigate, vaultId, chainId, direction] + ); + + return { + vaultId, + chainId, + chainName, + isValid, + isUpgrade, + goBack, + goBackAfterApprove, + }; +} diff --git a/apps/extension/src/pages/wallet/smart-account/confirm/hooks/use-smart-account-fee.ts b/apps/extension/src/pages/wallet/smart-account/confirm/hooks/use-smart-account-fee.ts new file mode 100644 index 0000000000..3ac955bd2e --- /dev/null +++ b/apps/extension/src/pages/wallet/smart-account/confirm/hooks/use-smart-account-fee.ts @@ -0,0 +1,119 @@ +import { useCallback, useEffect, useMemo, useState } from "react"; +import { EstimateSmartAccountFeeMsg } from "@keplr-wallet/background"; +import { InExtensionMessageRequester } from "@keplr-wallet/router-extension"; +import { BACKGROUND_PORT } from "@keplr-wallet/router"; +import { CoinPretty, Int } from "@keplr-wallet/unit"; +import { useStore } from "../../../../../stores"; +import { formatFee } from "../../utils"; + +type FeeState = + | { status: "loading" } + | { status: "success"; estimatedFeeWei: string } + | { status: "error" }; + +export function useSmartAccountFee( + chainId: string, + vaultId: string, + hexAddress: string, + isValid: boolean +) { + const { chainStore, queriesStore, priceStore } = useStore(); + + const nativeCurrency = useMemo(() => { + try { + const unwrapped = chainStore.getModularChain(chainId).unwrapped; + if (unwrapped.type === "evm") return unwrapped.evm.nativeCurrency; + if (unwrapped.type === "ethermint") return unwrapped.evm.nativeCurrency; + return undefined; + } catch { + return undefined; + } + }, [chainStore, chainId]); + + const nativeSymbol = nativeCurrency?.coinDenom ?? "ETH"; + + const [feeState, setFeeState] = useState({ status: "loading" }); + const [retryCount, setRetryCount] = useState(0); + + useEffect(() => { + if (!isValid) return; + let cancelled = false; + setFeeState({ status: "loading" }); + new InExtensionMessageRequester() + .sendMessage( + BACKGROUND_PORT, + new EstimateSmartAccountFeeMsg(chainId, vaultId) + ) + .then((result) => { + if (!cancelled) + setFeeState({ + status: "success", + estimatedFeeWei: result.estimatedFeeWei, + }); + }) + .catch(() => { + if (!cancelled) setFeeState({ status: "error" }); + }); + return () => { + cancelled = true; + }; + }, [chainId, vaultId, isValid, retryCount]); + + const estimatedFeeWei = + feeState.status === "success" ? feeState.estimatedFeeWei : null; + const isEstimating = feeState.status === "loading"; + const isFailed = feeState.status === "error"; + + const feeDisplay = useMemo( + () => + feeState.status === "success" + ? formatFee(feeState.estimatedFeeWei, nativeSymbol) + : null, + [feeState, nativeSymbol] + ); + + const feeUsd = useMemo(() => { + if (!estimatedFeeWei || !nativeCurrency) return null; + try { + const feeCoin = new CoinPretty( + nativeCurrency, + new Int(BigInt(estimatedFeeWei).toString()) + ); + const price = priceStore.calculatePrice(feeCoin); + return price ? price.toString() : null; + } catch { + return null; + } + }, [estimatedFeeWei, nativeCurrency, priceStore]); + + const balance = + nativeCurrency && hexAddress + ? queriesStore + .get(chainId) + .queryBalances.getQueryEthereumHexAddress(hexAddress) + .getBalance(nativeCurrency) + : undefined; + + const balanceLoaded = !!balance?.response; + + const insufficientBalance = + balance && balanceLoaded && estimatedFeeWei + ? new Int(balance.balance.toCoin().amount).lt( + new Int(BigInt(estimatedFeeWei).toString()) + ) + : false; + + const retry = useCallback(() => { + setRetryCount((prev) => prev + 1); + }, []); + + return { + feeDisplay, + feeUsd, + isEstimating, + isFailed, + insufficientBalance, + balanceLoaded, + retry, + }; +} diff --git a/apps/extension/src/pages/wallet/smart-account/confirm/index.ts b/apps/extension/src/pages/wallet/smart-account/confirm/index.ts new file mode 100644 index 0000000000..70afd3ae7b --- /dev/null +++ b/apps/extension/src/pages/wallet/smart-account/confirm/index.ts @@ -0,0 +1 @@ +export { SmartAccountConfirmPage } from "./confirm"; From 5deb3d865e1d30038fab2cdd02a065ec011516c9 Mon Sep 17 00:00:00 2001 From: jungcome7 Date: Wed, 1 Apr 2026 22:17:48 +0900 Subject: [PATCH 48/64] feat(extension): wire smart account routes and entry point MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Smart Account UI를 앱에 연결합니다. - index.tsx: /wallet/smart-account, /wallet/smart-account/confirm 라우트 등록 - wallet/index.ts: 페이지 컴포넌트 re-export - account-item.tsx: 계정 드롭다운에 "EVM Smart Account" 메뉴 항목 추가 (eip7702 체인 있을 때만) Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/extension/src/index.tsx | 10 ++++++++++ .../account-switch-float-modal/account-item.tsx | 16 ++++++++++++++++ apps/extension/src/pages/wallet/index.ts | 1 + 3 files changed, 27 insertions(+) diff --git a/apps/extension/src/index.tsx b/apps/extension/src/index.tsx index a8304a8968..9c73689805 100644 --- a/apps/extension/src/index.tsx +++ b/apps/extension/src/index.tsx @@ -66,6 +66,8 @@ import { WalletDeletePage, WalletSelectPage, WalletShowSensitivePage, + SmartAccountPage, + SmartAccountConfirmPage, } from "./pages/wallet"; import { SuggestChainPage } from "./pages/suggest-chain"; import { ModalRootProvider } from "./components/modal"; @@ -549,6 +551,14 @@ const RoutesAfterReady: FunctionComponent = observer(() => { path="/wallet/show-sensitive" element={} /> + } + /> + } + /> } /> } /> ` @@ -145,6 +146,7 @@ export const AccountItemSwitchModal = observer( const icnsPrimaryName = useGetIcnsName(bech32Address); const paragraph = useGetKeyInfoParagraph(keyInfo, true); + const eip7702Chains = useEip7702Chains(); const dropdownItems = (() => { const defaults = [ { @@ -186,6 +188,20 @@ export const AccountItemSwitchModal = observer( } } + // Smart Account menu — only for software wallets with at least one EIP-7702 chain + if ( + (keyInfo.type === "mnemonic" || keyInfo.type === "private-key") && + eip7702Chains.length > 0 + ) { + defaults.splice(defaults.length - 1, 0, { + key: "smart-account", + label: intl.formatMessage({ + id: "page.wallet.keyring-item.dropdown.smart-account-title", + }), + onSelect: () => navigate(`/wallet/smart-account?id=${keyInfo.id}`), + }); + } + return defaults; })(); diff --git a/apps/extension/src/pages/wallet/index.ts b/apps/extension/src/pages/wallet/index.ts index 364bdccb21..0ed97118e9 100644 --- a/apps/extension/src/pages/wallet/index.ts +++ b/apps/extension/src/pages/wallet/index.ts @@ -2,3 +2,4 @@ export * from "./select"; export * from "./delete"; export * from "./change-name"; export * from "./show-sensitive"; +export { SmartAccountPage, SmartAccountConfirmPage } from "./smart-account"; From 8f719fe907c58956d0e89615aeaff540b539b9bb Mon Sep 17 00:00:00 2001 From: jungcome7 Date: Wed, 1 Apr 2026 22:18:15 +0900 Subject: [PATCH 49/64] chore(extension): add i18n keys and minor fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - en/ko/zh-cn.json에 smart account 관련 13개 i18n 키 추가 - helper.ts: buildDummyAuthorizationList의 address 파라미터 타입 명확화 - DSTypography: minor fix Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/extension/src/languages/en.json | 22 +++++++++++++++++++ apps/extension/src/languages/ko.json | 22 +++++++++++++++++++ apps/extension/src/languages/zh-cn.json | 22 +++++++++++++++++++ .../background/src/keyring-ethereum/helper.ts | 4 ++-- .../src/foundation/typography/typography.tsx | 5 +++-- 5 files changed, 71 insertions(+), 4 deletions(-) diff --git a/apps/extension/src/languages/en.json b/apps/extension/src/languages/en.json index 4d70b929eb..2a569bb5fa 100644 --- a/apps/extension/src/languages/en.json +++ b/apps/extension/src/languages/en.json @@ -412,6 +412,28 @@ "page.wallet.keyring-item.dropdown.delete-wallet-title": "Delete Wallet", "page.wallet.keyring-item.dropdown.view-recovery-path-title": "View Recovery Phrase", "page.wallet.keyring-item.dropdown.view-private-key-title": "View Private key", + "page.wallet.keyring-item.dropdown.smart-account-title": "EVM Smart Account", + + "page.wallet.smart-account.title": "Smart Account", + "page.wallet.smart-account.description": "Enable or disable Smart Account for each network.", + "page.wallet.smart-account.status.unsupported": "Unsupported", + "page.wallet.smart-account.confirm.title": "Transaction Confirmation", + "page.wallet.smart-account.confirm.section-title": "Transaction Summary", + "page.wallet.smart-account.confirm.switch-to": "Switch To", + "page.wallet.smart-account.confirm.summary.upgrade": "Upgrade to Smart Account", + "page.wallet.smart-account.confirm.summary.upgrade.desc": "Switching from a regular account to a Smart Account. Your address stays the same.", + "page.wallet.smart-account.confirm.summary.downgrade": "Revert to Standard Account", + "page.wallet.smart-account.confirm.summary.downgrade.desc": "Switching from a Smart Account to a regular account. You can switch back anytime.", + "page.wallet.smart-account.confirm.current": "Current", + "page.wallet.smart-account.confirm.current.eoa": "Regular Account (EOA)", + "page.wallet.smart-account.confirm.current.smart": "Smart Account", + "page.wallet.smart-account.confirm.delegator": "Delegation Contract", + "page.wallet.smart-account.confirm.network": "Network", + "page.wallet.smart-account.confirm.account": "Account", + "page.wallet.smart-account.confirm.fee": "Network Fee", + "page.wallet.smart-account.confirm.fee.insufficient": "Insufficient balance for network fee.", + "page.wallet.smart-account.confirm.fee.failed": "Unable to estimate", + "page.wallet.smart-account.confirm.fee.retry": "Tap to retry", "page.wallet.change-name.previous-name-input-label": "Previous Wallet Name", "page.wallet.change-name.new-name-input-label": "New Wallet Name", diff --git a/apps/extension/src/languages/ko.json b/apps/extension/src/languages/ko.json index 5a2ec710c3..90d42ade18 100644 --- a/apps/extension/src/languages/ko.json +++ b/apps/extension/src/languages/ko.json @@ -406,6 +406,28 @@ "page.wallet.keyring-item.dropdown.delete-wallet-title": "지갑 삭제", "page.wallet.keyring-item.dropdown.view-recovery-path-title": "복구 문구 보기", "page.wallet.keyring-item.dropdown.view-private-key-title": "프라이빗키 보기", + "page.wallet.keyring-item.dropdown.smart-account-title": "EVM 스마트 계정", + + "page.wallet.smart-account.title": "스마트 계정", + "page.wallet.smart-account.description": "네트워크별로 Smart Account를 활성화하거나 비활성화할 수 있습니다.", + "page.wallet.smart-account.status.unsupported": "미지원", + "page.wallet.smart-account.confirm.title": "트랜잭션 확인", + "page.wallet.smart-account.confirm.section-title": "트랜잭션 요약", + "page.wallet.smart-account.confirm.switch-to": "전환 대상", + "page.wallet.smart-account.confirm.summary.upgrade": "계정 업그레이드", + "page.wallet.smart-account.confirm.summary.upgrade.desc": "일반 계정에서 스마트 계정으로 전환합니다. 주소는 동일하게 유지됩니다.", + "page.wallet.smart-account.confirm.summary.downgrade": "계정 다운그레이드", + "page.wallet.smart-account.confirm.summary.downgrade.desc": "스마트 계정에서 일반 계정으로 전환합니다. 언제든 다시 전환할 수 있습니다.", + "page.wallet.smart-account.confirm.current": "현재", + "page.wallet.smart-account.confirm.current.eoa": "일반 계정 (EOA)", + "page.wallet.smart-account.confirm.current.smart": "스마트 계정", + "page.wallet.smart-account.confirm.delegator": "위임 컨트랙트", + "page.wallet.smart-account.confirm.network": "네트워크", + "page.wallet.smart-account.confirm.account": "계정", + "page.wallet.smart-account.confirm.fee": "네트워크 수수료", + "page.wallet.smart-account.confirm.fee.insufficient": "네트워크 수수료를 위한 잔액이 부족합니다.", + "page.wallet.smart-account.confirm.fee.failed": "추정 불가", + "page.wallet.smart-account.confirm.fee.retry": "탭하여 재시도", "page.wallet.change-name.previous-name-input-label": "현재 지갑 이름", "page.wallet.change-name.new-name-input-label": "새로운 지갑 이름", diff --git a/apps/extension/src/languages/zh-cn.json b/apps/extension/src/languages/zh-cn.json index a97bb0c365..70f87078a2 100644 --- a/apps/extension/src/languages/zh-cn.json +++ b/apps/extension/src/languages/zh-cn.json @@ -376,6 +376,28 @@ "page.wallet.keyring-item.dropdown.delete-wallet-title": "删除钱包", "page.wallet.keyring-item.dropdown.view-recovery-path-title": "查看助记词", "page.wallet.keyring-item.dropdown.view-private-key-title": "查看私钥", + "page.wallet.keyring-item.dropdown.smart-account-title": "EVM 智能账户", + + "page.wallet.smart-account.title": "智能账户", + "page.wallet.smart-account.description": "按网络启用或禁用智能账户。", + "page.wallet.smart-account.status.unsupported": "不支持", + "page.wallet.smart-account.confirm.title": "交易确认", + "page.wallet.smart-account.confirm.section-title": "交易摘要", + "page.wallet.smart-account.confirm.switch-to": "切换到", + "page.wallet.smart-account.confirm.summary.upgrade": "升级为智能账户", + "page.wallet.smart-account.confirm.summary.upgrade.desc": "普通账户切换为智能账户。地址保持不变。", + "page.wallet.smart-account.confirm.summary.downgrade": "恢复为标准账户", + "page.wallet.smart-account.confirm.summary.downgrade.desc": "智能账户切换为普通账户。可随时切换回来。", + "page.wallet.smart-account.confirm.current": "当前", + "page.wallet.smart-account.confirm.current.eoa": "普通账户 (EOA)", + "page.wallet.smart-account.confirm.current.smart": "智能账户", + "page.wallet.smart-account.confirm.delegator": "委托合约", + "page.wallet.smart-account.confirm.network": "网络", + "page.wallet.smart-account.confirm.account": "账户", + "page.wallet.smart-account.confirm.fee": "网络费用", + "page.wallet.smart-account.confirm.fee.insufficient": "余额不足以支付网络费用。", + "page.wallet.smart-account.confirm.fee.failed": "无法估算", + "page.wallet.smart-account.confirm.fee.retry": "点击重试", "page.wallet.change-name.previous-name-input-label": "原钱包名称", "page.wallet.change-name.new-name-input-label": "新钱包名称", diff --git a/packages/background/src/keyring-ethereum/helper.ts b/packages/background/src/keyring-ethereum/helper.ts index 951cc989bf..eb739777b4 100644 --- a/packages/background/src/keyring-ethereum/helper.ts +++ b/packages/background/src/keyring-ethereum/helper.ts @@ -108,11 +108,11 @@ export function buildDummyAuthorizationList( return [ { address, - chainId, + chainId: `0x${chainId.toString(16)}`, nonce: "0x1", r: "0x" + "1".repeat(64), s: "0x" + "1".repeat(64), - yParity: 1, + yParity: "0x1", }, ]; } diff --git a/packages/design-system/src/foundation/typography/typography.tsx b/packages/design-system/src/foundation/typography/typography.tsx index 143c65dcb4..64bb669905 100644 --- a/packages/design-system/src/foundation/typography/typography.tsx +++ b/packages/design-system/src/foundation/typography/typography.tsx @@ -1,6 +1,7 @@ import React from "react"; import { dsTypographyTokens } from "./typography-tokens"; import type { DSTypographySize } from "./typography-tokens"; +import { DSColor } from "../color"; type Weight = "semibold" | "medium" | "regular"; @@ -17,7 +18,7 @@ export interface DSTypographyProps extends React.HTMLAttributes { weight?: Weight; /** Override token font size (px) */ fontSize?: number; - /** Text color — `DSColor.typography.primary`, `DSColor.blue400`, or any CSS color */ + /** Text color — defaults to `DSColor.typography.primary`. Accepts any CSS color. */ color?: string; /** HTML element to render as — `"span"`, `"p"`, `"h1"`, `"div"`, `"label"`, etc. */ as?: React.ElementType; @@ -47,7 +48,7 @@ export const DSTypography = React.forwardRef( lineHeight: token?.lineHeight, letterSpacing: token?.letterSpacing, fontWeight: WEIGHT_VALUE[weight], - color, + color: color ?? DSColor.typography.primary, ...style, }} {...rest} From 9378234f8f537792e7633db6ec49dced7f0ac1e5 Mon Sep 17 00:00:00 2001 From: jungcome7 Date: Thu, 2 Apr 2026 12:04:49 +0900 Subject: [PATCH 50/64] refactor(extension): extract constants from utils into constants.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 상수(TOGGLE_ANIMATION_DELAY_MS, STATUS_UPDATE_DELAY_MS, DELEGATOR_DISPLAY)를 constants.ts로 분리하고 utils.ts에는 순수 함수만 남깁니다. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../smart-account/confirm/components/detail-card.tsx | 3 ++- .../src/pages/wallet/smart-account/constants.ts | 9 +++++++++ .../network-list/hooks/use-chain-statuses.ts | 2 +- .../wallet/smart-account/network-list/network-list.tsx | 2 +- apps/extension/src/pages/wallet/smart-account/utils.ts | 9 --------- 5 files changed, 13 insertions(+), 12 deletions(-) create mode 100644 apps/extension/src/pages/wallet/smart-account/constants.ts diff --git a/apps/extension/src/pages/wallet/smart-account/confirm/components/detail-card.tsx b/apps/extension/src/pages/wallet/smart-account/confirm/components/detail-card.tsx index 95b7be1c8e..99f4731e76 100644 --- a/apps/extension/src/pages/wallet/smart-account/confirm/components/detail-card.tsx +++ b/apps/extension/src/pages/wallet/smart-account/confirm/components/detail-card.tsx @@ -4,7 +4,8 @@ import styled from "styled-components"; import { DSColor, DSTypography } from "@keplr-wallet/design-system"; import { XAxis, YAxis } from "../../../../../components/axis"; import { ChainImageFallback } from "../../../../../components/image"; -import { DELEGATOR_DISPLAY, truncateAddress } from "../../utils"; +import { DELEGATOR_DISPLAY } from "../../constants"; +import { truncateAddress } from "../../utils"; import type { IModularChainInfoImpl } from "@keplr-wallet/stores"; import { Card } from "./card"; diff --git a/apps/extension/src/pages/wallet/smart-account/constants.ts b/apps/extension/src/pages/wallet/smart-account/constants.ts new file mode 100644 index 0000000000..47a0164bff --- /dev/null +++ b/apps/extension/src/pages/wallet/smart-account/constants.ts @@ -0,0 +1,9 @@ +import { ALLOWED_DELEGATORS } from "@keplr-wallet/background"; +import { truncateAddress } from "./utils"; + +export const TOGGLE_ANIMATION_DELAY_MS = 200; +export const STATUS_UPDATE_DELAY_MS = 100; + +export const DELEGATOR_DISPLAY = ALLOWED_DELEGATORS[0] + ? truncateAddress(ALLOWED_DELEGATORS[0]) + : ""; diff --git a/apps/extension/src/pages/wallet/smart-account/network-list/hooks/use-chain-statuses.ts b/apps/extension/src/pages/wallet/smart-account/network-list/hooks/use-chain-statuses.ts index fd468773b1..d856e159dd 100644 --- a/apps/extension/src/pages/wallet/smart-account/network-list/hooks/use-chain-statuses.ts +++ b/apps/extension/src/pages/wallet/smart-account/network-list/hooks/use-chain-statuses.ts @@ -3,7 +3,7 @@ import { useSearchParams } from "react-router-dom"; import type { DelegationStatus } from "@keplr-wallet/types"; import { useStore } from "../../../../../stores"; import { useEip7702Chains } from "../../hooks/use-eip7702-chains"; -import { STATUS_UPDATE_DELAY_MS } from "../../utils"; +import { STATUS_UPDATE_DELAY_MS } from "../../constants"; export interface ChainStatus { chainId: string; diff --git a/apps/extension/src/pages/wallet/smart-account/network-list/network-list.tsx b/apps/extension/src/pages/wallet/smart-account/network-list/network-list.tsx index fe89af719e..12e9f8d83d 100644 --- a/apps/extension/src/pages/wallet/smart-account/network-list/network-list.tsx +++ b/apps/extension/src/pages/wallet/smart-account/network-list/network-list.tsx @@ -17,7 +17,7 @@ import { Box } from "../../../../components/box"; import { useStore } from "../../../../stores"; import { useRedirectIfInvalid } from "../hooks/use-redirect-if-invalid"; import { useEip7702Chains } from "../hooks/use-eip7702-chains"; -import { TOGGLE_ANIMATION_DELAY_MS } from "../utils"; +import { TOGGLE_ANIMATION_DELAY_MS } from "../constants"; import { useChainStatuses } from "./hooks/use-chain-statuses"; import { ChainRowItem } from "./components/chain-row-item"; diff --git a/apps/extension/src/pages/wallet/smart-account/utils.ts b/apps/extension/src/pages/wallet/smart-account/utils.ts index 573d8ef0ca..1de3de96f0 100644 --- a/apps/extension/src/pages/wallet/smart-account/utils.ts +++ b/apps/extension/src/pages/wallet/smart-account/utils.ts @@ -1,15 +1,6 @@ -import { ALLOWED_DELEGATORS } from "@keplr-wallet/background"; - -export const TOGGLE_ANIMATION_DELAY_MS = 200; -export const STATUS_UPDATE_DELAY_MS = 100; - export const truncateAddress = (addr: string): string => addr ? `${addr.slice(0, 10)}...${addr.slice(-8)}` : ""; -export const DELEGATOR_DISPLAY = ALLOWED_DELEGATORS[0] - ? truncateAddress(ALLOWED_DELEGATORS[0]) - : ""; - export const formatFee = ( weiStr: string, symbol: string, From 08400f3194356e99c66ebb22f09c6a559864a55e Mon Sep 17 00:00:00 2001 From: rowan Date: Thu, 2 Apr 2026 13:41:57 +0900 Subject: [PATCH 51/64] fix: improve array validation for send calls request --- packages/background/src/keyring-ethereum/helper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/background/src/keyring-ethereum/helper.ts b/packages/background/src/keyring-ethereum/helper.ts index 680e0ee219..608fa7132c 100644 --- a/packages/background/src/keyring-ethereum/helper.ts +++ b/packages/background/src/keyring-ethereum/helper.ts @@ -322,7 +322,7 @@ export function validateSendCallsRequest( request: WalletSendCallsRequest, hexChainId: string ): SendCallsValidationError | null { - if (!request.calls || request.calls.length === 0) { + if (!Array.isArray(request.calls) || request.calls.length === 0) { return { code: RPC_ERROR_INVALID_PARAMS, message: "Must provide at least one call.", From 30b7518b60668de5b95770bfcc07253791b06a0b Mon Sep 17 00:00:00 2001 From: jungcome7 Date: Thu, 2 Apr 2026 14:02:42 +0900 Subject: [PATCH 52/64] fix(cosmos): add missing ox dependency to fix lint error packages/cosmos/src/bech32/index.ts imports ox but it was not listed in package.json dependencies, causing import/no-extraneous-dependencies lint failure in CI. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/cosmos/package.json | 1 + yarn.lock | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/cosmos/package.json b/packages/cosmos/package.json index e671fa0c37..e6d1a6a578 100644 --- a/packages/cosmos/package.json +++ b/packages/cosmos/package.json @@ -31,6 +31,7 @@ "bech32": "^1.1.4", "buffer": "^6.0.3", "long": "^4.0.0", + "ox": "^0.14.6", "protobufjs": "^6.11.2", "utility-types": "^3.10.0" } diff --git a/yarn.lock b/yarn.lock index 583ddd66ff..a6d576283b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6522,6 +6522,7 @@ __metadata: bech32: ^1.1.4 buffer: ^6.0.3 long: ^4.0.0 + ox: ^0.14.6 protobufjs: ^6.11.2 utility-types: ^3.10.0 languageName: unknown From 9ad4d5948147feac85677eef307835c61c131f81 Mon Sep 17 00:00:00 2001 From: jungcome7 Date: Thu, 2 Apr 2026 14:10:59 +0900 Subject: [PATCH 53/64] fix: add missing ox dependency to stores-etc, hooks-evm, hooks Same import/no-extraneous-dependencies lint error as cosmos package. These three packages import from "ox" but didn't declare it in their package.json dependencies. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/hooks-evm/package.json | 3 ++- packages/hooks/package.json | 1 + packages/stores-etc/package.json | 1 + yarn.lock | 3 +++ 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/hooks-evm/package.json b/packages/hooks-evm/package.json index b062b8ec9f..602f0a50ad 100644 --- a/packages/hooks-evm/package.json +++ b/packages/hooks-evm/package.json @@ -30,7 +30,8 @@ "@keplr-wallet/stores-eth": "0.13.19", "@keplr-wallet/types": "0.13.19", "@keplr-wallet/unit": "0.13.19", - "buffer": "^6.0.3" + "buffer": "^6.0.3", + "ox": "^0.14.6" }, "peerDependencies": { "mobx": "^6", diff --git a/packages/hooks/package.json b/packages/hooks/package.json index 47ca190a7a..a90059366d 100644 --- a/packages/hooks/package.json +++ b/packages/hooks/package.json @@ -36,6 +36,7 @@ "@keplr-wallet/unit": "0.13.19", "buffer": "^6.0.3", "long": "^4.0.0", + "ox": "^0.14.6", "utility-types": "^3.10.0" }, "peerDependencies": { diff --git a/packages/stores-etc/package.json b/packages/stores-etc/package.json index 68eed71122..cc99db4801 100644 --- a/packages/stores-etc/package.json +++ b/packages/stores-etc/package.json @@ -27,6 +27,7 @@ "@keplr-wallet/stores": "0.13.19", "@keplr-wallet/types": "0.13.19", "@keplr-wallet/unit": "0.13.19", + "ox": "^0.14.6", "utility-types": "^3.10.0" }, "peerDependencies": { diff --git a/yarn.lock b/yarn.lock index a6d576283b..df12f70166 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6753,6 +6753,7 @@ __metadata: "@keplr-wallet/types": 0.13.19 "@keplr-wallet/unit": 0.13.19 buffer: ^6.0.3 + ox: ^0.14.6 peerDependencies: mobx: ^6 mobx-utils: ^6 @@ -6826,6 +6827,7 @@ __metadata: "@keplr-wallet/unit": 0.13.19 buffer: ^6.0.3 long: ^4.0.0 + ox: ^0.14.6 utility-types: ^3.10.0 peerDependencies: mobx: ^6 @@ -6991,6 +6993,7 @@ __metadata: "@keplr-wallet/stores": 0.13.19 "@keplr-wallet/types": 0.13.19 "@keplr-wallet/unit": 0.13.19 + ox: ^0.14.6 utility-types: ^3.10.0 peerDependencies: mobx: ^6 From d87d8ff829f0a8b1a03b80b2d7aac4e04acac838 Mon Sep 17 00:00:00 2001 From: jungcome7 Date: Fri, 3 Apr 2026 14:43:29 +0900 Subject: [PATCH 54/64] fix(smart-account): resolve vault-scoped address for non-selected accounts Add GetEthereumAddressForVaultMsg to resolve ethereum hex address for a specific vaultId instead of relying on the globally selected account. This fixes incorrect address, balance, and delegation status when managing smart accounts for non-active vaults. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../wallet/smart-account/confirm/confirm.tsx | 11 ++---- .../hooks/use-vault-hex-address.ts | 29 ++++++++++++++++ .../network-list/network-list.tsx | 23 +++---------- .../src/keyring-ethereum/handler.ts | 6 ++++ .../background/src/keyring-ethereum/init.ts | 2 ++ .../src/keyring-ethereum/messages.ts | 34 +++++++++++++++++++ .../src/keyring-ethereum/service.ts | 2 +- 7 files changed, 79 insertions(+), 28 deletions(-) create mode 100644 apps/extension/src/pages/wallet/smart-account/hooks/use-vault-hex-address.ts diff --git a/apps/extension/src/pages/wallet/smart-account/confirm/confirm.tsx b/apps/extension/src/pages/wallet/smart-account/confirm/confirm.tsx index 06a26760ca..3d406547c4 100644 --- a/apps/extension/src/pages/wallet/smart-account/confirm/confirm.tsx +++ b/apps/extension/src/pages/wallet/smart-account/confirm/confirm.tsx @@ -22,6 +22,7 @@ import { Box } from "../../../../components/box"; import { useStore } from "../../../../stores"; import { useNotification } from "../../../../hooks/notification"; import { useRedirectIfInvalid } from "../hooks/use-redirect-if-invalid"; +import { useVaultHexAddress } from "../hooks/use-vault-hex-address"; import { useConfirmRoute } from "./hooks/use-confirm-route"; import { useSmartAccountFee } from "./hooks/use-smart-account-fee"; import { SummaryCard } from "./components/summary-card"; @@ -31,7 +32,7 @@ import { FeeCard } from "./components/fee-card"; export const SmartAccountConfirmPage: FunctionComponent = observer(() => { const intl = useIntl(); const notification = useNotification(); - const { chainStore, accountStore, keyRingStore } = useStore(); + const { chainStore, keyRingStore } = useStore(); const { vaultId, @@ -45,13 +46,7 @@ export const SmartAccountConfirmPage: FunctionComponent = observer(() => { useRedirectIfInvalid(isValid); - const hexAddress = useMemo(() => { - try { - return accountStore.getAccount(chainId).ethereumHexAddress; - } catch { - return ""; - } - }, [accountStore, chainId]); + const hexAddress = useVaultHexAddress(chainId, vaultId); const accountName = useMemo(() => { const keyInfo = keyRingStore.keyInfos.find((info) => info.id === vaultId); diff --git a/apps/extension/src/pages/wallet/smart-account/hooks/use-vault-hex-address.ts b/apps/extension/src/pages/wallet/smart-account/hooks/use-vault-hex-address.ts new file mode 100644 index 0000000000..87181040a7 --- /dev/null +++ b/apps/extension/src/pages/wallet/smart-account/hooks/use-vault-hex-address.ts @@ -0,0 +1,29 @@ +import { useEffect, useState } from "react"; +import { GetEthereumAddressForVaultMsg } from "@keplr-wallet/background"; +import { InExtensionMessageRequester } from "@keplr-wallet/router-extension"; +import { BACKGROUND_PORT } from "@keplr-wallet/router"; + +export function useVaultHexAddress(chainId: string, vaultId: string): string { + const [hexAddress, setHexAddress] = useState(""); + + useEffect(() => { + if (!chainId || !vaultId) return; + let cancelled = false; + + new InExtensionMessageRequester() + .sendMessage( + BACKGROUND_PORT, + new GetEthereumAddressForVaultMsg(chainId, vaultId) + ) + .then((addr) => { + if (!cancelled) setHexAddress(addr); + }) + .catch(() => {}); + + return () => { + cancelled = true; + }; + }, [chainId, vaultId]); + + return hexAddress; +} diff --git a/apps/extension/src/pages/wallet/smart-account/network-list/network-list.tsx b/apps/extension/src/pages/wallet/smart-account/network-list/network-list.tsx index 12e9f8d83d..0679e1f19a 100644 --- a/apps/extension/src/pages/wallet/smart-account/network-list/network-list.tsx +++ b/apps/extension/src/pages/wallet/smart-account/network-list/network-list.tsx @@ -1,10 +1,4 @@ -import React, { - FunctionComponent, - useCallback, - useMemo, - useRef, - useState, -} from "react"; +import React, { FunctionComponent, useCallback, useRef, useState } from "react"; import { observer } from "mobx-react-lite"; import { useSearchParams } from "react-router-dom"; import { useNavigate } from "react-router"; @@ -14,9 +8,9 @@ import { DSColor, DSTypography } from "@keplr-wallet/design-system"; import { BackButton } from "../../../../layouts/header/components"; import { HeaderLayout } from "../../../../layouts/header"; import { Box } from "../../../../components/box"; -import { useStore } from "../../../../stores"; import { useRedirectIfInvalid } from "../hooks/use-redirect-if-invalid"; import { useEip7702Chains } from "../hooks/use-eip7702-chains"; +import { useVaultHexAddress } from "../hooks/use-vault-hex-address"; import { TOGGLE_ANIMATION_DELAY_MS } from "../constants"; import { useChainStatuses } from "./hooks/use-chain-statuses"; import { ChainRowItem } from "./components/chain-row-item"; @@ -25,22 +19,13 @@ export const SmartAccountPage: FunctionComponent = observer(() => { const [searchParams] = useSearchParams(); const navigate = useNavigate(); const intl = useIntl(); - const { accountStore } = useStore(); - const vaultId = searchParams.get("id"); useRedirectIfInvalid(!!vaultId); const eip7702Chains = useEip7702Chains(); - const hexAddress = useMemo(() => { - const firstChain = eip7702Chains[0]; - if (!firstChain) return ""; - try { - return accountStore.getAccount(firstChain.chainId).ethereumHexAddress; - } catch { - return ""; - } - }, [accountStore, eip7702Chains]); + const firstChainId = eip7702Chains[0]?.chainId ?? ""; + const hexAddress = useVaultHexAddress(firstChainId, vaultId ?? ""); const { chainStatuses, animatingChainId } = useChainStatuses(hexAddress); diff --git a/packages/background/src/keyring-ethereum/handler.ts b/packages/background/src/keyring-ethereum/handler.ts index da8da61ce1..e0dd0c791f 100644 --- a/packages/background/src/keyring-ethereum/handler.ts +++ b/packages/background/src/keyring-ethereum/handler.ts @@ -13,6 +13,7 @@ import { UpgradeSmartAccountMsg, DowngradeSmartAccountMsg, EstimateSmartAccountFeeMsg, + GetEthereumAddressForVaultMsg, } from "./messages"; import { KeyRingEthereumService } from "./service"; import { PermissionInteractiveService } from "../permission-interactive"; @@ -64,6 +65,11 @@ export const getHandler: ( (msg as EstimateSmartAccountFeeMsg).chainId, (msg as EstimateSmartAccountFeeMsg).vaultId ); + case GetEthereumAddressForVaultMsg: + return service.getEthAddressForVault( + (msg as GetEthereumAddressForVaultMsg).chainId, + (msg as GetEthereumAddressForVaultMsg).vaultId + ); default: throw new KeplrError("keyring", 221, "Unknown msg type"); } diff --git a/packages/background/src/keyring-ethereum/init.ts b/packages/background/src/keyring-ethereum/init.ts index f44c9789ee..927ee3b3ad 100644 --- a/packages/background/src/keyring-ethereum/init.ts +++ b/packages/background/src/keyring-ethereum/init.ts @@ -8,6 +8,7 @@ import { UpgradeSmartAccountMsg, DowngradeSmartAccountMsg, EstimateSmartAccountFeeMsg, + GetEthereumAddressForVaultMsg, } from "./messages"; import { ROUTE } from "./constants"; import { getHandler } from "./handler"; @@ -25,6 +26,7 @@ export function init( router.registerMessage(UpgradeSmartAccountMsg); router.registerMessage(DowngradeSmartAccountMsg); router.registerMessage(EstimateSmartAccountFeeMsg); + router.registerMessage(GetEthereumAddressForVaultMsg); router.addHandler(ROUTE, getHandler(service, permissionInteractionService)); } diff --git a/packages/background/src/keyring-ethereum/messages.ts b/packages/background/src/keyring-ethereum/messages.ts index b0cd3c5cbe..92d73b3ac8 100644 --- a/packages/background/src/keyring-ethereum/messages.ts +++ b/packages/background/src/keyring-ethereum/messages.ts @@ -218,6 +218,40 @@ export class EstimateSmartAccountFeeMsg extends Message<{ } } +export class GetEthereumAddressForVaultMsg extends Message { + public static type() { + return "get-ethereum-address-for-vault"; + } + + constructor( + public readonly chainId: string, + public readonly vaultId: string + ) { + super(); + } + + validateBasic(): void { + if (!this.chainId) { + throw new Error("chain id not set"); + } + if (!this.vaultId) { + throw new Error("vault id not set"); + } + } + + override approveExternal(): boolean { + return false; + } + + route(): string { + return ROUTE; + } + + type(): string { + return GetEthereumAddressForVaultMsg.type(); + } +} + export class CheckNeedEnableAccessForEVMMsg extends Message { public static type() { return "check-need-enable-access-for-evm"; diff --git a/packages/background/src/keyring-ethereum/service.ts b/packages/background/src/keyring-ethereum/service.ts index 54af5c452c..68c2519516 100644 --- a/packages/background/src/keyring-ethereum/service.ts +++ b/packages/background/src/keyring-ethereum/service.ts @@ -1198,7 +1198,7 @@ export class KeyRingEthereumService { return undefined; } - private async getEthAddressForVault( + async getEthAddressForVault( chainId: string, vaultId: string ): Promise { From b7efe483195e6bdc0c8c4dc2523e5bdbb89d301e Mon Sep 17 00:00:00 2001 From: jungcome7 Date: Fri, 3 Apr 2026 14:50:53 +0900 Subject: [PATCH 55/64] fix(smart-account): disable toggle while delegation query is loading or errored Add per-chain isLoading/hasError to ChainStatus and pass disabled prop to ChainRowItem toggle, preventing interaction before the delegation status query resolves. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../components/chain-row-item.tsx | 91 ++++++++++--------- .../network-list/hooks/use-chain-statuses.ts | 4 + .../network-list/network-list.tsx | 1 + 3 files changed, 53 insertions(+), 43 deletions(-) diff --git a/apps/extension/src/pages/wallet/smart-account/network-list/components/chain-row-item.tsx b/apps/extension/src/pages/wallet/smart-account/network-list/components/chain-row-item.tsx index 7e5afd1a72..e494d88a3b 100644 --- a/apps/extension/src/pages/wallet/smart-account/network-list/components/chain-row-item.tsx +++ b/apps/extension/src/pages/wallet/smart-account/network-list/components/chain-row-item.tsx @@ -15,52 +15,57 @@ export const ChainRowItem: FunctionComponent<{ chainName: string; status: DelegationStatus; isAnimating: boolean; + disabled?: boolean; onToggle: () => void; -}> = observer(({ chainId, chainName, status, isAnimating, onToggle }) => { - const intl = useIntl(); - const { chainStore } = useStore(); +}> = observer( + ({ chainId, chainName, status, isAnimating, disabled, onToggle }) => { + const intl = useIntl(); + const { chainStore } = useStore(); - let chainInfo; - try { - chainInfo = chainStore.getModularChain(chainId); - } catch { - // noop - } + let chainInfo; + try { + chainInfo = chainStore.getModularChain(chainId); + } catch { + // noop + } - return ( - - - {chainInfo && } - - - {chainName} - - {status === "unsupported" && ( - - - - {intl.formatMessage({ - id: "page.wallet.smart-account.status.unsupported", - })} - - - )} - - - - - - ); -}); + return ( + + + {chainInfo && ( + + )} + + + {chainName} + + {status === "unsupported" && ( + + + + {intl.formatMessage({ + id: "page.wallet.smart-account.status.unsupported", + })} + + + )} + + + + + + ); + } +); const ChainRow = styled(XAxis)` padding: 0.75rem 0; diff --git a/apps/extension/src/pages/wallet/smart-account/network-list/hooks/use-chain-statuses.ts b/apps/extension/src/pages/wallet/smart-account/network-list/hooks/use-chain-statuses.ts index d856e159dd..164d6d9bb3 100644 --- a/apps/extension/src/pages/wallet/smart-account/network-list/hooks/use-chain-statuses.ts +++ b/apps/extension/src/pages/wallet/smart-account/network-list/hooks/use-chain-statuses.ts @@ -9,6 +9,8 @@ export interface ChainStatus { chainId: string; chainName: string; status: DelegationStatus; + isLoading: boolean; + hasError: boolean; } export function useChainStatuses(hexAddress: string) { @@ -29,6 +31,8 @@ export function useChainStatuses(hexAddress: string) { chainId: chain.chainId, chainName: chain.chainName, status: query?.delegationStatus ?? "ready", + isLoading: !!(!query?.response && !query?.error), + hasError: !!query?.error, }; }); diff --git a/apps/extension/src/pages/wallet/smart-account/network-list/network-list.tsx b/apps/extension/src/pages/wallet/smart-account/network-list/network-list.tsx index 0679e1f19a..0fc6434e32 100644 --- a/apps/extension/src/pages/wallet/smart-account/network-list/network-list.tsx +++ b/apps/extension/src/pages/wallet/smart-account/network-list/network-list.tsx @@ -89,6 +89,7 @@ export const SmartAccountPage: FunctionComponent = observer(() => { chainName={chain.chainName} status={displayStatus} isAnimating={isPending || chain.chainId === animatingChainId} + disabled={chain.isLoading || chain.hasError} onToggle={() => handleToggle(chain.chainId, chain.chainName, chain.status) } From 356a40c36bdbcb20826deca17b8a1038a64f24ca Mon Sep 17 00:00:00 2001 From: rowan Date: Fri, 3 Apr 2026 16:36:11 +0900 Subject: [PATCH 56/64] fix: truncate gas fee values in transaction submission --- .../send/amount/evm/hooks/use-evm-transfer-tx-submit.ts | 8 ++++---- apps/extension/src/pages/send/amount/index.tsx | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/extension/src/pages/send/amount/evm/hooks/use-evm-transfer-tx-submit.ts b/apps/extension/src/pages/send/amount/evm/hooks/use-evm-transfer-tx-submit.ts index 3e942ccaf8..fa50427695 100644 --- a/apps/extension/src/pages/send/amount/evm/hooks/use-evm-transfer-tx-submit.ts +++ b/apps/extension/src/pages/send/amount/evm/hooks/use-evm-transfer-tx-submit.ts @@ -70,9 +70,9 @@ export function useEvmTransferTxSubmit({ amount: sendConfigs.amountConfig.amount[0].toDec().toString(), to: sendConfigs.recipientConfig.recipient, gasLimit: sendConfigs.gasConfig.gas, - maxFeePerGas: maxFeePerGas?.toString(), - maxPriorityFeePerGas: maxPriorityFeePerGas?.toString(), - gasPrice: gasPrice?.toString(), + maxFeePerGas: maxFeePerGas?.truncate().toString(), + maxPriorityFeePerGas: maxPriorityFeePerGas?.truncate().toString(), + gasPrice: gasPrice?.truncate().toString(), }); await ethereumAccount.sendEthereumTx( sender, @@ -133,7 +133,7 @@ export function useEvmTransferTxSubmit({ historyType, chainId, sendConfigs.recipientConfig.chainId, - signedTx, + new Uint8Array(signedTx), sendConfigs.senderConfig.sender, sendConfigs.recipientConfig.recipient, sendConfigs.amountConfig.amount.map((amount) => { diff --git a/apps/extension/src/pages/send/amount/index.tsx b/apps/extension/src/pages/send/amount/index.tsx index 7665f2e152..b431f34336 100644 --- a/apps/extension/src/pages/send/amount/index.tsx +++ b/apps/extension/src/pages/send/amount/index.tsx @@ -669,9 +669,9 @@ export const SendAmountPage: FunctionComponent = observer(() => { amount: sendConfigs.amountConfig.amount[0].toDec().toString(), to: sendConfigs.recipientConfig.recipient, gasLimit: sendConfigs.gasConfig.gas, - maxFeePerGas: maxFeePerGas?.toString(), - maxPriorityFeePerGas: maxPriorityFeePerGas?.toString(), - gasPrice: gasPrice?.toString(), + maxFeePerGas: maxFeePerGas?.truncate().toString(), + maxPriorityFeePerGas: maxPriorityFeePerGas?.truncate().toString(), + gasPrice: gasPrice?.truncate().toString(), }); const l1DataFee = await ethereumAccount.simulateOpStackL1Fee({ From f83ff444ec7110b1768d8b74679f325a6a5cf304 Mon Sep 17 00:00:00 2001 From: jungcome7 Date: Mon, 6 Apr 2026 15:39:12 +0900 Subject: [PATCH 57/64] feat(stores-eth): add authorizationList support to simulateGas Pass authorizationList from UnsignedEVMTransaction to eth_estimateGas params, enabling EIP-7702 gas estimation through the store layer. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/stores-eth/src/account/base.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/stores-eth/src/account/base.ts b/packages/stores-eth/src/account/base.ts index 93ba571a66..127696194d 100644 --- a/packages/stores-eth/src/account/base.ts +++ b/packages/stores-eth/src/account/base.ts @@ -140,7 +140,7 @@ export class EthereumAccountBase { throw new Error("No EVM chain info provided"); } - const { to, value, data } = unsignedTx; + const { to, value, data, authorizationList } = unsignedTx; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const keplr = (await this.getKeplr())!; @@ -151,6 +151,7 @@ export class EthereumAccountBase { to, value, data, + ...(authorizationList ? { authorizationList } : {}), }, ]; From 12de6897e5cdcee6a5a0b5db85a265dda185418e Mon Sep 17 00:00:00 2001 From: jungcome7 Date: Mon, 6 Apr 2026 15:39:24 +0900 Subject: [PATCH 58/64] feat(smart-account): atomic batch TX assembly + batchCallsMap improvements - Add createAtomicBatchTransaction using ox/erc7821 Execute.encodeData - Add buildDelegationStateOverride for non-upgraded account gas estimation - Export helper functions for UI consumption - Simplify handleSendCalls (no pre-built TX, gas estimated by UI) - Add origin/strategy/apiVersion to batchCallsMap - Fix atomic hardcoding and version hardcoding in handleGetCallsStatus - Add origin isolation in handleGetCallsStatus Co-Authored-By: Claude Opus 4.6 (1M context) --- .../background/src/keyring-ethereum/helper.ts | 55 ++++++++++++++++ .../background/src/keyring-ethereum/index.ts | 5 ++ .../src/keyring-ethereum/service.ts | 62 ++++++------------- 3 files changed, 80 insertions(+), 42 deletions(-) diff --git a/packages/background/src/keyring-ethereum/helper.ts b/packages/background/src/keyring-ethereum/helper.ts index 5e93c022fc..8114eb2e35 100644 --- a/packages/background/src/keyring-ethereum/helper.ts +++ b/packages/background/src/keyring-ethereum/helper.ts @@ -7,10 +7,12 @@ import { WalletSendCallsRequest, } from "@keplr-wallet/types"; import { Address, Hex } from "ox"; +import { Execute } from "ox/erc7821"; import { Buffer } from "buffer/"; import { Authorization } from "ox"; import { + ALLOWED_DELEGATORS, EIP5792_ERROR_UNSUPPORTED_CAPABILITY, RPC_ERROR_INVALID_PARAMS, RPC_ERROR_UNRECOGNIZED_CHAIN, @@ -124,6 +126,59 @@ export function buildDummyAuthorizationList( ]; } +// ── Atomic Batch TX Assembly ────────────────────────────── + +/** + * EIP-5792 calls 배열을 StatelessDeleGator의 execute(bytes32,bytes) calldata로 변환. + * ox/erc7821 Execute.encodeData를 사용하여 ERC-7579 표준 준수를 보장. + * nonce/gas/fee는 포함하지 않음 — fillUnsignedEVMTx가 담당. + */ +export function createAtomicBatchTransaction( + calls: EIP5792Call[], + selectedAddress: string, + delegationStatus: DelegationStatus, + evmChainId: number +): UnsignedEVMTransaction { + const data = Execute.encodeData( + calls.map((call) => ({ + to: (call.to ?? EVM_ZERO_ADDRESS) as `0x${string}`, + value: BigInt(call.value ?? "0x0"), + data: (call.data ?? "0x") as `0x${string}`, + })) + ); + + const tx: UnsignedEVMTransaction = { + to: selectedAddress, + data, + value: "0x0", + chainId: evmChainId, + }; + + if (delegationStatus === "ready") { + tx.authorizationList = buildDummyAuthorizationList( + ALLOWED_DELEGATORS[0], + evmChainId + ); + } + + return tx; +} + +/** + * 미업그레이드 계정의 가스 추정을 위한 stateOverride 생성. + * eth_estimateGas 3번째 파라미터로 전달하여 delegation 코드를 주입. + */ +export function buildDelegationStateOverride( + address: string, + delegatorAddress: string +): Record { + return { + [address]: { + code: EIP7702_DELEGATION_PREFIX + delegatorAddress.toLowerCase().slice(2), + }, + }; +} + // ── Types ── export interface EVMTransactionParam { diff --git a/packages/background/src/keyring-ethereum/index.ts b/packages/background/src/keyring-ethereum/index.ts index c0e3380a87..c090bdcb61 100644 --- a/packages/background/src/keyring-ethereum/index.ts +++ b/packages/background/src/keyring-ethereum/index.ts @@ -1,3 +1,8 @@ export * from "./messages"; export * from "./service"; export { ALLOWED_DELEGATORS } from "./constants"; +export { + buildDummyAuthorizationList, + createAtomicBatchTransaction, + buildDelegationStateOverride, +} from "./helper"; diff --git a/packages/background/src/keyring-ethereum/service.ts b/packages/background/src/keyring-ethereum/service.ts index 42a9548e99..29e4396c4a 100644 --- a/packages/background/src/keyring-ethereum/service.ts +++ b/packages/background/src/keyring-ethereum/service.ts @@ -5,6 +5,7 @@ import { AnalyticsService } from "../analytics"; import { Env, EthereumProviderRpcError } from "@keplr-wallet/router"; import { BatchSigningData, + BatchStrategy, DelegationStatus, EthereumBatchSignResponse, EthereumSignResponse, @@ -48,8 +49,8 @@ import { } from "./helper"; import { PermissionInteractiveService } from "../permission-interactive"; import { - DEFAULT_EVM_CHAIN_ID, ALLOWED_DELEGATORS, + DEFAULT_EVM_CHAIN_ID, EIP5792_ERROR_ATOMICITY_NOT_SUPPORTED, EIP5792_ERROR_UNKNOWN_BUNDLE, ERC20_TRANSFER_SELECTOR, @@ -66,10 +67,16 @@ export class KeyRingEthereumService { private readonly wsManager: EvmWebSocketManager; private readonly chainHandler: EvmChainHandler; - // EIP-5792: batchId → txHash mapping (in-memory, no persistence needed) + // EIP-5792: batchId → tx metadata mapping (in-memory, no persistence needed) private readonly batchCallsMap = new Map< string, - { txHash: string; chainId: string } + { + txHash: string; + chainId: string; + origin: string; + strategy: BatchStrategy; + apiVersion: string; + } >(); constructor( @@ -208,41 +215,6 @@ export class KeyRingEthereumService { ); } - async estimateSmartAccountFee( - _env: Env, - chainId: string, - vaultId: string - ): Promise<{ - gasLimit: string; - maxFeePerGas: string; - maxPriorityFeePerGas: string; - estimatedFeeWei: string; - }> { - const evmInfo = this.chainsService.getEVMInfoOrThrow(chainId); - const address = await this.getEthAddressForVault(chainId, vaultId); - - const filled = await fillUnsignedEVMTx("", evmInfo, address, { - to: address, - value: "0x0", - data: "0x", - authorizationList: buildDummyAuthorizationList( - ALLOWED_DELEGATORS[0], - evmInfo.chainId - ), - }); - - const gasLimit = `0x${BigInt(filled.gasLimit ?? 0).toString(16)}`; - const maxFeePerGas = `0x${BigInt(filled.maxFeePerGas ?? 0).toString(16)}`; - const maxPriorityFeePerGas = `0x${BigInt( - filled.maxPriorityFeePerGas ?? 0 - ).toString(16)}`; - - const estimatedFeeWei = - "0x" + (BigInt(gasLimit) * BigInt(maxFeePerGas)).toString(16); - - return { gasLimit, maxFeePerGas, maxPriorityFeePerGas, estimatedFeeWei }; - } - private async sendEip7702Tx( env: Env, chainId: string, @@ -1464,7 +1436,13 @@ export class KeyRingEthereumService { {} ); - this.batchCallsMap.set(batchId, { txHash, chainId: currentChainId }); + this.batchCallsMap.set(batchId, { + txHash, + chainId: currentChainId, + origin, + strategy: batchResponse.strategy, + apiVersion: request.version || "1.0", + }); } catch (error) { console.error(`Batch broadcast failed for ${batchId}:`, error); rethrowAsProviderError(error); @@ -1491,7 +1469,7 @@ export class KeyRingEthereumService { } const entry = this.batchCallsMap.get(batchId); - if (!entry) { + if (!entry || entry.origin !== origin) { throw new EthereumProviderRpcError( EIP5792_ERROR_UNKNOWN_BUNDLE, "Unknown bundle ID" @@ -1523,11 +1501,11 @@ export class KeyRingEthereumService { } return { - version: "1.0", + version: entry.apiVersion, chainId: `0x${evmInfo.chainId.toString(16)}`, id: batchId, status, - atomic: true, + atomic: entry.strategy === "atomic", receipts, }; } From 3672133589cfa166958a0616e26fa14430cd1126 Mon Sep 17 00:00:00 2001 From: jungcome7 Date: Mon, 6 Apr 2026 15:39:37 +0900 Subject: [PATCH 59/64] refactor(smart-account): migrate fee estimation from background msg to store Remove EstimateSmartAccountFeeMsg and use stores-eth simulateGas + hooks-evm computeEIP1559TxFees for reactive fee estimation. This aligns with the regular EVM sign page pattern and enables automatic fee updates when network conditions change. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../wallet/smart-account/confirm/confirm.tsx | 2 +- .../confirm/hooks/use-smart-account-fee.ts | 89 +++++++++++++------ .../src/keyring-ethereum/handler.ts | 7 -- .../background/src/keyring-ethereum/init.ts | 2 - .../src/keyring-ethereum/messages.ts | 39 -------- 5 files changed, 64 insertions(+), 75 deletions(-) diff --git a/apps/extension/src/pages/wallet/smart-account/confirm/confirm.tsx b/apps/extension/src/pages/wallet/smart-account/confirm/confirm.tsx index 3d406547c4..f2e73a04e7 100644 --- a/apps/extension/src/pages/wallet/smart-account/confirm/confirm.tsx +++ b/apps/extension/src/pages/wallet/smart-account/confirm/confirm.tsx @@ -69,7 +69,7 @@ export const SmartAccountConfirmPage: FunctionComponent = observer(() => { insufficientBalance, balanceLoaded, retry, - } = useSmartAccountFee(chainId, vaultId, hexAddress, isValid); + } = useSmartAccountFee(chainId, hexAddress, isValid); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); diff --git a/apps/extension/src/pages/wallet/smart-account/confirm/hooks/use-smart-account-fee.ts b/apps/extension/src/pages/wallet/smart-account/confirm/hooks/use-smart-account-fee.ts index 3ac955bd2e..f6b763ada7 100644 --- a/apps/extension/src/pages/wallet/smart-account/confirm/hooks/use-smart-account-fee.ts +++ b/apps/extension/src/pages/wallet/smart-account/confirm/hooks/use-smart-account-fee.ts @@ -1,23 +1,25 @@ import { useCallback, useEffect, useMemo, useState } from "react"; -import { EstimateSmartAccountFeeMsg } from "@keplr-wallet/background"; -import { InExtensionMessageRequester } from "@keplr-wallet/router-extension"; -import { BACKGROUND_PORT } from "@keplr-wallet/router"; -import { CoinPretty, Int } from "@keplr-wallet/unit"; +import { + ALLOWED_DELEGATORS, + buildDummyAuthorizationList, +} from "@keplr-wallet/background"; +import { computeEIP1559TxFees } from "@keplr-wallet/hooks-evm"; +import { CoinPretty, Dec, Int } from "@keplr-wallet/unit"; import { useStore } from "../../../../../stores"; import { formatFee } from "../../utils"; type FeeState = | { status: "loading" } - | { status: "success"; estimatedFeeWei: string } + | { status: "success"; gasUsed: number } | { status: "error" }; export function useSmartAccountFee( chainId: string, - vaultId: string, hexAddress: string, isValid: boolean ) { - const { chainStore, queriesStore, priceStore } = useStore(); + const { chainStore, queriesStore, priceStore, ethereumAccountStore } = + useStore(); const nativeCurrency = useMemo(() => { try { @@ -30,46 +32,81 @@ export function useSmartAccountFee( } }, [chainStore, chainId]); + const evmChainId = useMemo(() => { + try { + const unwrapped = chainStore.getModularChain(chainId).unwrapped; + if (unwrapped.type === "evm") return unwrapped.evm.chainId; + if (unwrapped.type === "ethermint") return unwrapped.evm.chainId; + return 0; + } catch { + return 0; + } + }, [chainStore, chainId]); + const nativeSymbol = nativeCurrency?.coinDenom ?? "ETH"; const [feeState, setFeeState] = useState({ status: "loading" }); const [retryCount, setRetryCount] = useState(0); useEffect(() => { - if (!isValid) return; + if (!isValid || !hexAddress || !evmChainId) return; let cancelled = false; setFeeState({ status: "loading" }); - new InExtensionMessageRequester() - .sendMessage( - BACKGROUND_PORT, - new EstimateSmartAccountFeeMsg(chainId, vaultId) - ) + + const ethereumAccount = ethereumAccountStore.getAccount(chainId); + + ethereumAccount + .simulateGas(hexAddress, { + to: hexAddress, + value: "0x0", + data: "0x", + authorizationList: buildDummyAuthorizationList( + ALLOWED_DELEGATORS[0], + evmChainId + ), + }) .then((result) => { - if (!cancelled) - setFeeState({ - status: "success", - estimatedFeeWei: result.estimatedFeeWei, - }); + if (!cancelled) { + setFeeState({ status: "success", gasUsed: result.gasUsed }); + } }) .catch(() => { if (!cancelled) setFeeState({ status: "error" }); }); + return () => { cancelled = true; }; - }, [chainId, vaultId, isValid, retryCount]); + }, [ + chainId, + hexAddress, + evmChainId, + isValid, + retryCount, + ethereumAccountStore, + ]); + + // Fee from reactive queries (feeHistory percentile + baseFee margin, fillUnsignedEVMTx와 동일 로직) + const ethereumQueries = queriesStore.get(chainId).ethereum; + const txFees = ethereumQueries + ? computeEIP1559TxFees(ethereumQueries, "average", chainId) + : undefined; + + const estimatedFeeWei = useMemo(() => { + if (feeState.status !== "success") return null; + const feePerGas = txFees?.maxFeePerGas ?? txFees?.gasPrice; + if (!feePerGas || feePerGas.isZero()) return null; + const gasLimit = Math.ceil(feeState.gasUsed * 1.3); + const fee = feePerGas.mul(new Dec(gasLimit)).truncate(); + return "0x" + BigInt(fee.toString()).toString(16); + }, [feeState, txFees]); - const estimatedFeeWei = - feeState.status === "success" ? feeState.estimatedFeeWei : null; const isEstimating = feeState.status === "loading"; const isFailed = feeState.status === "error"; const feeDisplay = useMemo( - () => - feeState.status === "success" - ? formatFee(feeState.estimatedFeeWei, nativeSymbol) - : null, - [feeState, nativeSymbol] + () => (estimatedFeeWei ? formatFee(estimatedFeeWei, nativeSymbol) : null), + [estimatedFeeWei, nativeSymbol] ); const feeUsd = useMemo(() => { diff --git a/packages/background/src/keyring-ethereum/handler.ts b/packages/background/src/keyring-ethereum/handler.ts index e0dd0c791f..37e1fd80b8 100644 --- a/packages/background/src/keyring-ethereum/handler.ts +++ b/packages/background/src/keyring-ethereum/handler.ts @@ -12,7 +12,6 @@ import { CheckNeedEnableAccessForEVMMsg, UpgradeSmartAccountMsg, DowngradeSmartAccountMsg, - EstimateSmartAccountFeeMsg, GetEthereumAddressForVaultMsg, } from "./messages"; import { KeyRingEthereumService } from "./service"; @@ -59,12 +58,6 @@ export const getHandler: ( (msg as DowngradeSmartAccountMsg).chainId, (msg as DowngradeSmartAccountMsg).vaultId ); - case EstimateSmartAccountFeeMsg: - return service.estimateSmartAccountFee( - env, - (msg as EstimateSmartAccountFeeMsg).chainId, - (msg as EstimateSmartAccountFeeMsg).vaultId - ); case GetEthereumAddressForVaultMsg: return service.getEthAddressForVault( (msg as GetEthereumAddressForVaultMsg).chainId, diff --git a/packages/background/src/keyring-ethereum/init.ts b/packages/background/src/keyring-ethereum/init.ts index 927ee3b3ad..adbbd44ab0 100644 --- a/packages/background/src/keyring-ethereum/init.ts +++ b/packages/background/src/keyring-ethereum/init.ts @@ -7,7 +7,6 @@ import { CheckNeedEnableAccessForEVMMsg, UpgradeSmartAccountMsg, DowngradeSmartAccountMsg, - EstimateSmartAccountFeeMsg, GetEthereumAddressForVaultMsg, } from "./messages"; import { ROUTE } from "./constants"; @@ -25,7 +24,6 @@ export function init( router.registerMessage(CheckNeedEnableAccessForEVMMsg); router.registerMessage(UpgradeSmartAccountMsg); router.registerMessage(DowngradeSmartAccountMsg); - router.registerMessage(EstimateSmartAccountFeeMsg); router.registerMessage(GetEthereumAddressForVaultMsg); router.addHandler(ROUTE, getHandler(service, permissionInteractionService)); diff --git a/packages/background/src/keyring-ethereum/messages.ts b/packages/background/src/keyring-ethereum/messages.ts index 92d73b3ac8..f1b0d73d3a 100644 --- a/packages/background/src/keyring-ethereum/messages.ts +++ b/packages/background/src/keyring-ethereum/messages.ts @@ -179,45 +179,6 @@ export class DowngradeSmartAccountMsg extends Message { } } -export class EstimateSmartAccountFeeMsg extends Message<{ - gasLimit: string; - maxFeePerGas: string; - maxPriorityFeePerGas: string; - estimatedFeeWei: string; -}> { - public static type() { - return "estimate-smart-account-fee"; - } - - constructor( - public readonly chainId: string, - public readonly vaultId: string - ) { - super(); - } - - validateBasic(): void { - if (!this.chainId) { - throw new Error("chain id not set"); - } - if (!this.vaultId) { - throw new Error("vault id not set"); - } - } - - override approveExternal(): boolean { - return false; - } - - route(): string { - return ROUTE; - } - - type(): string { - return EstimateSmartAccountFeeMsg.type(); - } -} - export class GetEthereumAddressForVaultMsg extends Message { public static type() { return "get-ethereum-address-for-vault"; From 416f3b1102d2c4af0b326dac56715cbf2d272614 Mon Sep 17 00:00:00 2001 From: jungcome7 Date: Mon, 6 Apr 2026 15:57:47 +0900 Subject: [PATCH 60/64] fix(smart-account): reject contract deploy calls in batch execution Contract deployment (missing `to`) is not supported in execute() batch since it uses CALL opcode, not CREATE. Explicitly reject instead of silently defaulting to zero address. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../background/src/keyring-ethereum/helper.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/background/src/keyring-ethereum/helper.ts b/packages/background/src/keyring-ethereum/helper.ts index 8114eb2e35..f295479a43 100644 --- a/packages/background/src/keyring-ethereum/helper.ts +++ b/packages/background/src/keyring-ethereum/helper.ts @@ -140,11 +140,18 @@ export function createAtomicBatchTransaction( evmChainId: number ): UnsignedEVMTransaction { const data = Execute.encodeData( - calls.map((call) => ({ - to: (call.to ?? EVM_ZERO_ADDRESS) as `0x${string}`, - value: BigInt(call.value ?? "0x0"), - data: (call.data ?? "0x") as `0x${string}`, - })) + calls.map((call, i) => { + if (!call.to) { + throw new Error( + `Call at index ${i}: contract deployment is not supported in batch execution` + ); + } + return { + to: call.to as `0x${string}`, + value: BigInt(call.value ?? "0x0"), + data: (call.data ?? "0x") as `0x${string}`, + }; + }) ); const tx: UnsignedEVMTransaction = { From c8c4c0ba7fe0f9cff1c2c20db637a3770c887c95 Mon Sep 17 00:00:00 2001 From: jungcome7 Date: Mon, 6 Apr 2026 16:18:45 +0900 Subject: [PATCH 61/64] refactor(stores-eth): move batch TX utilities from background to stores-eth createAtomicBatchTransaction and buildDelegationStateOverride are UI-only utilities (not used in background). Move to stores-eth where other EVM utilities like serializeEVMTransaction live. Decouple from background-specific dependencies (ALLOWED_DELEGATORS, buildDummyAuthorizationList) by accepting authorizationList as param. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../background/src/keyring-ethereum/helper.ts | 62 ------------------- .../background/src/keyring-ethereum/index.ts | 6 +- packages/stores-eth/src/batch.ts | 51 +++++++++++++++ packages/stores-eth/src/index.ts | 1 + 4 files changed, 53 insertions(+), 67 deletions(-) create mode 100644 packages/stores-eth/src/batch.ts diff --git a/packages/background/src/keyring-ethereum/helper.ts b/packages/background/src/keyring-ethereum/helper.ts index f295479a43..5e93c022fc 100644 --- a/packages/background/src/keyring-ethereum/helper.ts +++ b/packages/background/src/keyring-ethereum/helper.ts @@ -7,12 +7,10 @@ import { WalletSendCallsRequest, } from "@keplr-wallet/types"; import { Address, Hex } from "ox"; -import { Execute } from "ox/erc7821"; import { Buffer } from "buffer/"; import { Authorization } from "ox"; import { - ALLOWED_DELEGATORS, EIP5792_ERROR_UNSUPPORTED_CAPABILITY, RPC_ERROR_INVALID_PARAMS, RPC_ERROR_UNRECOGNIZED_CHAIN, @@ -126,66 +124,6 @@ export function buildDummyAuthorizationList( ]; } -// ── Atomic Batch TX Assembly ────────────────────────────── - -/** - * EIP-5792 calls 배열을 StatelessDeleGator의 execute(bytes32,bytes) calldata로 변환. - * ox/erc7821 Execute.encodeData를 사용하여 ERC-7579 표준 준수를 보장. - * nonce/gas/fee는 포함하지 않음 — fillUnsignedEVMTx가 담당. - */ -export function createAtomicBatchTransaction( - calls: EIP5792Call[], - selectedAddress: string, - delegationStatus: DelegationStatus, - evmChainId: number -): UnsignedEVMTransaction { - const data = Execute.encodeData( - calls.map((call, i) => { - if (!call.to) { - throw new Error( - `Call at index ${i}: contract deployment is not supported in batch execution` - ); - } - return { - to: call.to as `0x${string}`, - value: BigInt(call.value ?? "0x0"), - data: (call.data ?? "0x") as `0x${string}`, - }; - }) - ); - - const tx: UnsignedEVMTransaction = { - to: selectedAddress, - data, - value: "0x0", - chainId: evmChainId, - }; - - if (delegationStatus === "ready") { - tx.authorizationList = buildDummyAuthorizationList( - ALLOWED_DELEGATORS[0], - evmChainId - ); - } - - return tx; -} - -/** - * 미업그레이드 계정의 가스 추정을 위한 stateOverride 생성. - * eth_estimateGas 3번째 파라미터로 전달하여 delegation 코드를 주입. - */ -export function buildDelegationStateOverride( - address: string, - delegatorAddress: string -): Record { - return { - [address]: { - code: EIP7702_DELEGATION_PREFIX + delegatorAddress.toLowerCase().slice(2), - }, - }; -} - // ── Types ── export interface EVMTransactionParam { diff --git a/packages/background/src/keyring-ethereum/index.ts b/packages/background/src/keyring-ethereum/index.ts index c090bdcb61..78d99aea49 100644 --- a/packages/background/src/keyring-ethereum/index.ts +++ b/packages/background/src/keyring-ethereum/index.ts @@ -1,8 +1,4 @@ export * from "./messages"; export * from "./service"; export { ALLOWED_DELEGATORS } from "./constants"; -export { - buildDummyAuthorizationList, - createAtomicBatchTransaction, - buildDelegationStateOverride, -} from "./helper"; +export { buildDummyAuthorizationList } from "./helper"; diff --git a/packages/stores-eth/src/batch.ts b/packages/stores-eth/src/batch.ts new file mode 100644 index 0000000000..55bcf5f835 --- /dev/null +++ b/packages/stores-eth/src/batch.ts @@ -0,0 +1,51 @@ +import { Execute } from "ox/erc7821"; +import type { UnsignedEVMTransaction } from "./types"; + +const EIP7702_DELEGATION_PREFIX = "0xef0100"; + +/** + * EIP-5792 calls 배열을 StatelessDeleGator의 execute(bytes32,bytes) calldata로 변환. + * ox/erc7821 Execute.encodeData를 사용하여 ERC-7579 표준 준수를 보장. + */ +export function createAtomicBatchTransaction( + calls: { to?: string; data?: string; value?: string }[], + selectedAddress: string, + authorizationList?: UnsignedEVMTransaction["authorizationList"] +): UnsignedEVMTransaction { + const data = Execute.encodeData( + calls.map((call, i) => { + if (!call.to) { + throw new Error( + `Call at index ${i}: contract deployment is not supported in batch execution` + ); + } + return { + to: call.to as `0x${string}`, + value: BigInt(call.value ?? "0x0"), + data: (call.data ?? "0x") as `0x${string}`, + }; + }) + ); + + return { + to: selectedAddress, + data, + value: "0x0", + ...(authorizationList ? { authorizationList } : {}), + }; +} + +/** + * 미업그레이드 계정의 가스 추정을 위한 stateOverride 생성. + * eth_estimateGas 3번째 파라미터로 전달하여 delegation 코드를 주입. + */ +export function buildDelegationStateOverride( + address: string, + delegatorAddress: string +): Record { + return { + [address]: { + code: EIP7702_DELEGATION_PREFIX + delegatorAddress.toLowerCase().slice(2), + }, + }; +} diff --git a/packages/stores-eth/src/index.ts b/packages/stores-eth/src/index.ts index 9f41b56298..553512ecb0 100644 --- a/packages/stores-eth/src/index.ts +++ b/packages/stores-eth/src/index.ts @@ -4,3 +4,4 @@ export * from "./constants"; export * from "./currency-registrar"; export * from "./types"; export * from "./serialize"; +export * from "./batch"; From cb42016ad3a1c654c6efba08b628296cf08d67a6 Mon Sep 17 00:00:00 2001 From: jungcome7 Date: Mon, 6 Apr 2026 16:32:48 +0900 Subject: [PATCH 62/64] fix(smart-account): disable approve when fee data is unavailable Add !feeDisplay to approveDisabled guard to prevent approval when gas simulation succeeded but fee queries have not resolved yet. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/pages/wallet/smart-account/confirm/confirm.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/extension/src/pages/wallet/smart-account/confirm/confirm.tsx b/apps/extension/src/pages/wallet/smart-account/confirm/confirm.tsx index f2e73a04e7..224c6aa18d 100644 --- a/apps/extension/src/pages/wallet/smart-account/confirm/confirm.tsx +++ b/apps/extension/src/pages/wallet/smart-account/confirm/confirm.tsx @@ -109,7 +109,8 @@ export const SmartAccountConfirmPage: FunctionComponent = observer(() => { insufficientBalance || isFailed || !balanceLoaded || - isEstimating; + isEstimating || + !feeDisplay; return ( Date: Tue, 7 Apr 2026 13:21:24 +0900 Subject: [PATCH 63/64] fix: remove unnecessary try-catch for evmChainId lookup Let the error propagate so the error boundary catches it and shows the cache reset page, since a missing chainId is a critical failure. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../confirm/hooks/use-smart-account-fee.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/apps/extension/src/pages/wallet/smart-account/confirm/hooks/use-smart-account-fee.ts b/apps/extension/src/pages/wallet/smart-account/confirm/hooks/use-smart-account-fee.ts index f6b763ada7..99a9eeb6e8 100644 --- a/apps/extension/src/pages/wallet/smart-account/confirm/hooks/use-smart-account-fee.ts +++ b/apps/extension/src/pages/wallet/smart-account/confirm/hooks/use-smart-account-fee.ts @@ -33,14 +33,10 @@ export function useSmartAccountFee( }, [chainStore, chainId]); const evmChainId = useMemo(() => { - try { - const unwrapped = chainStore.getModularChain(chainId).unwrapped; - if (unwrapped.type === "evm") return unwrapped.evm.chainId; - if (unwrapped.type === "ethermint") return unwrapped.evm.chainId; - return 0; - } catch { - return 0; - } + const unwrapped = chainStore.getModularChain(chainId).unwrapped; + if (unwrapped.type === "evm") return unwrapped.evm.chainId; + if (unwrapped.type === "ethermint") return unwrapped.evm.chainId; + return 0; }, [chainStore, chainId]); const nativeSymbol = nativeCurrency?.coinDenom ?? "ETH"; From 395bbcb0fe3eb84b68f62882ca85130972bb022a Mon Sep 17 00:00:00 2001 From: jungcome7 Date: Tue, 7 Apr 2026 13:50:03 +0900 Subject: [PATCH 64/64] fix: add OP Stack L1 data fee calculation for smart account Simulate L1 data fee via simulateOpStackL1Fee for chains with "op-stack-l1-data-fee" feature and include it in the total estimated fee. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../confirm/hooks/use-smart-account-fee.ts | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/apps/extension/src/pages/wallet/smart-account/confirm/hooks/use-smart-account-fee.ts b/apps/extension/src/pages/wallet/smart-account/confirm/hooks/use-smart-account-fee.ts index 99a9eeb6e8..470571fedf 100644 --- a/apps/extension/src/pages/wallet/smart-account/confirm/hooks/use-smart-account-fee.ts +++ b/apps/extension/src/pages/wallet/smart-account/confirm/hooks/use-smart-account-fee.ts @@ -10,7 +10,7 @@ import { formatFee } from "../../utils"; type FeeState = | { status: "loading" } - | { status: "success"; gasUsed: number } + | { status: "success"; gasUsed: number; l1DataFee?: Dec } | { status: "error" }; export function useSmartAccountFee( @@ -51,6 +51,9 @@ export function useSmartAccountFee( const ethereumAccount = ethereumAccountStore.getAccount(chainId); + const modularChainInfo = chainStore.getModularChain(chainId); + const hasOpStackFee = modularChainInfo.hasFeature("op-stack-l1-data-fee"); + ethereumAccount .simulateGas(hexAddress, { to: hexAddress, @@ -61,9 +64,27 @@ export function useSmartAccountFee( evmChainId ), }) - .then((result) => { + .then(async (result) => { + if (cancelled) return; + + let l1DataFee: Dec | undefined; + if (hasOpStackFee) { + const gasLimit = Math.ceil(result.gasUsed * 1.3); + const l1Fee = await ethereumAccount.simulateOpStackL1Fee({ + to: hexAddress, + value: "0x0", + data: "0x", + gasLimit, + }); + l1DataFee = new Dec(BigInt(l1Fee)); + } + if (!cancelled) { - setFeeState({ status: "success", gasUsed: result.gasUsed }); + setFeeState({ + status: "success", + gasUsed: result.gasUsed, + l1DataFee, + }); } }) .catch(() => { @@ -75,6 +96,7 @@ export function useSmartAccountFee( }; }, [ chainId, + chainStore, hexAddress, evmChainId, isValid, @@ -93,7 +115,10 @@ export function useSmartAccountFee( const feePerGas = txFees?.maxFeePerGas ?? txFees?.gasPrice; if (!feePerGas || feePerGas.isZero()) return null; const gasLimit = Math.ceil(feeState.gasUsed * 1.3); - const fee = feePerGas.mul(new Dec(gasLimit)).truncate(); + let fee = feePerGas.mul(new Dec(gasLimit)).truncate(); + if (feeState.l1DataFee) { + fee = fee.add(feeState.l1DataFee.truncate()); + } return "0x" + BigInt(fee.toString()).toString(16); }, [feeState, txFees]);