From 10474b227a123d1260a0fd3eeff7574b0224d09b Mon Sep 17 00:00:00 2001 From: tate Date: Fri, 9 Aug 2024 11:45:48 +1000 Subject: [PATCH 01/12] fix: detect desynced expiry + show banner --- package.json | 6 +- pnpm-lock.yaml | 104 +++++++++++++---- public/locales/en/common.json | 4 +- public/locales/en/profile.json | 5 + .../ExpandableSection/ExpandableSection.tsx | 12 +- .../AddRecordButton/AddRecordButton.tsx | 14 +-- .../@molecules/NameExpiryDesyncBanner.tsx | 40 +++++++ .../@molecules/SearchInput/SearchInput.tsx | 18 +-- src/components/Header.tsx | 10 +- .../pages/profile/[name]/Profile.tsx | 15 +-- .../ExpirySection/hooks/useExpiryDetails.ts | 2 + src/hooks/ensjs/public/useOwner.ts | 43 ++++++- src/hooks/nameType/getNameType.ts | 2 + src/hooks/ownership/useRoles/useRoles.ts | 2 - .../ownership/useRoles/utils/getRoles.ts | 8 +- src/hooks/useBasicName.ts | 37 +----- src/hooks/useNameDetails.tsx | 108 +++++++++++------- src/layouts/Content.tsx | 14 ++- .../input/SendName/utils/checkCanSend.ts | 2 + .../SyncManager/utils/checkCanSyncManager.ts | 2 + src/transaction-flow/transaction/index.ts | 2 + .../transaction/syncWrappedExpiry.ts | 38 ++++++ 22 files changed, 335 insertions(+), 153 deletions(-) create mode 100644 src/components/@molecules/NameExpiryDesyncBanner.tsx create mode 100644 src/transaction-flow/transaction/syncWrappedExpiry.ts diff --git a/package.json b/package.json index 2bbfac934..cf91a174c 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "@ensdomains/content-hash": "^3.0.0-beta.5", "@ensdomains/ens-contracts": "1.2.0-beta.0", "@ensdomains/ensjs": "4.0.0", - "@ensdomains/thorin": "0.6.50", + "@ensdomains/thorin": "0.0.0-feat-banner-action-button.20240809T010925661", "@metamask/mobile-provider": "^2.1.0", "@metamask/post-message-stream": "^6.1.2", "@metamask/providers": "^14.0.2", @@ -89,11 +89,11 @@ "react-hook-form": "7.51.0", "react-i18next": "^11.18.5", "react-is": "^17.0.2", - "react-transition-state": "^1.1.5", + "react-transition-state": "2.1.1", "react-use": "^17.4.0", "react-use-error-boundary": "^3.0.0", "react-use-intercom": "^5.1.4", - "styled-components": "^5.3.5", + "styled-components": "5.3.6", "ts-pattern": "^4.2.2", "use-immer": "^0.7.0", "viem": "2.19.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6643d884a..abf7bb09e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -39,8 +39,8 @@ importers: specifier: 4.0.0 version: 4.0.0(encoding@0.1.13)(typescript@5.4.5)(viem@2.19.0(bufferutil@4.0.7)(typescript@5.4.5)(utf-8-validate@6.0.3)(zod@3.23.8))(zod@3.23.8) '@ensdomains/thorin': - specifier: 0.6.50 - version: 0.6.50(react-dom@18.3.1(react@18.3.1))(react-transition-state@1.1.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(styled-components@5.3.11(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react-is@17.0.2)(react@18.3.1)) + specifier: 0.0.0-feat-banner-action-button.20240809T010925661 + version: 0.0.0-feat-banner-action-button.20240809T010925661(react-dom@18.3.1(react@18.3.1))(react-transition-state@2.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(styled-components@5.3.6(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react-is@17.0.2)(react@18.3.1)) '@metamask/mobile-provider': specifier: ^2.1.0 version: 2.1.0 @@ -150,8 +150,8 @@ importers: specifier: ^17.0.2 version: 17.0.2 react-transition-state: - specifier: ^1.1.5 - version: 1.1.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: 2.1.1 + version: 2.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-use: specifier: ^17.4.0 version: 17.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -162,8 +162,8 @@ importers: specifier: ^5.1.4 version: 5.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) styled-components: - specifier: ^5.3.5 - version: 5.3.11(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react-is@17.0.2)(react@18.3.1) + specifier: 5.3.6 + version: 5.3.6(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react-is@17.0.2)(react@18.3.1) ts-pattern: specifier: ^4.2.2 version: 4.3.0 @@ -215,7 +215,7 @@ importers: version: 1.44.1 '@testing-library/jest-dom': specifier: ^6.4.2 - version: 6.4.5(vitest@1.6.0(@types/node@18.19.33)(jsdom@24.1.0(bufferutil@4.0.7)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@6.0.3))(terser@5.31.0)) + version: 6.4.5(@types/jest@29.5.12)(vitest@1.6.0(@types/node@18.19.33)(jsdom@24.1.0(bufferutil@4.0.7)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@6.0.3))(terser@5.31.0)) '@testing-library/react': specifier: ^14.0.0 version: 14.3.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -1340,13 +1340,13 @@ packages: '@ensdomains/solsha1@0.0.3': resolution: {integrity: sha512-uhuG5LzRt/UJC0Ux83cE2rCKwSleRePoYdQVcqPN1wyf3/ekMzT/KZUF9+v7/AG5w9jlMLCQkUM50vfjr0Yu9Q==} - '@ensdomains/thorin@0.6.50': - resolution: {integrity: sha512-UA1Blyz1h/Yy9e2h8ykNwyIfFMa85+fM5viS1Jra7Ms/EbgLhiP/BEMNr1/oZvwLHE54UM/rZGij1GaxurrOoQ==} + '@ensdomains/thorin@0.0.0-feat-banner-action-button.20240809T010925661': + resolution: {integrity: sha512-/gw83sHi/40xNzzf0fsPoM0KMayYQP8Xm3ErGnFUSAdJwLt70M1tyO/yIUVvdHKU6yBBmpXC1W2mANxxDyMq2w==} peerDependencies: react: ^18.2.0 react-dom: ^18.2.0 - react-transition-state: ^1.1.4 - styled-components: ^5.3.3 + react-transition-state: ^2.1.1 + styled-components: ^5.3.6 '@esbuild-plugins/node-globals-polyfill@0.2.3': resolution: {integrity: sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==} @@ -1815,6 +1815,10 @@ packages: resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/expect-utils@29.7.0': + resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/fake-timers@29.7.0': resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -3047,6 +3051,9 @@ packages: '@types/istanbul-reports@3.0.4': resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + '@types/jest@29.5.12': + resolution: {integrity: sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==} + '@types/js-cookie@2.2.7': resolution: {integrity: sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA==} @@ -5249,6 +5256,10 @@ packages: resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} engines: {node: '>=6'} + expect@29.7.0: + resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + express@4.19.2: resolution: {integrity: sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==} engines: {node: '>= 0.10.0'} @@ -6304,6 +6315,10 @@ packages: jest-canvas-mock@2.5.2: resolution: {integrity: sha512-vgnpPupjOL6+L5oJXzxTxFrlGEIbHdZqFU+LFNdtLxZ3lRDCl17FlTMM7IatoRQkrcyOTMlDinjUguqmQ6bR2A==} + jest-diff@29.7.0: + resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-environment-node@29.7.0: resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -6312,6 +6327,10 @@ packages: resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-matcher-utils@29.7.0: + resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-message-util@29.7.0: resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -8008,8 +8027,8 @@ packages: '@types/react': optional: true - react-transition-state@1.1.5: - resolution: {integrity: sha512-ITY2mZqc2dWG2eitJkYNdcSFW8aKeOlkL2A/vowRrLL8GH3J6Re/SpD/BLvQzrVOTqjsP0b5S9N10vgNNzwMUQ==} + react-transition-state@2.1.1: + resolution: {integrity: sha512-kQx5g1FVu9knoz1T1WkapjUgFz08qQ/g1OmuWGi3/AoEFfS0kStxrPlZx81urjCXdz2d+1DqLpU6TyLW/Ro04Q==} peerDependencies: react: ^18.2.0 react-dom: ^18.2.0 @@ -8758,8 +8777,8 @@ packages: style-search@0.1.0: resolution: {integrity: sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==} - styled-components@5.3.11: - resolution: {integrity: sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==} + styled-components@5.3.6: + resolution: {integrity: sha512-hGTZquGAaTqhGWldX7hhfzjnIYBZ0IXQXkCYdvF1Sq3DsUaLx6+NTHC5Jj1ooM2F68sBiVz3lvhfwQs/S3l6qg==} engines: {node: '>=10'} peerDependencies: react: ^18.2.0 @@ -11222,15 +11241,15 @@ snapshots: dependencies: hash-test-vectors: 1.3.2 - '@ensdomains/thorin@0.6.50(react-dom@18.3.1(react@18.3.1))(react-transition-state@1.1.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(styled-components@5.3.11(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react-is@17.0.2)(react@18.3.1))': + '@ensdomains/thorin@0.0.0-feat-banner-action-button.20240809T010925661(react-dom@18.3.1(react@18.3.1))(react-transition-state@2.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)(styled-components@5.3.6(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react-is@17.0.2)(react@18.3.1))': dependencies: clsx: 1.2.1 focus-visible: 5.2.0 lodash: 4.17.21 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-transition-state: 1.1.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - styled-components: 5.3.11(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react-is@17.0.2)(react@18.3.1) + react-transition-state: 2.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + styled-components: 5.3.6(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react-is@17.0.2)(react@18.3.1) ts-pattern: 4.3.0 '@esbuild-plugins/node-globals-polyfill@0.2.3(esbuild@0.17.19)': @@ -11747,6 +11766,11 @@ snapshots: '@types/node': 18.19.33 jest-mock: 29.7.0 + '@jest/expect-utils@29.7.0': + dependencies: + jest-get-type: 29.6.3 + optional: true + '@jest/fake-timers@29.7.0': dependencies: '@jest/types': 29.6.3 @@ -13193,7 +13217,7 @@ snapshots: lz-string: 1.5.0 pretty-format: 27.5.1 - '@testing-library/jest-dom@6.4.5(vitest@1.6.0(@types/node@18.19.33)(jsdom@24.1.0(bufferutil@4.0.7)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@6.0.3))(terser@5.31.0))': + '@testing-library/jest-dom@6.4.5(@types/jest@29.5.12)(vitest@1.6.0(@types/node@18.19.33)(jsdom@24.1.0(bufferutil@4.0.7)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@6.0.3))(terser@5.31.0))': dependencies: '@adobe/css-tools': 4.3.3 '@babel/runtime': 7.24.6 @@ -13204,6 +13228,7 @@ snapshots: lodash: 4.17.21 redent: 3.0.0 optionalDependencies: + '@types/jest': 29.5.12 vitest: 1.6.0(@types/node@18.19.33)(jsdom@24.1.0(bufferutil@4.0.7)(canvas@2.11.2(encoding@0.1.13))(utf-8-validate@6.0.3))(terser@5.31.0) '@testing-library/react-hooks@8.0.1(@types/react@18.2.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': @@ -13418,6 +13443,12 @@ snapshots: dependencies: '@types/istanbul-lib-report': 3.0.3 + '@types/jest@29.5.12': + dependencies: + expect: 29.7.0 + pretty-format: 29.7.0 + optional: true + '@types/js-cookie@2.2.7': {} '@types/js-levenshtein@1.1.3': {} @@ -14628,14 +14659,14 @@ snapshots: transitivePeerDependencies: - supports-color - babel-plugin-styled-components@2.1.4(@babel/core@7.24.6)(styled-components@5.3.11(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react-is@17.0.2)(react@18.3.1)): + babel-plugin-styled-components@2.1.4(@babel/core@7.24.6)(styled-components@5.3.6(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react-is@17.0.2)(react@18.3.1)): dependencies: '@babel/helper-annotate-as-pure': 7.24.6 '@babel/helper-module-imports': 7.24.6 '@babel/plugin-syntax-jsx': 7.24.6(@babel/core@7.24.6) lodash: 4.17.21 picomatch: 2.3.1 - styled-components: 5.3.11(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react-is@17.0.2)(react@18.3.1) + styled-components: 5.3.6(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react-is@17.0.2)(react@18.3.1) transitivePeerDependencies: - '@babel/core' @@ -16476,6 +16507,15 @@ snapshots: exit-hook@2.2.1: {} + expect@29.7.0: + dependencies: + '@jest/expect-utils': 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + optional: true + express@4.19.2: dependencies: accepts: 1.3.8 @@ -17710,6 +17750,14 @@ snapshots: cssfontparser: 1.2.1 moo-color: 1.0.3 + jest-diff@29.7.0: + dependencies: + chalk: 4.1.2 + diff-sequences: 29.6.3 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + optional: true + jest-environment-node@29.7.0: dependencies: '@jest/environment': 29.7.0 @@ -17721,6 +17769,14 @@ snapshots: jest-get-type@29.6.3: {} + jest-matcher-utils@29.7.0: + dependencies: + chalk: 4.1.2 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + optional: true + jest-message-util@29.7.0: dependencies: '@babel/code-frame': 7.24.6 @@ -19660,7 +19716,7 @@ snapshots: optionalDependencies: '@types/react': 18.2.21 - react-transition-state@1.1.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + react-transition-state@2.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) @@ -20549,14 +20605,14 @@ snapshots: style-search@0.1.0: {} - styled-components@5.3.11(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react-is@17.0.2)(react@18.3.1): + styled-components@5.3.6(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react-is@17.0.2)(react@18.3.1): dependencies: '@babel/helper-module-imports': 7.24.6 '@babel/traverse': 7.24.6(supports-color@5.5.0) '@emotion/is-prop-valid': 1.2.2 '@emotion/stylis': 0.8.5 '@emotion/unitless': 0.7.5 - babel-plugin-styled-components: 2.1.4(@babel/core@7.24.6)(styled-components@5.3.11(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react-is@17.0.2)(react@18.3.1)) + babel-plugin-styled-components: 2.1.4(@babel/core@7.24.6)(styled-components@5.3.6(@babel/core@7.24.6)(react-dom@18.3.1(react@18.3.1))(react-is@17.0.2)(react@18.3.1)) css-to-react-native: 3.2.0 hoist-non-react-statics: 3.3.2 react: 18.3.1 diff --git a/public/locales/en/common.json b/public/locales/en/common.json index b00a83a10..46c459be9 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -218,7 +218,8 @@ "syncManager": "Sync manager", "updateProfileRecords": "Update profile", "resetProfile": "Reset profile", - "unwrapName": "Unwrap name" + "unwrapName": "Unwrap name", + "syncWrappedExpiry": "Repair name" }, "info": { "sendName": "Set the controller and registrant of the name", @@ -237,6 +238,7 @@ "burnFuses": "Burn the chosen permissions until name expiry", "commitName": "Start timer to register name", "approveNameWrapper": "Approve the NameWrapper to manage your names", + "syncWrappedExpiry": "Sync the wrapped name expiry", "fuses": { "revoke": "Revoke", "grant": "Grant", diff --git a/public/locales/en/profile.json b/public/locales/en/profile.json index fbe32cd47..18f33ebbf 100644 --- a/public/locales/en/profile.json +++ b/public/locales/en/profile.json @@ -16,6 +16,11 @@ "available": { "title": "{{name}} is available", "description": "This name expired on {{date}}. Click here to view the registration page." + }, + "expiryDesync": { + "title": "Name misconfigured", + "description": "A transaction is required to repair this name. Ownership data displayed may be inaccurate.", + "actionLabel": "Repair" } }, "tabs": { diff --git a/src/components/@atoms/ExpandableSection/ExpandableSection.tsx b/src/components/@atoms/ExpandableSection/ExpandableSection.tsx index 4721420a3..3417430da 100644 --- a/src/components/@atoms/ExpandableSection/ExpandableSection.tsx +++ b/src/components/@atoms/ExpandableSection/ExpandableSection.tsx @@ -1,5 +1,5 @@ import { PropsWithChildren, useRef, useState } from 'react' -import { TransitionState, useTransition } from 'react-transition-state' +import { useTransition, type TransitionStatus } from 'react-transition-state' import styled, { css } from 'styled-components' import { DownChevronSVG, Typography } from '@ensdomains/thorin' @@ -38,7 +38,7 @@ const IconWrapper = styled.div<{ $open: boolean }>( `, ) -const Body = styled.div<{ $state: TransitionState; $height: number }>( +const Body = styled.div<{ $state: TransitionStatus; $height: number }>( ({ theme, $state, $height }) => css` overflow: hidden; width: 100%; @@ -94,15 +94,15 @@ export const ExpandableSection = ({ title, children }: PropsWithChildren) timeout: 300, preEnter: true, preExit: true, - onChange: ({ state: _state }) => { - if (_state === 'preEnter' || _state === 'preExit') { + onStateChange: ({ current: newState }) => { + if (newState.status === 'preEnter' || newState.status === 'preExit') { const _heigth = ref.current?.getBoundingClientRect().height || 0 setHeight(_heigth) } }, }) - const open = ['entered', 'entering', 'preEnter'].includes(state) + const open = ['entered', 'entering', 'preEnter'].includes(state.status) return ( @@ -112,7 +112,7 @@ export const ExpandableSection = ({ title, children }: PropsWithChildren) - + {children} diff --git a/src/components/@molecules/AddRecordButton/AddRecordButton.tsx b/src/components/@molecules/AddRecordButton/AddRecordButton.tsx index 2ed6e8e60..1260c3f6d 100644 --- a/src/components/@molecules/AddRecordButton/AddRecordButton.tsx +++ b/src/components/@molecules/AddRecordButton/AddRecordButton.tsx @@ -1,6 +1,6 @@ import { ButtonHTMLAttributes, ReactNode, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' -import useTransition, { TransitionState } from 'react-transition-state' +import useTransition, { TransitionStatus } from 'react-transition-state' import styled, { css } from 'styled-components' import { Button, Input, MagnifyingGlassSimpleSVG, PlusSVG, Typography } from '@ensdomains/thorin' @@ -8,7 +8,7 @@ import { Button, Input, MagnifyingGlassSimpleSVG, PlusSVG, Typography } from '@e import UnsupportedSVG from '@app/assets/Unsupported.svg' import { formSafeKey } from '@app/utils/editor' -const Container = styled.div<{ $state: TransitionState }>( +const Container = styled.div<{ $state: TransitionStatus }>( ({ theme, $state }) => css` position: relative; border: 1px solid ${theme.colors.border}; @@ -43,7 +43,7 @@ const Container = styled.div<{ $state: TransitionState }>( `, ) -const ControlsContainer = styled.div<{ $state: TransitionState }>( +const ControlsContainer = styled.div<{ $state: TransitionStatus }>( ({ theme, $state }) => css` transition: all 0.3s ${theme.transitionTimingFunction.inOut}; top: 0; @@ -253,7 +253,7 @@ const SVGWrapper = styled.div( `, ) -const ButtonContainer = styled.div<{ $state: TransitionState }>( +const ButtonContainer = styled.div<{ $state: TransitionStatus }>( ({ theme, $state }) => css` transition: all 0.3s ${theme.transitionTimingFunction.inOut}; position: absolute; @@ -368,8 +368,8 @@ export const AddRecordButton = ({ } return ( - - + + {inputType === 'placeholder' ? ( @@ -427,7 +427,7 @@ export const AddRecordButton = ({ )} - + + ) : undefined + } + > + {t('description')} + + ) +} diff --git a/src/components/@molecules/SearchInput/SearchInput.tsx b/src/components/@molecules/SearchInput/SearchInput.tsx index f5ca41986..4cf4e3010 100644 --- a/src/components/@molecules/SearchInput/SearchInput.tsx +++ b/src/components/@molecules/SearchInput/SearchInput.tsx @@ -11,7 +11,7 @@ import { useState, } from 'react' import { TFunction, useTranslation } from 'react-i18next' -import useTransition, { TransitionState } from 'react-transition-state' +import useTransition, { type TransitionStatus } from 'react-transition-state' import styled, { css } from 'styled-components' import { Address, isAddress } from 'viem' import { useAccount, useChainId } from 'wagmi' @@ -63,7 +63,7 @@ const Container = styled.div<{ $size: 'medium' | 'extraLarge' }>( ) const SearchResultsContainer = styled.div<{ - $state: TransitionState + $state: TransitionStatus }>( ({ theme, $state }) => css` position: absolute; @@ -102,7 +102,7 @@ const SearchResultsContainer = styled.div<{ `, ) -const FloatingSearchContainer = styled.div<{ $state: TransitionState }>( +const FloatingSearchContainer = styled.div<{ $state: TransitionStatus }>( ({ theme, $state }) => css` width: 95%; @@ -143,7 +143,7 @@ const CancelButton = styled(Typography)( ) type MobileSearchInputProps = { - state: TransitionState + state: TransitionStatus toggle: (value: boolean) => void searchInputRef: RefObject SearchResultsElement: JSX.Element @@ -405,7 +405,7 @@ const useSelectionManager = ({ }: { inputVal: string setSelected: Dispatch> - state: TransitionState + state: TransitionStatus }) => { useEffect(() => { if (inputVal === '') { @@ -683,7 +683,7 @@ export const SearchInput = ({ size = 'extraLarge' }: { size?: 'medium' | 'extraL handleFocusOut, }) - useSelectionManager({ inputVal, setSelected, state }) + useSelectionManager({ inputVal, setSelected, state: state.status }) const setInput = (val: string) => { setInputVal(val) @@ -707,7 +707,7 @@ export const SearchInput = ({ size = 'extraLarge' }: { size?: 'medium' | 'extraL width: width === Infinity ? undefined : width, }} onMouseLeave={() => inputVal === '' && setSelected(-1)} - $state={state} + $state={state.status} data-testid="search-input-results" > {dropdownItems.map((searchItem, index) => ( @@ -732,7 +732,7 @@ export const SearchInput = ({ size = 'extraLarge' }: { size?: 'medium' | 'extraL return ( {SearchInputElement} - {state !== 'unmounted' && SearchResultsElement} + {state.status !== 'unmounted' && SearchResultsElement} ) } @@ -744,7 +744,7 @@ export const SearchInput = ({ size = 'extraLarge' }: { size?: 'medium' | 'extraL SearchInputElement, SearchResultsElement, searchInputRef, - state, + state: state.status, toggle, }} /> diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 43b564366..2fc1c74a6 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,6 +1,6 @@ import { useRouter } from 'next/router' import { ReactNode, useCallback, useEffect, useRef } from 'react' -import useTransition, { TransitionState } from 'react-transition-state' +import useTransition, { type TransitionStatus } from 'react-transition-state' import styled, { css, useTheme } from 'styled-components' import { useAccount } from 'wagmi' @@ -63,7 +63,7 @@ const NavContainer = styled.div( `, ) -const RouteContainer = styled.div<{ $state: TransitionState }>( +const RouteContainer = styled.div<{ $state: TransitionStatus }>( ({ theme, $state }) => css` display: flex; flex-direction: row; @@ -101,7 +101,7 @@ const RouteWrapper = styled.div( `, ) -const SearchWrapper = styled.div<{ $state: TransitionState }>( +const SearchWrapper = styled.div<{ $state: TransitionStatus }>( ({ theme, $state }) => css` width: ${theme.space.full}; max-width: ${theme.space['80']}; @@ -213,7 +213,7 @@ export const Header = () => { @@ -224,7 +224,7 @@ export const Header = () => { {RouteItems} diff --git a/src/components/pages/profile/[name]/Profile.tsx b/src/components/pages/profile/[name]/Profile.tsx index 7ebb583be..953ace5f8 100644 --- a/src/components/pages/profile/[name]/Profile.tsx +++ b/src/components/pages/profile/[name]/Profile.tsx @@ -15,7 +15,7 @@ import { useNameDetails } from '@app/hooks/useNameDetails' import { useProtectedRoute } from '@app/hooks/useProtectedRoute' import { useQueryParameterState } from '@app/hooks/useQueryParameterState' import { useRouterWithHistory } from '@app/hooks/useRouterWithHistory' -import { Content, ContentWarning } from '@app/layouts/Content' +import { Content } from '@app/layouts/Content' import { OG_IMAGE_URL } from '@app/utils/constants' import { formatFullExpiry, getEncodedLabelAmount, makeEtherscanLink } from '@app/utils/utils' @@ -109,8 +109,7 @@ const ProfileContent = ({ isSelf, isLoading: parentIsLoading, name }: Props) => const nameDetails = useNameDetails({ name }) const { - error, - errorTitle, + warning, profile, gracePeriodEndDate, expiryDate, @@ -221,16 +220,6 @@ const ProfileContent = ({ isSelf, isLoading: parentIsLoading, name }: Props) => return undefined }, [registrationStatus, gracePeriodEndDate, normalisedName, expiryDate]) - const warning: ContentWarning = useMemo(() => { - if (error) - return { - type: 'warning', - message: error, - title: errorTitle, - } - return undefined - }, [error, errorTitle]) - const ogImageUrl = `${OG_IMAGE_URL}/name/${normalisedName || name}` const chainName = useChainName() diff --git a/src/components/pages/profile/[name]/tabs/OwnershipTab/sections/ExpirySection/hooks/useExpiryDetails.ts b/src/components/pages/profile/[name]/tabs/OwnershipTab/sections/ExpirySection/hooks/useExpiryDetails.ts index 4bde1eb79..95ac3a98b 100644 --- a/src/components/pages/profile/[name]/tabs/OwnershipTab/sections/ExpirySection/hooks/useExpiryDetails.ts +++ b/src/components/pages/profile/[name]/tabs/OwnershipTab/sections/ExpirySection/hooks/useExpiryDetails.ts @@ -68,6 +68,8 @@ export const useExpiryDetails = ({ name, details }: Input, options: Options = {} 'eth-emancipated-2ld:grace-period', 'eth-locked-2ld', 'eth-locked-2ld:grace-period', + 'eth-wrapped-2ld', + 'eth-wrapped-2ld:grace-period', ), () => [ ...(expiry diff --git a/src/hooks/ensjs/public/useOwner.ts b/src/hooks/ensjs/public/useOwner.ts index 9cae1b9a8..1628395d6 100644 --- a/src/hooks/ensjs/public/useOwner.ts +++ b/src/hooks/ensjs/public/useOwner.ts @@ -1,10 +1,12 @@ import { QueryFunctionContext } from '@tanstack/react-query' +import { call } from 'viem/actions' import { getOwner, GetOwnerParameters, GetOwnerReturnType } from '@ensdomains/ensjs/public' import { useQueryOptions } from '@app/hooks/useQueryOptions' import { ConfigWithEns, CreateQueryKey, PartialBy, QueryConfig } from '@app/types' import { getIsCachedData } from '@app/utils/getIsCachedData' +import { getSupportedChainContractAddress } from '@app/utils/getSupportedChainContractAddress' import { prepareQueryOptions } from '@app/utils/prepareQueryOptions' import { useQuery } from '@app/utils/query/useQuery' @@ -12,7 +14,10 @@ type OwnerContract = 'nameWrapper' | 'registry' | 'registrar' export type UseOwnerParameters< TContract extends OwnerContract | undefined = OwnerContract | undefined, -> = PartialBy, 'name'> +> = PartialBy, 'name'> & { + /** Bypass contract-level expiry checks when fetching owner */ + forceUnexpired?: boolean +} export type UseOwnerReturnType = GetOwnerReturnType @@ -23,16 +28,48 @@ type UseOwnerConfig = CreateQueryKey, 'getOwner', 'standard'> +/** + * Creates bytecode that allows fetching an address value from a single-level mapped storage slot. + * + * i.e. `tokens[id] = address` + * @param params + * @param params.forSlot The storage slot to fetch the address from + * @returns The bytecode to fetch the address from the storage slot + * @source https://gist.github.com/TateB/d84e4789e1419a637fda6e23250fa1df yul code + */ +const createAddressStorageSlotFetcherBytecode = ({ forSlot }: { forSlot: number }) => + `0x6004355f52600${forSlot}60205260405f20545f52600c60405f5e60205ff3` as const + export const getOwnerQueryFn = (config: ConfigWithEns) => async ({ - queryKey: [{ name, ...params }, chainId], + queryKey: [{ name, forceUnexpired, ...params }, chainId], }: QueryFunctionContext>) => { if (!name) throw new Error('name is required') const client = config.getClient({ chainId }) - return getOwner(client, { name, ...params }) + if (!forceUnexpired) return getOwner(client, { name, ...params }) + + return call(client, { + ...getOwner.encode(client, { name, ...params }), + // override bytecode for namewrapper + registrar contracts with a simple helper + stateOverride: [ + { + address: getSupportedChainContractAddress({ client, contract: 'ensNameWrapper' }), + // _tokens at slot 1 + code: createAddressStorageSlotFetcherBytecode({ forSlot: 1 }), + }, + { + address: getSupportedChainContractAddress({ + client, + contract: 'ensBaseRegistrarImplementation', + }), + // _tokenOwner at slot 5 + code: createAddressStorageSlotFetcherBytecode({ forSlot: 5 }), + }, + ], + }).then((r) => getOwner.decode(client, r.data || '0x', { name, ...params })) } export const useOwner = < diff --git a/src/hooks/nameType/getNameType.ts b/src/hooks/nameType/getNameType.ts index 2c0bbde28..4a2a23ec6 100644 --- a/src/hooks/nameType/getNameType.ts +++ b/src/hooks/nameType/getNameType.ts @@ -15,6 +15,8 @@ export type NameType = | 'eth-emancipated-2ld:grace-period' | 'eth-locked-2ld' | 'eth-locked-2ld:grace-period' + | 'eth-wrapped-2ld' + | 'eth-wrapped-2ld:grace-period' | 'eth-unwrapped-subname' | 'eth-wrapped-subname' | 'eth-emancipated-subname' diff --git a/src/hooks/ownership/useRoles/useRoles.ts b/src/hooks/ownership/useRoles/useRoles.ts index e5e5083b0..3966703a3 100644 --- a/src/hooks/ownership/useRoles/useRoles.ts +++ b/src/hooks/ownership/useRoles/useRoles.ts @@ -65,7 +65,6 @@ function useRoles(name: string, options?: Options): Result { nameType: nameType.data!, owner: details.ownerData?.owner, registrant: details.ownerData?.registrant, - wrapperOwner: details.wrapperData?.owner, ethAddress: details.profile?.address, dnsOwner: details.dnsOwner ?? undefined, parentOwner: parentData.ownerData?.owner, @@ -78,7 +77,6 @@ function useRoles(name: string, options?: Options): Result { details.ownerData?.registrant, details.dnsOwner, details.profile?.address, - details.wrapperData?.owner, parentData.ownerData?.owner, parentData.wrapperData?.owner, ]) diff --git a/src/hooks/ownership/useRoles/utils/getRoles.ts b/src/hooks/ownership/useRoles/utils/getRoles.ts index 4fcadab54..6ca9aa287 100644 --- a/src/hooks/ownership/useRoles/utils/getRoles.ts +++ b/src/hooks/ownership/useRoles/utils/getRoles.ts @@ -7,7 +7,6 @@ export const getRoles = ({ nameType, registrant, owner, - wrapperOwner, dnsOwner, parentOwner, parentWrapperOwner, @@ -16,13 +15,16 @@ export const getRoles = ({ nameType?: NameType registrant?: Address | null owner?: Address - wrapperOwner?: Address | null dnsOwner?: Address | null parentOwner?: Address parentWrapperOwner?: Address | null ethAddress?: Address }) => { return match(nameType) + .with(P.union('eth-wrapped-2ld', 'eth-wrapped-2ld:grace-period'), () => [ + { address: owner, role: 'owner' as const }, + { address: ethAddress, role: 'eth-record' as const }, + ]) .with(P.union('eth-unwrapped-2ld', 'eth-unwrapped-2ld:grace-period'), () => [ { address: registrant || undefined, role: 'owner' as const }, { address: owner, role: 'manager' as const }, @@ -36,7 +38,7 @@ export const getRoles = ({ ], ) .with(P.union('eth-emancipated-2ld:grace-period', 'eth-locked-2ld:grace-period'), () => [ - { address: wrapperOwner, role: 'owner' as const }, + { address: owner, role: 'owner' as const }, { address: ethAddress, role: 'eth-record' as const }, ]) .with( diff --git a/src/hooks/useBasicName.ts b/src/hooks/useBasicName.ts index c9535baac..4c236d7e7 100644 --- a/src/hooks/useBasicName.ts +++ b/src/hooks/useBasicName.ts @@ -1,5 +1,4 @@ import { useMemo } from 'react' -import { getAddress } from 'viem' import { truncateFormat } from '@ensdomains/ensjs/utils' @@ -10,10 +9,9 @@ import { useContractAddress } from './chain/useContractAddress' import useCurrentBlockTimestamp from './chain/useCurrentBlockTimestamp' import { useAddressRecord } from './ensjs/public/useAddressRecord' import { useExpiry } from './ensjs/public/useExpiry' -import { useOwner, UseOwnerReturnType } from './ensjs/public/useOwner' +import { useOwner } from './ensjs/public/useOwner' import { usePrice } from './ensjs/public/usePrice' import { useWrapperData } from './ensjs/public/useWrapperData' -import { useSubgraphRegistrant } from './ensjs/subgraph/useSubgraphRegistrant' import { usePccExpired } from './fuses/usePccExpired' import { useSupportsTLD } from './useSupportsTLD' import { useValidate } from './useValidate' @@ -27,12 +25,7 @@ export type UseBasicNameOptions = { subgraphEnabled?: boolean } -export const useBasicName = ({ - name, - normalised = false, - enabled = true, - subgraphEnabled = true, -}: UseBasicNameOptions) => { +export const useBasicName = ({ name, normalised = false, enabled = true }: UseBasicNameOptions) => { const validation = useValidate({ input: name!, enabled: enabled && !!name }) const { name: _normalisedName, isValid, isShort, isETH, is2LD } = validation @@ -49,7 +42,7 @@ export const useBasicName = ({ isLoading: isOwnerLoading, isCachedData: isOwnerCachedData, refetchIfEnabled: refetchOwner, - } = useOwner({ name: normalisedName, enabled: commonEnabled }) + } = useOwner({ name: normalisedName, forceUnexpired: true, enabled: commonEnabled }) const { data: wrapperData, isLoading: isWrapperDataLoading, @@ -142,28 +135,6 @@ export const useBasicName = ({ }) : undefined - const { data: subgraphRegistrant } = useSubgraphRegistrant({ - name: normalisedName, - enabled: - enabled && - subgraphEnabled && - registrationStatus === 'gracePeriod' && - is2LD && - isETH && - !isWrapped, - }) - - const ownerDataWithSubgraphRegistrant = useMemo(() => { - if (!ownerData) return ownerData - const checkSumSubgraphRegistrant = subgraphRegistrant - ? getAddress(subgraphRegistrant) - : undefined - return { - ...ownerData, - registrant: ownerData?.registrant ?? checkSumSubgraphRegistrant, - } as UseOwnerReturnType - }, [ownerData, subgraphRegistrant]) - const truncatedName = normalisedName ? truncateFormat(normalisedName) : undefined const pccExpired = usePccExpired({ ownerData, wrapperData }) @@ -172,7 +143,7 @@ export const useBasicName = ({ return { ...validation, normalisedName, - ownerData: ownerDataWithSubgraphRegistrant, + ownerData, wrapperData, priceData, expiryDate, diff --git a/src/hooks/useNameDetails.tsx b/src/hooks/useNameDetails.tsx index 8cf401ba5..d6cd57d1c 100644 --- a/src/hooks/useNameDetails.tsx +++ b/src/hooks/useNameDetails.tsx @@ -1,6 +1,8 @@ -import { ReactNode, useMemo } from 'react' +import { useMemo } from 'react' import { useTranslation } from 'react-i18next' +import { NameExpiryDesyncBanner } from '@app/components/@molecules/NameExpiryDesyncBanner' +import type { ContentWarning } from '@app/layouts/Content' import { formatFullExpiry } from '@app/utils/utils' import { useDnsOwner } from './ensjs/dns/useDnsOwner' @@ -16,17 +18,24 @@ export const useNameDetails = ({ name, subgraphEnabled = true }: UseNameDetailsP const { t } = useTranslation('profile') const { - isValid, - normalisedName, isLoading: isBasicLoading, isCachedData: isBasicCachedData, - registrationStatus, - expiryDate, - gracePeriodEndDate, refetchIfEnabled: refetchBasicName, ...basicName } = useBasicName({ name }) + const { + isValid, + normalisedName, + registrationStatus, + expiryDate, + gracePeriodEndDate, + wrapperData, + isETH, + is2LD, + ownerData, + } = basicName + const { data: profile, isLoading: isProfileLoading, @@ -44,28 +53,51 @@ export const useNameDetails = ({ name, subgraphEnabled = true }: UseNameDetailsP isCachedData: isDnsOwnerCachedData, refetchIfEnabled: refetchDnsOwner, } = useDnsOwner({ name: normalisedName, enabled: isValid }) - const error: string | ReactNode | null = useMemo(() => { + + const warning: ContentWarning = useMemo(() => { if (isValid === false) { - return t('errors.invalidName') + return { type: 'warning', message: t('errors.invalidName') } } if (registrationStatus === 'unsupportedTLD') { - return t('errors.unsupportedTLD') + return { type: 'warning', message: t('errors.unsupportedTLD') } } + + if ( + !wrapperData && + ownerData?.ownershipLevel === 'nameWrapper' && + isETH && + is2LD && + expiryDate && + expiryDate > new Date() + ) { + return { + type: 'custom', + content: , + } + } + if (profile && !profile.isMigrated && typeof profile.isMigrated === 'boolean') { - return ( - <> - {t('errors.migrationNotAvailable')} - - {t('errors.migrationNotAvailableLink')} - - - ) + return { + type: 'warning', + message: ( + <> + {t('errors.migrationNotAvailable')} + + {t('errors.migrationNotAvailableLink')} + + + ), + } } if (registrationStatus === 'invalid') { - return t('errors.invalidName') + return { type: 'warning', message: t('errors.invalidName') } } if (registrationStatus === 'gracePeriod') { - return `${t('errors.expiringSoon', { date: formatFullExpiry(gracePeriodEndDate) })}` + return { + type: 'warning', + title: t('errors.hasExpired', { name: normalisedName }), + message: `${t('errors.expiringSoon', { date: formatFullExpiry(gracePeriodEndDate) })}`, + } } if ( // bypass unknown error for root name @@ -74,36 +106,33 @@ export const useNameDetails = ({ name, subgraphEnabled = true }: UseNameDetailsP !profile && !isProfileLoading ) { - return t('errors.networkError.message', { ns: 'common' }) + return { + type: 'warning', + title: t('errors.networkError.title', { ns: 'common' }), + message: t('errors.networkError.message', { ns: 'common' }), + } } - return null + return undefined }, [ - gracePeriodEndDate, - normalisedName, + isValid, + registrationStatus, + wrapperData, + ownerData?.ownershipLevel, + isETH, + is2LD, + expiryDate, profile, + normalisedName, isProfileLoading, - registrationStatus, t, - isValid, + gracePeriodEndDate, ]) - const errorTitle = useMemo(() => { - if (registrationStatus === 'gracePeriod') { - return t('errors.hasExpired', { name: normalisedName }) - } - if (normalisedName !== '[root]' && !profile && !isProfileLoading) { - return t('errors.networkError.title', { ns: 'common' }) - } - }, [registrationStatus, t, profile, isProfileLoading, normalisedName]) - const isLoading = isProfileLoading || isBasicLoading || isDnsOwnerLoading const isCachedData = isBasicCachedData || isProfileCachedData || isDnsOwnerCachedData return { - error, - errorTitle, - normalisedName, - isValid, + warning, profile, isLoading, isProfileLoading, @@ -111,9 +140,6 @@ export const useNameDetails = ({ name, subgraphEnabled = true }: UseNameDetailsP isDnsOwnerLoading, dnsOwner, isCachedData, - registrationStatus, - gracePeriodEndDate, - expiryDate, refetchIfEnabled: () => { refetchBasicName() refetchProfile() diff --git a/src/layouts/Content.tsx b/src/layouts/Content.tsx index d311a64ae..fcc5cd604 100644 --- a/src/layouts/Content.tsx +++ b/src/layouts/Content.tsx @@ -20,6 +20,10 @@ export type ContentWarning = title?: BannerProps['title'] message: BannerProps['children'] } + | { + type: 'custom' + content: ReactNode + } | undefined const HeadingItems = styled.div( @@ -262,9 +266,13 @@ export const Content = ({ const warning = useContentWarning([children.warning]) const WarningComponent = !loading && warning && ( - - {warning.message} - + {warning.type === 'custom' ? ( + warning.content + ) : ( + + {warning.message} + + )} ) diff --git a/src/transaction-flow/input/SendName/utils/checkCanSend.ts b/src/transaction-flow/input/SendName/utils/checkCanSend.ts index d8a22cfe9..76e9c8aac 100644 --- a/src/transaction-flow/input/SendName/utils/checkCanSend.ts +++ b/src/transaction-flow/input/SendName/utils/checkCanSend.ts @@ -37,6 +37,8 @@ export const senderRole = (nameType: ReturnType['data']) => 'eth-emancipated-2ld:grace-period', 'eth-locked-2ld:grace-period', 'eth-unwrapped-2ld:grace-period', + 'eth-wrapped-2ld', + 'eth-wrapped-2ld:grace-period', ), () => null, ) diff --git a/src/transaction-flow/input/SyncManager/utils/checkCanSyncManager.ts b/src/transaction-flow/input/SyncManager/utils/checkCanSyncManager.ts index 713d44482..67a038ab0 100644 --- a/src/transaction-flow/input/SyncManager/utils/checkCanSyncManager.ts +++ b/src/transaction-flow/input/SyncManager/utils/checkCanSyncManager.ts @@ -34,6 +34,8 @@ export const checkCanSyncManager = ({ 'eth-emancipated-2ld:grace-period', 'eth-locked-2ld', 'eth-locked-2ld:grace-period', + 'eth-wrapped-2ld', + 'eth-wrapped-2ld:grace-period', 'eth-unwrapped-subname', 'eth-wrapped-subname', 'eth-emancipated-subname', diff --git a/src/transaction-flow/transaction/index.ts b/src/transaction-flow/transaction/index.ts index 0bea70ee3..33d52072b 100644 --- a/src/transaction-flow/transaction/index.ts +++ b/src/transaction-flow/transaction/index.ts @@ -16,6 +16,7 @@ import resetProfile from './resetProfile' import resetProfileWithRecords from './resetProfileWithRecords' import setPrimaryName from './setPrimaryName' import syncManager from './syncManager' +import syncWrappedExpiry from './syncWrappedExpiry' import testSendName from './testSendName' import transferController from './transferController' import transferName from './transferName' @@ -46,6 +47,7 @@ export const transactions = { resetProfileWithRecords, setPrimaryName, syncManager, + syncWrappedExpiry, testSendName, transferController, transferName, diff --git a/src/transaction-flow/transaction/syncWrappedExpiry.ts b/src/transaction-flow/transaction/syncWrappedExpiry.ts new file mode 100644 index 000000000..b89a22365 --- /dev/null +++ b/src/transaction-flow/transaction/syncWrappedExpiry.ts @@ -0,0 +1,38 @@ +import type { TFunction } from 'react-i18next' + +import { renewNames } from '@ensdomains/ensjs/wallet' + +import { Transaction, TransactionDisplayItem, TransactionFunctionParameters } from '@app/types' + +type Data = { + name: string +} + +const displayItems = ( + { name }: Data, + t: TFunction<'translation', undefined>, +): TransactionDisplayItem[] => [ + { + label: 'action', + value: t(`transaction.description.syncWrappedExpiry`), + }, + { + label: 'info', + value: t(`transaction.info.syncWrappedExpiry`), + }, + { + label: 'name', + value: name, + type: 'name', + }, +] + +const transaction = async ({ connectorClient, data }: TransactionFunctionParameters) => { + return renewNames.makeFunctionData(connectorClient, { + nameOrNames: data.name, + duration: 0, + value: 0n, + }) +} + +export default { displayItems, transaction } satisfies Transaction From 8cf24078709b71d0cc1da9236153395bc7e28e91 Mon Sep 17 00:00:00 2001 From: Leon Talbert Date: Wed, 16 Oct 2024 09:42:29 +0800 Subject: [PATCH 02/12] Remove Thorin dep --- .../@molecules/NameExpiryDesyncBanner.tsx | 75 ++++++++++++++----- 1 file changed, 57 insertions(+), 18 deletions(-) diff --git a/src/components/@molecules/NameExpiryDesyncBanner.tsx b/src/components/@molecules/NameExpiryDesyncBanner.tsx index 6fcc4574b..0f704b851 100644 --- a/src/components/@molecules/NameExpiryDesyncBanner.tsx +++ b/src/components/@molecules/NameExpiryDesyncBanner.tsx @@ -1,11 +1,52 @@ import { useTranslation } from 'react-i18next' +import styled, { css } from 'styled-components' import { useAccount } from 'wagmi' -import { Banner, Button } from '@ensdomains/thorin' +import { Banner, Button, mq, Typography } from '@ensdomains/thorin' import { createTransactionItem } from '@app/transaction-flow/transaction' import { useTransactionFlow } from '@app/transaction-flow/TransactionFlowProvider' +const StyledButton = styled(Button)( + ({ theme }) => css` + width: 100%; + + ${mq.md.min(css` + width: auto; + margin-top: -25px; + `)} + `, +) + +const Container = styled.div( + ({ theme }) => css` + display: flex; + flex-direction: column; + gap: ${theme.space['4']}; + + ${mq.md.min(css` + flex-direction: row; + align-items: center; + justify-content: space-between; + `)} + `, +) + +const handleSyncClick = + ( + resumable: boolean, + resumeTransactionFlow: ReturnType['resumeTransactionFlow'], + createTransactionFlow: ReturnType['createTransactionFlow'], + key: string, + normalisedName: string, + ) => + () => { + if (resumable) resumeTransactionFlow(key) + return createTransactionFlow(key, { + transactions: [createTransactionItem('syncWrappedExpiry', { name: normalisedName })], + }) + } + export const NameExpiryDesyncBanner = ({ normalisedName }: { normalisedName: string }) => { const { t } = useTranslation('profile', { keyPrefix: 'banner.expiryDesync' }) @@ -15,26 +56,24 @@ export const NameExpiryDesyncBanner = ({ normalisedName }: { normalisedName: str const key = `wrapExpirySync-${normalisedName}` const resumable = getResumable(key) - const handleSyncClick = () => { - if (resumable) resumeTransactionFlow(key) - return createTransactionFlow(key, { - transactions: [createTransactionItem('syncWrappedExpiry', { name: normalisedName })], - }) - } + const _handleSyncClick = handleSyncClick( + resumable, + resumeTransactionFlow, + createTransactionFlow, + key, + normalisedName, + ) return ( - + + + {t('description')} + {isConnected ? ( + {t('actionLabel')} - - ) : undefined - } - > - {t('description')} + + ) : null} + ) } From c08a7ecbc4742584ec90d8b9a74fde742c0d87a6 Mon Sep 17 00:00:00 2001 From: Leon Talbert Date: Wed, 16 Oct 2024 10:09:42 +0800 Subject: [PATCH 03/12] Expandable Section test --- .../ExpandableSection.test.tsx | 42 ++++++++++++++++++- .../ExpandableSection/ExpandableSection.tsx | 16 ++++--- 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/src/components/@atoms/ExpandableSection/ExpandableSection.test.tsx b/src/components/@atoms/ExpandableSection/ExpandableSection.test.tsx index e44c4517c..7ee60b1ae 100644 --- a/src/components/@atoms/ExpandableSection/ExpandableSection.test.tsx +++ b/src/components/@atoms/ExpandableSection/ExpandableSection.test.tsx @@ -1,8 +1,8 @@ import { render, screen, userEvent } from '@app/test-utils' -import { describe, expect, it } from 'vitest' +import { describe, expect, it, vi } from 'vitest' -import { ExpandableSection } from './ExpandableSection' +import { ExpandableSection, stateChangeHandler } from './ExpandableSection' describe('ExpandableSection', () => { it('should expand and close when header is clicked', async () => { @@ -15,3 +15,41 @@ describe('ExpandableSection', () => { expect(screen.getByText('CONTENT')).not.toBeVisible() }) }) + +describe('stateChangeHandler', () => { + it('should set height when status is preEnter or preExit', () => { + const setHeight = vi.fn() + const ref = { current: { getBoundingClientRect: () => ({ height: 100 }) } } + + const handler = stateChangeHandler(setHeight, ref) + + handler({ current: { status: 'preEnter' } }) + expect(setHeight).toHaveBeenCalledWith(100) + + handler({ current: { status: 'preExit' } }) + expect(setHeight).toHaveBeenCalledWith(100) + }) + + it('should not set height for other statuses', () => { + const setHeight = vi.fn() + const ref = { current: { getBoundingClientRect: () => ({ height: 100 }) } } + + const handler = stateChangeHandler(setHeight, ref) + + handler({ current: { status: 'entered' } }) + expect(setHeight).not.toHaveBeenCalled() + + handler({ current: { status: 'exited' } }) + expect(setHeight).not.toHaveBeenCalled() + }) + + it('should handle null ref', () => { + const setHeight = vi.fn() + const ref = { current: null } + + const handler = stateChangeHandler(setHeight, ref) + + handler({ current: { status: 'preEnter' } }) + expect(setHeight).toHaveBeenCalledWith(0) + }) +}) diff --git a/src/components/@atoms/ExpandableSection/ExpandableSection.tsx b/src/components/@atoms/ExpandableSection/ExpandableSection.tsx index 3417430da..1ca850c2c 100644 --- a/src/components/@atoms/ExpandableSection/ExpandableSection.tsx +++ b/src/components/@atoms/ExpandableSection/ExpandableSection.tsx @@ -86,6 +86,15 @@ type Props = { title: string } +export const stateChangeHandler = + (setHeight: React.Dispatch>, ref: React.RefObject) => + ({ current: newState }: { current: { status: TransitionStatus } }) => { + if (newState.status === 'preEnter' || newState.status === 'preExit') { + const height = ref.current?.getBoundingClientRect().height || 0 + setHeight(height) + } + } + export const ExpandableSection = ({ title, children }: PropsWithChildren) => { const ref = useRef(null) @@ -94,12 +103,7 @@ export const ExpandableSection = ({ title, children }: PropsWithChildren) timeout: 300, preEnter: true, preExit: true, - onStateChange: ({ current: newState }) => { - if (newState.status === 'preEnter' || newState.status === 'preExit') { - const _heigth = ref.current?.getBoundingClientRect().height || 0 - setHeight(_heigth) - } - }, + onStateChange: stateChangeHandler(setHeight, ref), }) const open = ['entered', 'entering', 'preEnter'].includes(state.status) From 31b59005bcdd5e9f2fb19067ba286f7a8c9f0010 Mon Sep 17 00:00:00 2001 From: Leon Talbert Date: Wed, 16 Oct 2024 14:11:51 +0800 Subject: [PATCH 04/12] NameExpiryDesyncBanner tests --- .../NameExpiryDesyncBanner.test.tsx | 30 +++++++++++++++++++ .../@molecules/NameExpiryDesyncBanner.tsx | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 src/components/@molecules/NameExpiryDesyncBanner.test.tsx diff --git a/src/components/@molecules/NameExpiryDesyncBanner.test.tsx b/src/components/@molecules/NameExpiryDesyncBanner.test.tsx new file mode 100644 index 000000000..05753b36e --- /dev/null +++ b/src/components/@molecules/NameExpiryDesyncBanner.test.tsx @@ -0,0 +1,30 @@ +import { mockFunction, render, screen } from '@app/test-utils' + +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { useAccount } from 'wagmi' + +import { NameExpiryDesyncBanner } from './NameExpiryDesyncBanner' + +vi.mock('wagmi') + +const mockUseAccount = mockFunction(useAccount) + +describe('NameExpiryDesyncBanner', () => { + it('should render', () => { + mockUseAccount.mockReturnValue({ isConnected: true }) + render() + expect(screen.getByText('description')).toBeInTheDocument() + }) + + it('should show action button when connected', () => { + mockUseAccount.mockReturnValue({ isConnected: true }) + render() + expect(screen.getByText('actionLabel')).toBeInTheDocument() + }) + + it('should not show action button when not connected', () => { + mockUseAccount.mockReturnValue({ isConnected: false }) + render() + expect(screen.queryByText('actionLabel')).not.toBeInTheDocument() + }) +}) diff --git a/src/components/@molecules/NameExpiryDesyncBanner.tsx b/src/components/@molecules/NameExpiryDesyncBanner.tsx index 0f704b851..ce1e40a86 100644 --- a/src/components/@molecules/NameExpiryDesyncBanner.tsx +++ b/src/components/@molecules/NameExpiryDesyncBanner.tsx @@ -8,7 +8,7 @@ import { createTransactionItem } from '@app/transaction-flow/transaction' import { useTransactionFlow } from '@app/transaction-flow/TransactionFlowProvider' const StyledButton = styled(Button)( - ({ theme }) => css` + () => css` width: 100%; ${mq.md.min(css` From e7d274377a59c4099c30fc26fae321decb15da7b Mon Sep 17 00:00:00 2001 From: Leon Talbert Date: Wed, 16 Oct 2024 15:47:29 +0800 Subject: [PATCH 05/12] useOwner tests --- src/hooks/ensjs/public/useOwner.test.ts | 87 +++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 src/hooks/ensjs/public/useOwner.test.ts diff --git a/src/hooks/ensjs/public/useOwner.test.ts b/src/hooks/ensjs/public/useOwner.test.ts new file mode 100644 index 000000000..348709dfd --- /dev/null +++ b/src/hooks/ensjs/public/useOwner.test.ts @@ -0,0 +1,87 @@ +import { mockFunction } from '@app/test-utils' + +import { call } from 'viem/actions' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +import { getOwner } from '@ensdomains/ensjs/public' + +import { ClientWithEns } from '@app/types' +import { getSupportedChainContractAddress } from '@app/utils/getSupportedChainContractAddress' + +import { getOwnerQueryFn } from './useOwner' + +vi.mock('@ensdomains/ensjs/public') +vi.mock('viem/actions') +vi.mock('@app/utils/getSupportedChainContractAddress') + +const mockGetOwner = mockFunction(getOwner) +const mockGetSupportedChainContractAddress = mockFunction(getSupportedChainContractAddress) +const mockCall = mockFunction(call) + +const mockRequest = vi.fn() +const mockClient = { + chain: { + id: 1, + }, + request: mockRequest, +} as unknown as ClientWithEns +const mockConfig = { + getClient: () => mockClient, +} + +describe('getOwnerQueryFn', () => { + beforeEach(() => { + vi.resetAllMocks() + }) + it('should return the owner for a valid name', async () => { + mockGetOwner.mockResolvedValue({ owner: '0x1234567890123456789012345678901234567890' }) + + const queryFn = getOwnerQueryFn(mockConfig) + const result = await queryFn({ queryKey: [{ name: 'test.eth' }, 1] }) + + expect(result).toStrictEqual({ owner: '0x1234567890123456789012345678901234567890' }) + expect(mockGetOwner).toHaveBeenCalledWith(mockClient, { name: 'test.eth' }) + }) + + it('should use call with state override when forceUnexpired is true', async () => { + mockCall.mockResolvedValue({ data: '0x' }) + mockClient.request.mockImplementation(mockCall) + + mockGetOwner.mockResolvedValue({ + encode: () => ({ to: '0x1234', data: '0xabcd' }), + decode: () => ({ owner: '0x5678' }), + }) + + mockGetSupportedChainContractAddress.mockReturnValue( + '0x9876543210987654321098765432109876543210', + ) + + const queryFn = getOwnerQueryFn(mockConfig) + await queryFn({ queryKey: [{ name: 'test.eth', forceUnexpired: true }] }) + + expect(mockCall).toHaveBeenCalledWith(mockConfig.getClient(), { + stateOverride: [ + { + address: '0x9876543210987654321098765432109876543210', + code: '0x6004355f52600160205260405f20545f52600c60405f5e60205ff3', + }, + { + address: '0x9876543210987654321098765432109876543210', + code: '0x6004355f52600560205260405f20545f52600c60405f5e60205ff3', + }, + ], + }) + + expect(mockGetOwner.decode).toHaveBeenCalledWith(mockClient, '0x', { name: 'test.eth' }) + }) + + it('should not use call with state override when forceUnexpired is false', async () => { + mockGetOwner.mockResolvedValue({ owner: '0x1234567890123456789012345678901234567890' }) + + const queryFn = getOwnerQueryFn(mockConfig) + await queryFn({ queryKey: [{ name: 'test.eth', forceUnexpired: false }, 1] }) + + expect(mockCall).not.toHaveBeenCalled() + expect(mockGetOwner).toHaveBeenCalledWith(mockClient, { name: 'test.eth' }) + }) +}) From 3bb22c27635bb7f4287c111d57478275012d656f Mon Sep 17 00:00:00 2001 From: Leon Talbert Date: Wed, 16 Oct 2024 15:54:16 +0800 Subject: [PATCH 06/12] tweak --- src/hooks/ensjs/public/useOwner.test.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/hooks/ensjs/public/useOwner.test.ts b/src/hooks/ensjs/public/useOwner.test.ts index 348709dfd..ae25c2867 100644 --- a/src/hooks/ensjs/public/useOwner.test.ts +++ b/src/hooks/ensjs/public/useOwner.test.ts @@ -47,11 +47,6 @@ describe('getOwnerQueryFn', () => { mockCall.mockResolvedValue({ data: '0x' }) mockClient.request.mockImplementation(mockCall) - mockGetOwner.mockResolvedValue({ - encode: () => ({ to: '0x1234', data: '0xabcd' }), - decode: () => ({ owner: '0x5678' }), - }) - mockGetSupportedChainContractAddress.mockReturnValue( '0x9876543210987654321098765432109876543210', ) From b2be9c7e109fffebe43d6a4b2064de8b43b2eb35 Mon Sep 17 00:00:00 2001 From: Leon Talbert Date: Thu, 17 Oct 2024 06:45:58 +0800 Subject: [PATCH 07/12] getNameType tests --- .../[name]/tabs/OwnershipTab/OwnershipTab.tsx | 1 + src/hooks/nameType/getNameType.test.ts | 148 ++++++++++++++++++ src/hooks/nameType/getNameType.ts | 23 ++- 3 files changed, 158 insertions(+), 14 deletions(-) create mode 100644 src/hooks/nameType/getNameType.test.ts diff --git a/src/components/pages/profile/[name]/tabs/OwnershipTab/OwnershipTab.tsx b/src/components/pages/profile/[name]/tabs/OwnershipTab/OwnershipTab.tsx index 2f2e650a3..6d68d10d4 100644 --- a/src/components/pages/profile/[name]/tabs/OwnershipTab/OwnershipTab.tsx +++ b/src/components/pages/profile/[name]/tabs/OwnershipTab/OwnershipTab.tsx @@ -27,6 +27,7 @@ type Props = { export const OwnershipTab = ({ name, details }: Props) => { const roles = useRoles(name, { grouped: true }) + console.log('roles: ', roles) const nameType = useNameType(name) const warning = useOwnershipWarning({ name, details, nameType }) const isLoading = roles.isLoading || details.isLoading diff --git a/src/hooks/nameType/getNameType.test.ts b/src/hooks/nameType/getNameType.test.ts new file mode 100644 index 000000000..71facfa7f --- /dev/null +++ b/src/hooks/nameType/getNameType.test.ts @@ -0,0 +1,148 @@ +import { Address } from 'viem' +import { describe, expect, it } from 'vitest' + +import { GetOwnerReturnType, GetWrapperDataReturnType } from '@ensdomains/ensjs/public' + +import { RegistrationStatus } from '@app/utils/registrationStatus' + +import { getNameType } from './getNameType' + +describe('getNameType', () => { + const mockNameWrapperAddress: Address = '0x1234567890123456789012345678901234567890' + + it('should return "root" for [root]', () => { + const result = getNameType({ + name: '[root]', + ownerData: undefined, + wrapperData: undefined, + pccExpired: false, + registrationStatus: undefined, + nameWrapperAddress: mockNameWrapperAddress, + }) + expect(result).toBe('root') + }) + + it('should return "tld" for .eth', () => { + const result = getNameType({ + name: 'eth', + ownerData: undefined, + wrapperData: undefined, + pccExpired: false, + registrationStatus: undefined, + nameWrapperAddress: mockNameWrapperAddress, + }) + expect(result).toBe('tld') + }) + + it('should return "eth-unwrapped-2ld" for an unwrapped .eth 2LD', () => { + const result = getNameType({ + name: 'test.eth', + ownerData: { ownershipLevel: 'registry' } as GetOwnerReturnType, + wrapperData: undefined, + pccExpired: false, + registrationStatus: 'registered', + nameWrapperAddress: mockNameWrapperAddress, + }) + expect(result).toBe('eth-unwrapped-2ld') + }) + + it('should return "eth-wrapped-2ld" for a wrapped .eth 2LD', () => { + const result = getNameType({ + name: 'test.eth', + ownerData: { ownershipLevel: 'nameWrapper' } as GetOwnerReturnType, + wrapperData: { fuses: { child: {}, parent: {} } } as GetWrapperDataReturnType, + pccExpired: false, + registrationStatus: 'registered', + nameWrapperAddress: mockNameWrapperAddress, + }) + expect(result).toBe('eth-wrapped-2ld') + }) + + it('should return "eth-locked-2ld" for a locked .eth 2LD', () => { + const result = getNameType({ + name: 'test.eth', + ownerData: { ownershipLevel: 'nameWrapper' } as GetOwnerReturnType, + wrapperData: { + fuses: { child: { CANNOT_UNWRAP: true }, parent: {} }, + } as GetWrapperDataReturnType, + pccExpired: false, + registrationStatus: 'registered', + nameWrapperAddress: mockNameWrapperAddress, + }) + expect(result).toBe('eth-locked-2ld') + }) + + it('should return "eth-emancipated-2ld" for an emancipated .eth 2LD', () => { + const result = getNameType({ + name: 'test.eth', + ownerData: { ownershipLevel: 'nameWrapper' } as GetOwnerReturnType, + wrapperData: { + fuses: { child: {}, parent: { PARENT_CANNOT_CONTROL: true } }, + } as GetWrapperDataReturnType, + pccExpired: false, + registrationStatus: 'registered', + nameWrapperAddress: mockNameWrapperAddress, + }) + expect(result).toBe('eth-emancipated-2ld') + }) + + it('should return "eth-unwrapped-2ld:grace-period" for an unwrapped .eth 2LD in grace period', () => { + const result = getNameType({ + name: 'test.eth', + ownerData: { owner: '0x1234567890123456789012345678901234567890' } as GetOwnerReturnType, + wrapperData: undefined, + pccExpired: false, + registrationStatus: 'gracePeriod', + nameWrapperAddress: mockNameWrapperAddress, + }) + expect(result).toBe('eth-unwrapped-2ld:grace-period') + }) + + it('should return "eth-wrapped-subname" for a wrapped .eth subname', () => { + const result = getNameType({ + name: 'sub.test.eth', + ownerData: { ownershipLevel: 'nameWrapper' } as GetOwnerReturnType, + wrapperData: { fuses: { child: {}, parent: {} } } as GetWrapperDataReturnType, + pccExpired: false, + registrationStatus: undefined, + nameWrapperAddress: mockNameWrapperAddress, + }) + expect(result).toBe('eth-wrapped-subname') + }) + + it('should return "eth-pcc-expired-subname" for an expired PCC .eth subname', () => { + const result = getNameType({ + name: 'sub.test.eth', + ownerData: { ownershipLevel: 'nameWrapper' } as GetOwnerReturnType, + wrapperData: { fuses: { child: {}, parent: {} } } as GetWrapperDataReturnType, + pccExpired: true, + registrationStatus: undefined, + nameWrapperAddress: mockNameWrapperAddress, + }) + expect(result).toBe('eth-pcc-expired-subname') + }) + + it('should return "dns-unwrapped-2ld" for an unwrapped DNS 2LD', () => { + const result = getNameType({ + name: 'test.com', + ownerData: { ownershipLevel: 'registry' } as GetOwnerReturnType, + wrapperData: undefined, + pccExpired: false, + registrationStatus: undefined, + nameWrapperAddress: mockNameWrapperAddress, + }) + expect(result).toBe('dns-unwrapped-2ld') + }) + + it('should return "dns-wrapped-subname" for a wrapped DNS subname', () => { + const result = getNameType({ + name: 'sub.test.com', + ownerData: { ownershipLevel: 'nameWrapper' } as GetOwnerReturnType, + wrapperData: { fuses: { child: {}, parent: {} } } as GetWrapperDataReturnType, + pccExpired: false, + registrationStatus: undefined, + nameWrapperAddress: mockNameWrapperAddress, + }) + expect(result).toBe('dns-wrapped-subname') + }) +}) diff --git a/src/hooks/nameType/getNameType.ts b/src/hooks/nameType/getNameType.ts index 4a2a23ec6..1a2bb70e4 100644 --- a/src/hooks/nameType/getNameType.ts +++ b/src/hooks/nameType/getNameType.ts @@ -68,28 +68,23 @@ export const getNameType = ({ const level = nameLevel(name) const wrapLevel = getWrapLevel({ wrapperData, ownerData }) - return match([tldType, wrapLevel, level, registrationStatus]) - .with([P._, P._, P.union('root', 'tld'), P._], ([, , _level]) => _level) - .with(['eth', P._, '2ld', 'gracePeriod'], () => { + return match({ tldType, wrapLevel, level, registrationStatus }) + .with({ level: P.union('root', 'tld') }, ({ level: _level }) => _level) + .with({ tldType: 'eth', level: '2ld', registrationStatus: 'gracePeriod' }, () => { if (ownerData?.owner !== nameWrapperAddress) return 'eth-unwrapped-2ld:grace-period' as const if (wrapperData?.fuses?.child?.CANNOT_UNWRAP) return 'eth-locked-2ld:grace-period' as const if (wrapperData?.fuses?.parent?.PARENT_CANNOT_CONTROL) return 'eth-emancipated-2ld:grace-period' as const return 'eth-unwrapped-2ld:grace-period' as const }) + .with({ tldType: 'eth', level: '2ld' }, ({ tldType: _tldType, wrapLevel: _wrapLevel }) => { + return `${_tldType}-${_wrapLevel}-2ld` as const + }) .with( - ['eth', P._, '2ld', P._], - ([_tldType, _wrapLevel]: [ - 'eth', - 'unwrapped' | 'emancipated' | 'locked', - '2ld', - RegistrationStatus, - ]) => { - return `${_tldType}-${_wrapLevel}-2ld` as const - }, + { tldType: 'dns', level: '2ld' }, + ({ wrapLevel: _wrapLevel }) => `dns-${_wrapLevel}-2ld` as const, ) - .with(['dns', P._, '2ld', P._], ([, _wrapLevel]) => `dns-${_wrapLevel}-2ld` as const) - .with([P._, P._, 'subname', P._], ([_tldType, _wrapLevel]) => + .with({ level: 'subname' }, ({ tldType: _tldType, wrapLevel: _wrapLevel }) => pccExpired ? (`${_tldType}-pcc-expired-subname` as const) : (`${_tldType}-${_wrapLevel}-subname` as const), From 8f75822226d4a93691cfa5a303753c2b67df0069 Mon Sep 17 00:00:00 2001 From: Leon Talbert Date: Thu, 17 Oct 2024 07:20:27 +0800 Subject: [PATCH 08/12] getRoles tests --- .../ownership/useRoles/utils/getRoles.test.ts | 216 +++++++++++++++++- .../ownership/useRoles/utils/getRoles.ts | 9 + 2 files changed, 224 insertions(+), 1 deletion(-) diff --git a/src/hooks/ownership/useRoles/utils/getRoles.test.ts b/src/hooks/ownership/useRoles/utils/getRoles.test.ts index ff5b30472..21db2b3f6 100644 --- a/src/hooks/ownership/useRoles/utils/getRoles.test.ts +++ b/src/hooks/ownership/useRoles/utils/getRoles.test.ts @@ -3,7 +3,7 @@ import { describe, expect, it } from 'vitest' import { getRoles } from './getRoles' describe('getRoles', () => { - it('should return an list of roles', () => { + it('should return a list of roles', () => { expect( getRoles({ nameType: 'eth-unwrapped-2ld', @@ -19,4 +19,218 @@ describe('getRoles', () => { { address: '0xethAddress', role: 'eth-record' }, ]) }) + + it('should return a list of roles for a wrapped 2LD', () => { + expect( + getRoles({ + nameType: 'eth-wrapped-2ld', + registrant: '0xregistrant', + owner: '0xowner', + ethAddress: '0xethAddress', + }), + ).toEqual([ + { address: '0xowner', role: 'owner' }, + { address: '0xethAddress', role: 'eth-record' }, + ]) + }) + + it('should return a list of roles for an unwrapped 2LD', () => { + expect( + getRoles({ + nameType: 'eth-unwrapped-2ld', + registrant: '0xregistrant', + owner: '0xowner', + ethAddress: '0xethAddress', + }), + ).toEqual([ + { address: '0xregistrant', role: 'owner' }, + { address: '0xowner', role: 'manager' }, + { address: '0xethAddress', role: 'eth-record' }, + ]) + }) + + it('should return an list of roles for an emancipated 2LD', () => { + expect( + getRoles({ + nameType: 'eth-emancipated-2ld', + registrant: '0xregistrant', + owner: '0xowner', + ethAddress: '0xethAddress', + }), + ).toEqual([ + { address: '0xowner', role: 'owner' }, + { address: '0xethAddress', role: 'eth-record' }, + ]) + }) + + it('should return an list of roles for a locked 2LD', () => { + expect( + getRoles({ + nameType: 'eth-locked-2ld', + registrant: '0xregistrant', + owner: '0xowner', + ethAddress: '0xethAddress', + }), + ).toEqual([ + { address: '0xowner', role: 'owner' }, + { address: '0xethAddress', role: 'eth-record' }, + ]) + }) + + it('should return a list of roles for a subname', () => { + expect( + getRoles({ + nameType: 'eth-unwrapped-subname', + registrant: '0xregistrant', + owner: '0xowner', + ethAddress: '0xethAddress', + parentOwner: '0xparentOwner', + parentWrapperOwner: '0xparentWrapperOwner', + }), + ).toEqual([ + { address: '0xparentWrapperOwner', role: 'parent-owner' }, + { address: '0xowner', role: 'manager' }, + { address: '0xethAddress', role: 'eth-record' }, + ]) + }) + + it('should return an list of roles for a wrapped subname', () => { + expect( + getRoles({ + nameType: 'eth-wrapped-subname', + registrant: '0xregistrant', + owner: '0xowner', + ethAddress: '0xethAddress', + parentWrapperOwner: '0xparentWrapperOwner', + }), + ).toEqual([ + { address: '0xparentWrapperOwner', role: 'parent-owner' }, + { address: '0xowner', role: 'manager' }, + { address: '0xethAddress', role: 'eth-record' }, + ]) + }) + + it('should return an list of roles for a pcc expired subname', () => { + expect( + getRoles({ + nameType: 'eth-pcc-expired-subname', + registrant: '0xregistrant', + owner: '0xowner', + ethAddress: '0xethAddress', + parentOwner: '0xparentOwner', + parentWrapperOwner: '0xparentWrapperOwner', + }), + ).toEqual([ + { address: '0xparentWrapperOwner', role: 'parent-owner' }, + { address: '0xowner', role: 'manager' }, + { address: '0xethAddress', role: 'eth-record' }, + ]) + }) + + it('should return an list of roles for a dns subname', () => { + expect( + getRoles({ + nameType: 'dns-unwrapped-subname', + registrant: '0xregistrant', + owner: '0xowner', + ethAddress: '0xethAddress', + parentOwner: '0xparentOwner', + parentWrapperOwner: '0xparentWrapperOwner', + }), + ).toEqual([ + { address: '0xparentWrapperOwner', role: 'parent-owner' }, + { address: '0xowner', role: 'manager' }, + { address: '0xethAddress', role: 'eth-record' }, + ]) + }) + + it('should return an list of roles for a dns wrapped subname', () => { + expect( + getRoles({ + nameType: 'dns-wrapped-subname', + registrant: '0xregistrant', + owner: '0xowner', + ethAddress: '0xethAddress', + parentOwner: '0xparentOwner', + parentWrapperOwner: '0xparentWrapperOwner', + }), + ).toEqual([ + { address: '0xparentWrapperOwner', role: 'parent-owner' }, + { address: '0xowner', role: 'manager' }, + { address: '0xethAddress', role: 'eth-record' }, + ]) + }) + + it('should return an list of roles for a dns emancipated subname', () => { + expect( + getRoles({ + nameType: 'dns-emancipated-subname', + registrant: '0xregistrant', + owner: '0xowner', + ethAddress: '0xethAddress', + parentOwner: '0xparentOwner', + parentWrapperOwner: '0xparentWrapperOwner', + }), + ).toEqual([ + { address: '0xowner', role: 'owner' }, + { address: '0xethAddress', role: 'eth-record' }, + ]) + }) + + it('should return an list of roles for a dns locked subname', () => { + expect( + getRoles({ + nameType: 'dns-locked-subname', + registrant: '0xregistrant', + owner: '0xowner', + ethAddress: '0xethAddress', + parentOwner: '0xparentOwner', + parentWrapperOwner: '0xparentWrapperOwner', + }), + ).toEqual([ + { address: '0xowner', role: 'owner' }, + { address: '0xethAddress', role: 'eth-record' }, + ]) + }) + + it('should return an list of roles for a dns pcc expired subname', () => { + expect( + getRoles({ + nameType: 'dns-pcc-expired-subname', + registrant: '0xregistrant', + owner: '0xowner', + ethAddress: '0xethAddress', + parentOwner: '0xparentOwner', + parentWrapperOwner: '0xparentWrapperOwner', + }), + ).toEqual([ + { address: '0xparentWrapperOwner', role: 'parent-owner' }, + { address: '0xowner', role: 'manager' }, + { address: '0xethAddress', role: 'eth-record' }, + ]) + }) + + it('should return an empty list of roles for a tld', () => { + expect( + getRoles({ + nameType: 'tld', + }), + ).toEqual([]) + }) + + it('should return an empty list of roles for a root', () => { + expect( + getRoles({ + nameType: 'root', + }), + ).toEqual([]) + }) + + it('should return an empty list of roles for a nullish', () => { + expect( + getRoles({ + nameType: undefined, + }), + ).toEqual([]) + }) }) diff --git a/src/hooks/ownership/useRoles/utils/getRoles.ts b/src/hooks/ownership/useRoles/utils/getRoles.ts index 6ca9aa287..1d54b5b09 100644 --- a/src/hooks/ownership/useRoles/utils/getRoles.ts +++ b/src/hooks/ownership/useRoles/utils/getRoles.ts @@ -3,6 +3,15 @@ import { Address } from 'viem' import type { NameType } from '@app/hooks/nameType/getNameType' +/* + Roles relate to the type of ownership + - owner: The ultimate owner of the name + - manager: Able to manager various details of the name, but is not the ultimate owner + - eth-record: Mostly for off-chain names, assume the owner is the eth record + - parent-owner: The owner of the parent name + - dns-owner: The owner of the DNS name +*/ + export const getRoles = ({ nameType, registrant, From 79e71d008e47f88005ee504afbd125cb055f1e9f Mon Sep 17 00:00:00 2001 From: Leon Talbert Date: Fri, 18 Oct 2024 15:05:44 +0800 Subject: [PATCH 09/12] Desynced name type --- .../ExpirySection/hooks/useExpiryDetails.ts | 4 ++-- src/hooks/nameType/getNameType.test.ts | 21 +++++++++++++++---- src/hooks/nameType/getNameType.ts | 8 +++++-- src/hooks/nameType/useNameType.test.ts | 2 +- .../ownership/useRoles/utils/getRoles.test.ts | 2 +- .../ownership/useRoles/utils/getRoles.ts | 2 +- .../input/SendName/utils/checkCanSend.ts | 4 ++-- .../SyncManager/utils/checkCanSyncManager.ts | 4 ++-- 8 files changed, 32 insertions(+), 15 deletions(-) diff --git a/src/components/pages/profile/[name]/tabs/OwnershipTab/sections/ExpirySection/hooks/useExpiryDetails.ts b/src/components/pages/profile/[name]/tabs/OwnershipTab/sections/ExpirySection/hooks/useExpiryDetails.ts index 95ac3a98b..8597e6297 100644 --- a/src/components/pages/profile/[name]/tabs/OwnershipTab/sections/ExpirySection/hooks/useExpiryDetails.ts +++ b/src/components/pages/profile/[name]/tabs/OwnershipTab/sections/ExpirySection/hooks/useExpiryDetails.ts @@ -68,8 +68,8 @@ export const useExpiryDetails = ({ name, details }: Input, options: Options = {} 'eth-emancipated-2ld:grace-period', 'eth-locked-2ld', 'eth-locked-2ld:grace-period', - 'eth-wrapped-2ld', - 'eth-wrapped-2ld:grace-period', + 'eth-desynced-2ld', + 'eth-desynced-2ld:grace-period', ), () => [ ...(expiry diff --git a/src/hooks/nameType/getNameType.test.ts b/src/hooks/nameType/getNameType.test.ts index 71facfa7f..2bef12b08 100644 --- a/src/hooks/nameType/getNameType.test.ts +++ b/src/hooks/nameType/getNameType.test.ts @@ -3,8 +3,6 @@ import { describe, expect, it } from 'vitest' import { GetOwnerReturnType, GetWrapperDataReturnType } from '@ensdomains/ensjs/public' -import { RegistrationStatus } from '@app/utils/registrationStatus' - import { getNameType } from './getNameType' describe('getNameType', () => { @@ -46,7 +44,7 @@ describe('getNameType', () => { expect(result).toBe('eth-unwrapped-2ld') }) - it('should return "eth-wrapped-2ld" for a wrapped .eth 2LD', () => { + it('should return "eth-desynced-2ld" for a desynced .eth 2LD', () => { const result = getNameType({ name: 'test.eth', ownerData: { ownershipLevel: 'nameWrapper' } as GetOwnerReturnType, @@ -55,7 +53,7 @@ describe('getNameType', () => { registrationStatus: 'registered', nameWrapperAddress: mockNameWrapperAddress, }) - expect(result).toBe('eth-wrapped-2ld') + expect(result).toBe('eth-desynced-2ld') }) it('should return "eth-locked-2ld" for a locked .eth 2LD', () => { @@ -145,4 +143,19 @@ describe('getNameType', () => { }) expect(result).toBe('dns-wrapped-subname') }) + + it('should return "eth-desynced-2ld:grace-period" for a desynced .eth 2LD in grace period', () => { + const result = getNameType({ + name: 'test.eth', + ownerData: { + ownershipLevel: 'nameWrapper', + owner: mockNameWrapperAddress, + } as GetOwnerReturnType, + wrapperData: undefined, + pccExpired: false, + registrationStatus: 'gracePeriod', + nameWrapperAddress: mockNameWrapperAddress, + }) + expect(result).toBe('eth-desynced-2ld:grace-period') + }) }) diff --git a/src/hooks/nameType/getNameType.ts b/src/hooks/nameType/getNameType.ts index 1a2bb70e4..c1cbc7cb9 100644 --- a/src/hooks/nameType/getNameType.ts +++ b/src/hooks/nameType/getNameType.ts @@ -15,8 +15,8 @@ export type NameType = | 'eth-emancipated-2ld:grace-period' | 'eth-locked-2ld' | 'eth-locked-2ld:grace-period' - | 'eth-wrapped-2ld' - | 'eth-wrapped-2ld:grace-period' + | 'eth-desynced-2ld' + | 'eth-desynced-2ld:grace-period' | 'eth-unwrapped-subname' | 'eth-wrapped-subname' | 'eth-emancipated-subname' @@ -75,9 +75,13 @@ export const getNameType = ({ if (wrapperData?.fuses?.child?.CANNOT_UNWRAP) return 'eth-locked-2ld:grace-period' as const if (wrapperData?.fuses?.parent?.PARENT_CANNOT_CONTROL) return 'eth-emancipated-2ld:grace-period' as const + // If wrapped eth 2ld without fuses set then it's desynced + if (wrapLevel === 'wrapped') return 'eth-desynced-2ld:grace-period' as const return 'eth-unwrapped-2ld:grace-period' as const }) .with({ tldType: 'eth', level: '2ld' }, ({ tldType: _tldType, wrapLevel: _wrapLevel }) => { + // If wrapped eth 2ld without fuses set then it's desynced + if (wrapLevel === 'wrapped') return 'eth-desynced-2ld' as const return `${_tldType}-${_wrapLevel}-2ld` as const }) .with( diff --git a/src/hooks/nameType/useNameType.test.ts b/src/hooks/nameType/useNameType.test.ts index 33aa54a53..b0e8b9406 100644 --- a/src/hooks/nameType/useNameType.test.ts +++ b/src/hooks/nameType/useNameType.test.ts @@ -113,7 +113,7 @@ describe('useNameType', () => { it('should return wrapped', async () => { mockBasicData.mockReturnValue(makeMockUseBasicName('eth-wrapped-subname')) const { result } = renderHook(() => useNameType('test.eth')) - expect(result.current.data).toEqual('eth-wrapped-2ld') + expect(result.current.data).toEqual('eth-desynced-2ld') }) it('should return emancipated', async () => { diff --git a/src/hooks/ownership/useRoles/utils/getRoles.test.ts b/src/hooks/ownership/useRoles/utils/getRoles.test.ts index 21db2b3f6..071f045ec 100644 --- a/src/hooks/ownership/useRoles/utils/getRoles.test.ts +++ b/src/hooks/ownership/useRoles/utils/getRoles.test.ts @@ -23,7 +23,7 @@ describe('getRoles', () => { it('should return a list of roles for a wrapped 2LD', () => { expect( getRoles({ - nameType: 'eth-wrapped-2ld', + nameType: 'eth-desynced-2ld', registrant: '0xregistrant', owner: '0xowner', ethAddress: '0xethAddress', diff --git a/src/hooks/ownership/useRoles/utils/getRoles.ts b/src/hooks/ownership/useRoles/utils/getRoles.ts index 1d54b5b09..5de5d6f22 100644 --- a/src/hooks/ownership/useRoles/utils/getRoles.ts +++ b/src/hooks/ownership/useRoles/utils/getRoles.ts @@ -30,7 +30,7 @@ export const getRoles = ({ ethAddress?: Address }) => { return match(nameType) - .with(P.union('eth-wrapped-2ld', 'eth-wrapped-2ld:grace-period'), () => [ + .with(P.union('eth-desynced-2ld', 'eth-desynced-2ld:grace-period'), () => [ { address: owner, role: 'owner' as const }, { address: ethAddress, role: 'eth-record' as const }, ]) diff --git a/src/transaction-flow/input/SendName/utils/checkCanSend.ts b/src/transaction-flow/input/SendName/utils/checkCanSend.ts index 76e9c8aac..e4d9ba9dd 100644 --- a/src/transaction-flow/input/SendName/utils/checkCanSend.ts +++ b/src/transaction-flow/input/SendName/utils/checkCanSend.ts @@ -37,8 +37,8 @@ export const senderRole = (nameType: ReturnType['data']) => 'eth-emancipated-2ld:grace-period', 'eth-locked-2ld:grace-period', 'eth-unwrapped-2ld:grace-period', - 'eth-wrapped-2ld', - 'eth-wrapped-2ld:grace-period', + 'eth-synced-2ld', + 'eth-desynced-2ld:grace-period', ), () => null, ) diff --git a/src/transaction-flow/input/SyncManager/utils/checkCanSyncManager.ts b/src/transaction-flow/input/SyncManager/utils/checkCanSyncManager.ts index 67a038ab0..08d60be78 100644 --- a/src/transaction-flow/input/SyncManager/utils/checkCanSyncManager.ts +++ b/src/transaction-flow/input/SyncManager/utils/checkCanSyncManager.ts @@ -34,8 +34,8 @@ export const checkCanSyncManager = ({ 'eth-emancipated-2ld:grace-period', 'eth-locked-2ld', 'eth-locked-2ld:grace-period', - 'eth-wrapped-2ld', - 'eth-wrapped-2ld:grace-period', + 'eth-desynced-2ld', + 'eth-desynced-2ld:grace-period', 'eth-unwrapped-subname', 'eth-wrapped-subname', 'eth-emancipated-subname', From 2a16352357aadd826ac4605335f147e36af5f2a8 Mon Sep 17 00:00:00 2001 From: Leon Talbert Date: Sat, 19 Oct 2024 10:38:47 +0800 Subject: [PATCH 10/12] Register legacy refactor --- deploy/00_register_legacy.ts | 386 ++++++++++++++++++---------------- deploy/00_register_wrapped.ts | 31 +++ 2 files changed, 238 insertions(+), 179 deletions(-) diff --git a/deploy/00_register_legacy.ts b/deploy/00_register_legacy.ts index 886248ac9..e2275ca4c 100644 --- a/deploy/00_register_legacy.ts +++ b/deploy/00_register_legacy.ts @@ -2,11 +2,12 @@ /* eslint-disable no-await-in-loop */ import cbor from 'cbor' +import { Contract } from 'ethers' import { ethers } from 'hardhat' import { DeployFunction } from 'hardhat-deploy/types' import { HardhatRuntimeEnvironment } from 'hardhat/types' import pako from 'pako' -import { labelhash, namehash, stringToBytes } from 'viem' +import { Address, labelhash, namehash, stringToBytes } from 'viem' const dummyABI = [ { @@ -366,28 +367,15 @@ const names: Name[] = [ }, ] -const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { - const { getNamedAccounts, network } = hre - const allNamedAccts = await getNamedAccounts() - - const registry = await ethers.getContract('ENSRegistry') - const controller = await ethers.getContract('LegacyETHRegistrarController') - const publicResolver = await ethers.getContract('LegacyPublicResolver') - - const makeData = ({ - namedOwner, - namedController, - namedAddr, - customDuration, - subnames, - ...rest - }: Name) => { +const makeNameData = + (allNamedAccts: Record, publicResolverAddress: Address) => + ({ namedOwner, namedController, namedAddr, customDuration, subnames, ...rest }: Name) => { // eslint-disable-next-line no-restricted-syntax const secret = '0x0000000000000000000000000000000000000000000000000000000000000000' const registrant = allNamedAccts[namedOwner] const owner = namedController ? allNamedAccts[namedController] : undefined const addr = allNamedAccts[namedAddr] - const resolver = rest.resolver ?? publicResolver.address + const resolver = rest.resolver ?? publicResolverAddress const duration = customDuration || 31536000 return { @@ -402,201 +390,241 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { } } - const makeCommitment = - (nonce: number) => - async ( - { label, registrant, secret, resolver, addr }: ReturnType, - index: number, - ) => { - const commitment = await controller.makeCommitmentWithConfig( - label, - registrant, - secret, - resolver, - addr, - ) +const makeCommitment = + (controller: Contract) => + (nonce: number) => + async ( + { label, registrant, secret, resolver, addr }: ReturnType>, + index: number, + ) => { + const commitment = await controller.makeCommitmentWithConfig( + label, + registrant, + secret, + resolver, + addr, + ) - const _controller = controller.connect(await ethers.getSigner(registrant)) - const commitTx = await _controller.commit(commitment, { nonce: nonce + index }) - console.log(`Commiting commitment for ${label}.eth (tx: ${commitTx.hash})...`) + const _controller = controller.connect(await ethers.getSigner(registrant)) + const commitTx = await _controller.commit(commitment, { nonce: nonce + index }) + console.log(`Commiting commitment for ${label}.eth (tx: ${commitTx.hash})...`) - return 1 - } + return 1 + } - const makeRegistration = - (nonce: number) => - async ( - { label, registrant, secret, resolver, addr, duration }: ReturnType, - index: number, - ) => { - const price = await controller.rentPrice(label, duration) - - const _controller = controller.connect(await ethers.getSigner(registrant)) - - const registerTx = await _controller.registerWithConfig( - label, - registrant, - duration, - secret, - resolver, - addr, - { - value: price, - nonce: nonce + index, - }, - ) - console.log(`Registering name ${label}.eth (tx: ${registerTx.hash})...`) +const makeRegistration = + (controller: Contract) => + (nonce: number) => + async ( + { + label, + registrant, + secret, + resolver, + addr, + duration, + }: ReturnType>, + index: number, + ) => { + const price = await controller.rentPrice(label, duration) - return 1 - } + const _controller = controller.connect(await ethers.getSigner(registrant)) - const makeRecords = - (nonce: number) => - async ( - { label, records: _records, registrant }: ReturnType, - index: number, - ) => { - const records = _records! - let nonceRef = nonce + index - const _publicResolver = publicResolver.connect(await ethers.getSigner(registrant)) - - const hash = namehash(`${label}.eth`) - console.log(`Setting records for ${label}.eth...`) - if (records.text) { - console.log('TEXT') - for (const { key, value } of records.text) { - const setTextTx = await _publicResolver.setText(hash, key, value, { nonce: nonceRef }) - console.log(` - ${key} ${value} (tx: ${setTextTx.hash})...`) - nonceRef += 1 - } - } - if (records.addr) { - console.log('ADDR') - for (const { key, value } of records.addr) { - const setAddrTx = await _publicResolver['setAddr(bytes32,uint256,bytes)']( - hash, - key, - value, - { - nonce: nonceRef, - }, - ) - console.log(` - ${key} ${value} (tx: ${setAddrTx.hash})...`) - nonceRef += 1 - } - } - if (records.contenthash) { - console.log('CONTENTHASH') - const setContenthashTx = await _publicResolver.setContenthash(hash, records.contenthash, { - nonce: nonceRef, - }) - console.log(` - ${records.contenthash} (tx: ${setContenthashTx.hash})...`) + const registerTx = await _controller.registerWithConfig( + label, + registrant, + duration, + secret, + resolver, + addr, + { + value: price, + nonce: nonce + index, + }, + ) + console.log(`Registering name ${label}.eth (tx: ${registerTx.hash})...`) + + return 1 + } + +const makeRecords = + (publicResolver: Contract) => + (nonce: number) => + async ( + { label, records: _records, registrant }: ReturnType>, + index: number, + ) => { + const records = _records! + let nonceRef = nonce + index + const _publicResolver = publicResolver.connect(await ethers.getSigner(registrant)) + + const hash = namehash(`${label}.eth`) + console.log(`Setting records for ${label}.eth...`) + if (records.text) { + console.log('TEXT') + for (const { key, value } of records.text) { + const setTextTx = await _publicResolver.setText(hash, key, value, { nonce: nonceRef }) + console.log(` - ${key} ${value} (tx: ${setTextTx.hash})...`) nonceRef += 1 } - if (records.abi) { - const abis = Array.isArray(records.abi) ? records.abi : [records.abi] - for (const abi of abis) { - console.log('ABI') - const { contentType, data } = abi - let data_ - if (contentType === 1) data_ = stringToBytes(JSON.stringify(data)) - else if (contentType === 2) data_ = pako.deflate(JSON.stringify(abi.data)) - else if (contentType === 4) data_ = cbor.encode(abi.data) - else data_ = stringToBytes(data) - const setAbiTx = await _publicResolver.setABI(hash, contentType, data_, { - nonce: nonceRef, - }) - console.log(` - ${records.abi} (tx: ${setAbiTx.hash})...`) - nonceRef += 1 - } - } - return nonceRef - nonce - index } - - const makeSubnames = - (nonce: number) => - async ( - { label, subnames, registrant, resolver }: ReturnType, - index: number, - ) => { - if (!subnames) return 0 - for (let i = 0; i < subnames.length; i += 1) { - const { label: subnameLabel, namedOwner: namedSubOwner } = subnames[i] - const subOwner = allNamedAccts[namedSubOwner] - const _registry = registry.connect(await ethers.getSigner(registrant)) - const subnameTx = await _registry.setSubnodeRecord( - namehash(`${label}.eth`), - labelhash(subnameLabel), - subOwner, - resolver, - 0, + if (records.addr) { + console.log('ADDR') + for (const { key, value } of records.addr) { + const setAddrTx = await _publicResolver['setAddr(bytes32,uint256,bytes)']( + hash, + key, + value, { - nonce: nonce + index + i, + nonce: nonceRef, }, ) - console.log(`Creating subname ${subnameLabel}.${label}.eth (tx: ${subnameTx.hash})...`) + console.log(` - ${key} ${value} (tx: ${setAddrTx.hash})...`) + nonceRef += 1 } - return subnames.length } - - const makeController = - (nonce: number) => - async ({ label, owner, registrant }: ReturnType, index: number) => { - const _registry = registry.connect(await ethers.getSigner(registrant)) - const setControllerTx = await _registry.setOwner(namehash(`${label}.eth`), owner, { - nonce: nonce + index, + if (records.contenthash) { + console.log('CONTENTHASH') + const setContenthashTx = await _publicResolver.setContenthash(hash, records.contenthash, { + nonce: nonceRef, }) - console.log( - `Setting controller for ${label}.eth to ${owner} (tx: ${setControllerTx.hash})...`, - ) - - return 1 + console.log(` - ${records.contenthash} (tx: ${setContenthashTx.hash})...`) + nonceRef += 1 } + if (records.abi) { + const abis = Array.isArray(records.abi) ? records.abi : [records.abi] + for (const abi of abis) { + console.log('ABI') + const { contentType, data } = abi + let data_ + if (contentType === 1) data_ = stringToBytes(JSON.stringify(data)) + else if (contentType === 2) data_ = pako.deflate(JSON.stringify(abi.data)) + else if (contentType === 4) data_ = cbor.encode(abi.data) + else data_ = stringToBytes(data) + const setAbiTx = await _publicResolver.setABI(hash, contentType, data_, { + nonce: nonceRef, + }) + console.log(` - ${records.abi} (tx: ${setAbiTx.hash})...`) + nonceRef += 1 + } + } + return nonceRef - nonce - index + } - const allNameData = names.map(makeData) +const makeController = + (registry: Contract) => + (nonce: number) => + async ( + { label, owner, registrant }: ReturnType>, + index: number, + ) => { + const _registry = registry.connect(await ethers.getSigner(registrant)) + const setControllerTx = await _registry.setOwner(namehash(`${label}.eth`), owner, { + nonce: nonce + index, + }) + console.log(`Setting controller for ${label}.eth to ${owner} (tx: ${setControllerTx.hash})...`) - const getNonceAndApply = async ( - property: keyof ReturnType, - _func: typeof makeCommitment, - filter?: (data: ReturnType) => boolean, - nonceMap?: Record, + return 1 + } + +const makeSubnames = + (allNamedAccts: Record, registry: Contract) => + (nonce: number) => + async ( + { label, subnames, registrant, resolver }: ReturnType>, + index: number, ) => { - const newNonceMap = nonceMap || {} - for (const account of Object.values(allNamedAccts)) { - const namesWithAccount = allNameData.filter( - (data) => data[property] === account && (filter ? filter(data) : true), + if (!subnames) return 0 + for (let i = 0; i < subnames.length; i += 1) { + const { label: subnameLabel, namedOwner: namedSubOwner } = subnames[i] + const subOwner = allNamedAccts[namedSubOwner] + const _registry = registry.connect(await ethers.getSigner(registrant)) + const subnameTx = await _registry.setSubnodeRecord( + namehash(`${label}.eth`), + labelhash(subnameLabel), + subOwner, + resolver, + 0, + { + nonce: nonce + index + i, + }, ) - if (!newNonceMap[account]) { - const nonce = await ethers.provider.getTransactionCount(account) - newNonceMap[account] = nonce - } - let usedNonces = 0 + console.log(`Creating subname ${subnameLabel}.${label}.eth (tx: ${subnameTx.hash})...`) + } + return subnames.length + } - for (let i = 0; i < namesWithAccount.length; i += 1) { - const data = namesWithAccount[i] - usedNonces += await _func(newNonceMap[account])(data, usedNonces) - } - newNonceMap[account] += usedNonces +const getNonceAndApply = async ( + property: keyof ReturnType, + _func: typeof makeCommitment, + allNamedAccts: Record, + allNameData: ReturnType[], + filter?: (data: ReturnType) => boolean, + nonceMap?: Record, +) => { + const newNonceMap = nonceMap || {} + + for (const account of Object.values(allNamedAccts)) { + const namesWithAccount = allNameData.filter( + (data) => data[property] === account && (filter ? filter(data) : true), + ) + if (!newNonceMap[account]) { + const nonce = await ethers.provider.getTransactionCount(account) + newNonceMap[account] = nonce } - return newNonceMap + let usedNonces = 0 + + for (let i = 0; i < namesWithAccount.length; i += 1) { + const data = namesWithAccount[i] + usedNonces += await _func(newNonceMap[account])(data, usedNonces) + } + newNonceMap[account] += usedNonces } + return newNonceMap +} + +const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { + const { getNamedAccounts, network } = hre + const allNamedAccts = await getNamedAccounts() + + const registry = await ethers.getContract('ENSRegistry') + const controller = await ethers.getContract('LegacyETHRegistrarController') + const publicResolver = await ethers.getContract('LegacyPublicResolver') + + const allNameData = names.map(makeNameData(allNamedAccts, publicResolver.address)) + await network.provider.send('evm_setAutomine', [false]) - await getNonceAndApply('registrant', makeCommitment) + await getNonceAndApply('registrant', makeCommitment(controller), allNamedAccts, allNameData) await network.provider.send('evm_mine') const oldTimestamp = (await ethers.provider.getBlock('latest')).timestamp await network.provider.send('evm_setNextBlockTimestamp', [oldTimestamp + 60]) await network.provider.send('evm_mine') - await getNonceAndApply('registrant', makeRegistration) + await getNonceAndApply('registrant', makeRegistration(controller), allNamedAccts, allNameData) await network.provider.send('evm_mine') - const tempNonces = await getNonceAndApply('registrant', makeRecords, (data) => !!data.records) + const tempNonces = await getNonceAndApply( + 'registrant', + makeRecords(publicResolver), + allNamedAccts, + allNameData, + (data) => !!data.records, + ) const tempNonces2 = await getNonceAndApply( 'registrant', - makeController, + makeController(registry), + allNamedAccts, + allNameData, (data) => !!data.owner, tempNonces, ) - await getNonceAndApply('registrant', makeSubnames, (data) => !!data.subnames, tempNonces2) + await getNonceAndApply( + 'registrant', + makeSubnames(allNamedAccts, registry), + allNamedAccts, + allNameData, + (data) => !!data.subnames, + tempNonces2, + ) await network.provider.send('evm_mine') // Skip forward 28 + 90 days so that minimum exp names go into premium diff --git a/deploy/00_register_wrapped.ts b/deploy/00_register_wrapped.ts index 87a967d7b..cdbc2cefc 100644 --- a/deploy/00_register_wrapped.ts +++ b/deploy/00_register_wrapped.ts @@ -137,6 +137,11 @@ const names: Name[] = [ }, ], }, + { + name: 'desynced.eth', + namedOwner: 'owner', + customDuration: 2419200, + }, ] type ProcessedNameData = RegistrationParameters & { @@ -256,6 +261,32 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { await network.provider.send('anvil_setBlockTimestampInterval', [1]) await network.provider.send('evm_mine') + /* + await network.provider.send('evm_setAutomine', [false]) + // Skip forward 28 + 90 days so that minimum exp names go into premium + await network.provider.send('anvil_setBlockTimestampInterval', [2419200 + 7776000]) + await network.provider.send('evm_mine') + await network.provider.send('evm_setAutomine', [true]) + await network.provider.send('anvil_setBlockTimestampInterval', [1]) + await network.provider.send('evm_mine') + */ + + /* + // Renew desynced.eth with the legacy ETH registrar + const legacyETHRegistrarController = await ethers.getContract('LegacyETHRegistrarController') + const owner = allNamedAccts.owner + const _legacyETHRegistrarController = legacyETHRegistrarController.connect( + await ethers.getSigner(owner), + ) + + const label = 'desynced' + const duration = 31536000 // 1 year in seconds + + const renewTx = await _legacyETHRegistrarController.renew(label, duration) + console.log(`Renewing desynced.eth for 1 year (tx: ${renewTx.hash})...`) + await renewTx.wait() + */ + return true } From 0133a9642c3ba4a632fc982402c4cad6f3253dd4 Mon Sep 17 00:00:00 2001 From: Leon Talbert Date: Sat, 19 Oct 2024 11:36:46 +0800 Subject: [PATCH 11/12] Extract wrapped registration helpers --- deploy/.utils/wrappedNameHelpers.ts | 39 +++++++++++++++++++++ deploy/00_register_legacy.ts | 25 +++++++++++--- deploy/00_register_wrapped.ts | 53 +++-------------------------- 3 files changed, 64 insertions(+), 53 deletions(-) create mode 100644 deploy/.utils/wrappedNameHelpers.ts diff --git a/deploy/.utils/wrappedNameHelpers.ts b/deploy/.utils/wrappedNameHelpers.ts new file mode 100644 index 000000000..58d3700fd --- /dev/null +++ b/deploy/.utils/wrappedNameHelpers.ts @@ -0,0 +1,39 @@ +import type { Contract } from 'ethers' +import { ethers } from 'hardhat' + +import { + makeCommitment as generateCommitment, + makeRegistrationTuple, +} from '@ensdomains/ensjs/utils' + +export const makeWrappedCommitment = + (controller: Contract) => + (nonce: number) => + async ({ owner, name, ...rest }: ProcessedNameData, index: number) => { + const commitment = generateCommitment({ owner, name, ...rest }) + + const _controller = controller.connect(await ethers.getSigner(owner)) + const commitTx = await _controller.commit(commitment, { nonce: nonce + index }) + console.log(`Commiting commitment for ${name} (tx: ${commitTx.hash})...`) + return 1 + } + +export const makeWrappedRegistration = + (controller: Contract) => + (nonce: number) => + async ({ owner, name, duration, label, ...rest }: ProcessedNameData, index: number) => { + const [price] = await controller.rentPrice(label, duration) + + const _controller = controller.connect(await ethers.getSigner(owner)) + + const registerTx = await _controller.register( + ...makeRegistrationTuple({ owner, name, duration, ...rest }), + { + value: price, + nonce: nonce + index, + }, + ) + console.log(`Registering name ${name} (tx: ${registerTx.hash})...`) + + return 1 + } diff --git a/deploy/00_register_legacy.ts b/deploy/00_register_legacy.ts index e2275ca4c..faf85ba54 100644 --- a/deploy/00_register_legacy.ts +++ b/deploy/00_register_legacy.ts @@ -589,18 +589,24 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const allNamedAccts = await getNamedAccounts() const registry = await ethers.getContract('ENSRegistry') - const controller = await ethers.getContract('LegacyETHRegistrarController') + const legacyController = await ethers.getContract('LegacyETHRegistrarController') + // const controller = await ethers.getContract('ETHRegistrarController') const publicResolver = await ethers.getContract('LegacyPublicResolver') const allNameData = names.map(makeNameData(allNamedAccts, publicResolver.address)) await network.provider.send('evm_setAutomine', [false]) - await getNonceAndApply('registrant', makeCommitment(controller), allNamedAccts, allNameData) + await getNonceAndApply('registrant', makeCommitment(legacyController), allNamedAccts, allNameData) await network.provider.send('evm_mine') const oldTimestamp = (await ethers.provider.getBlock('latest')).timestamp await network.provider.send('evm_setNextBlockTimestamp', [oldTimestamp + 60]) await network.provider.send('evm_mine') - await getNonceAndApply('registrant', makeRegistration(controller), allNamedAccts, allNameData) + await getNonceAndApply( + 'registrant', + makeRegistration(legacyController), + allNamedAccts, + allNameData, + ) await network.provider.send('evm_mine') const tempNonces = await getNonceAndApply( 'registrant', @@ -627,6 +633,17 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { ) await network.provider.send('evm_mine') + //Register wrapped names + // await getNonceAndApply('owner', makeCommitment(controller, allNamedAccts, allNameData)) + // await network.provider.send('evm_mine') + // const oldTimestamp = (await ethers.provider.getBlock('latest')).timestamp + // await network.provider.send('evm_setNextBlockTimestamp', [oldTimestamp + 60]) + // await network.provider.send('evm_mine') + // await getNonceAndApply('owner', makeRegistration(controller)) + // await network.provider.send('evm_mine') + // await getNonceAndApply('owner', makeSubname) + // await network.provider.send('evm_mine') + // Skip forward 28 + 90 days so that minimum exp names go into premium await network.provider.send('anvil_setBlockTimestampInterval', [2419200 + 7776000]) await network.provider.send('evm_mine') @@ -653,7 +670,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { func.id = 'register-unwrapped-names' func.tags = ['register-unwrapped-names'] -func.dependencies = ['LegacyETHRegistrarController'] +func.dependencies = ['LegacyETHRegistrarController', 'ETHRegistrarController'] func.runAtTheEnd = true export default func diff --git a/deploy/00_register_wrapped.ts b/deploy/00_register_wrapped.ts index cdbc2cefc..8336ef861 100644 --- a/deploy/00_register_wrapped.ts +++ b/deploy/00_register_wrapped.ts @@ -6,15 +6,10 @@ import { DeployFunction } from 'hardhat-deploy/types' import { HardhatRuntimeEnvironment } from 'hardhat/types' import { Address, namehash } from 'viem' -import { - encodeFuses, - makeCommitment as generateCommitment, - makeRegistrationTuple, - RecordOptions, - RegistrationParameters, -} from '@ensdomains/ensjs/utils' +import { encodeFuses, RecordOptions, RegistrationParameters } from '@ensdomains/ensjs/utils' import { nonceManager } from './.utils/nonceManager' +import { makeWrappedCommitment, makeWrappedRegistration } from './.utils/wrappedNameHelpers' type Name = { name: string @@ -191,36 +186,6 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { } } - const makeCommitment = - (nonce: number) => - async ({ owner, name, ...rest }: ProcessedNameData, index: number) => { - const commitment = generateCommitment({ owner, name, ...rest }) - - const _controller = controller.connect(await ethers.getSigner(owner)) - const commitTx = await _controller.commit(commitment, { nonce: nonce + index }) - console.log(`Commiting commitment for ${name} (tx: ${commitTx.hash})...`) - return 1 - } - - const makeRegistration = - (nonce: number) => - async ({ owner, name, duration, label, ...rest }: ProcessedNameData, index: number) => { - const [price] = await controller.rentPrice(label, duration) - - const _controller = controller.connect(await ethers.getSigner(owner)) - - const registerTx = await _controller.register( - ...makeRegistrationTuple({ owner, name, duration, ...rest }), - { - value: price, - nonce: nonce + index, - }, - ) - console.log(`Registering name ${name} (tx: ${registerTx.hash})...`) - - return 1 - } - const makeSubname = (nonce: number) => async ({ name, subnames, owner }: ProcessedNameData, index: number) => { @@ -247,12 +212,12 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const getNonceAndApply = nonceManager(ethers, allNamedAccts, allNameData) await network.provider.send('evm_setAutomine', [false]) - await getNonceAndApply('owner', makeCommitment) + await getNonceAndApply('owner', makeWrappedCommitment(controller)) await network.provider.send('evm_mine') const oldTimestamp = (await ethers.provider.getBlock('latest')).timestamp await network.provider.send('evm_setNextBlockTimestamp', [oldTimestamp + 60]) await network.provider.send('evm_mine') - await getNonceAndApply('owner', makeRegistration) + await getNonceAndApply('owner', makeWrappedRegistration(controller)) await network.provider.send('evm_mine') await getNonceAndApply('owner', makeSubname) await network.provider.send('evm_mine') @@ -261,16 +226,6 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { await network.provider.send('anvil_setBlockTimestampInterval', [1]) await network.provider.send('evm_mine') - /* - await network.provider.send('evm_setAutomine', [false]) - // Skip forward 28 + 90 days so that minimum exp names go into premium - await network.provider.send('anvil_setBlockTimestampInterval', [2419200 + 7776000]) - await network.provider.send('evm_mine') - await network.provider.send('evm_setAutomine', [true]) - await network.provider.send('anvil_setBlockTimestampInterval', [1]) - await network.provider.send('evm_mine') - */ - /* // Renew desynced.eth with the legacy ETH registrar const legacyETHRegistrarController = await ethers.getContract('LegacyETHRegistrarController') From 39e52e38dfcd5b0fcabf37957f33db9cc4e2d49c Mon Sep 17 00:00:00 2001 From: Leon Talbert Date: Sat, 19 Oct 2024 16:40:08 +0800 Subject: [PATCH 12/12] Desynced name --- deploy/.utils/wrappedNameHelpers.ts | 54 +++++++++++++++++++++++ deploy/00_register_legacy.ts | 68 +++++++++++++++++++++++------ deploy/00_register_wrapped.ts | 47 +++----------------- 3 files changed, 114 insertions(+), 55 deletions(-) diff --git a/deploy/.utils/wrappedNameHelpers.ts b/deploy/.utils/wrappedNameHelpers.ts index 58d3700fd..c91884c2d 100644 --- a/deploy/.utils/wrappedNameHelpers.ts +++ b/deploy/.utils/wrappedNameHelpers.ts @@ -1,11 +1,25 @@ import type { Contract } from 'ethers' import { ethers } from 'hardhat' +import type { Address } from 'viem' import { makeCommitment as generateCommitment, makeRegistrationTuple, + RegistrationParameters, } from '@ensdomains/ensjs/utils' +type ProcessedSubname = { + label: string + owner: Address + expiry: number + fuses: number +} + +type ProcessedNameData = RegistrationParameters & { + label: string + subnames: ProcessedSubname[] +} + export const makeWrappedCommitment = (controller: Contract) => (nonce: number) => @@ -18,6 +32,12 @@ export const makeWrappedCommitment = return 1 } +export const makeWrappedRenew = + (controller: Contract) => (nonce: number) => async (name: string, duration: number) => { + const [price] = await controller.rentPrice(name, duration) + return price + } + export const makeWrappedRegistration = (controller: Contract) => (nonce: number) => @@ -37,3 +57,37 @@ export const makeWrappedRegistration = return 1 } + +export const makeWrappedData = + (resolverAddress, allNamedAccts) => + ({ namedOwner, customDuration, fuses, name, subnames, ...rest }: Name) => { + const secret = + // eslint-disable-next-line no-restricted-syntax + '0x0000000000000000000000000000000000000000000000000000000000000000' as Address + const duration = customDuration || 31536000 + // 1659467455 is the approximate time of the transaction, this is for keeping block hashes the same + const wrapperExpiry = 1659467455 + duration + const owner = allNamedAccts[namedOwner] + + const processedSubnames: ProcessedSubname[] = + subnames?.map( + ({ label, namedOwner: subNamedOwner, fuses: subnameFuses, expiry: subnameExpiry }) => ({ + label, + owner: allNamedAccts[subNamedOwner], + expiry: subnameExpiry || wrapperExpiry, + fuses: subnameFuses || 0, + }), + ) || [] + + return { + resolverAddress, + secret, + duration, + owner, + name, + label: name.split('.')[0], + subnames: processedSubnames, + fuses: fuses || undefined, + ...rest, + } + } diff --git a/deploy/00_register_legacy.ts b/deploy/00_register_legacy.ts index faf85ba54..24ec379c0 100644 --- a/deploy/00_register_legacy.ts +++ b/deploy/00_register_legacy.ts @@ -9,6 +9,12 @@ import { HardhatRuntimeEnvironment } from 'hardhat/types' import pako from 'pako' import { Address, labelhash, namehash, stringToBytes } from 'viem' +import { + makeWrappedCommitment, + makeWrappedData, + makeWrappedRegistration, +} from './.utils/wrappedNameHelpers' + const dummyABI = [ { type: 'event', @@ -587,10 +593,9 @@ const getNonceAndApply = async ( const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const { getNamedAccounts, network } = hre const allNamedAccts = await getNamedAccounts() - const registry = await ethers.getContract('ENSRegistry') const legacyController = await ethers.getContract('LegacyETHRegistrarController') - // const controller = await ethers.getContract('ETHRegistrarController') + const controller = await ethers.getContract('ETHRegistrarController') const publicResolver = await ethers.getContract('LegacyPublicResolver') const allNameData = names.map(makeNameData(allNamedAccts, publicResolver.address)) @@ -633,14 +638,49 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { ) await network.provider.send('evm_mine') - //Register wrapped names - // await getNonceAndApply('owner', makeCommitment(controller, allNamedAccts, allNameData)) - // await network.provider.send('evm_mine') - // const oldTimestamp = (await ethers.provider.getBlock('latest')).timestamp - // await network.provider.send('evm_setNextBlockTimestamp', [oldTimestamp + 60]) - // await network.provider.send('evm_mine') - // await getNonceAndApply('owner', makeRegistration(controller)) - // await network.provider.send('evm_mine') + const wrappedNames = [ + { + name: 'desynced.eth', + namedOwner: 'owner', + customDuration: 2419200, + }, + ] + + // Register desynced name ------------------------- + const wrappedFunctionData = wrappedNames.map( + makeWrappedData(publicResolver.address, allNamedAccts), + ) + console.log('****wrappedFunctionData', wrappedFunctionData) + await getNonceAndApply( + 'owner', + makeWrappedCommitment(controller), + allNamedAccts, + wrappedFunctionData, + ) + await network.provider.send('evm_mine') + await network.provider.send('evm_setNextBlockTimestamp', [ + (await ethers.provider.getBlock('latest')).timestamp + 60, + ]) + await network.provider.send('evm_mine') + await getNonceAndApply( + 'owner', + makeWrappedRegistration(controller), + allNamedAccts, + wrappedFunctionData, + ) + await network.provider.send('evm_mine') + + //Extend desynced.eth on the legacy controller + const [price] = await controller.rentPrice('desynced', 31556952) + const legacyControllerConnected = legacyController.connect( + await ethers.getSigner(allNamedAccts.owner), + ) + await legacyControllerConnected.renew('desynced', 31556952, { + value: price, + }) + + //--------------------------------- + // await getNonceAndApply('owner', makeSubname) // await network.provider.send('evm_mine') @@ -668,9 +708,9 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { return true } -func.id = 'register-unwrapped-names' -func.tags = ['register-unwrapped-names'] -func.dependencies = ['LegacyETHRegistrarController', 'ETHRegistrarController'] -func.runAtTheEnd = true +func.id = 'register-legacy-names' +func.tags = ['register-legacy-names'] +func.dependencies = ['ETHRegistrarController'] +// func.runAtTheEnd = true export default func diff --git a/deploy/00_register_wrapped.ts b/deploy/00_register_wrapped.ts index 8336ef861..8abe864dd 100644 --- a/deploy/00_register_wrapped.ts +++ b/deploy/00_register_wrapped.ts @@ -9,7 +9,11 @@ import { Address, namehash } from 'viem' import { encodeFuses, RecordOptions, RegistrationParameters } from '@ensdomains/ensjs/utils' import { nonceManager } from './.utils/nonceManager' -import { makeWrappedCommitment, makeWrappedRegistration } from './.utils/wrappedNameHelpers' +import { + makeWrappedCommitment, + makeWrappedData, + makeWrappedRegistration, +} from './.utils/wrappedNameHelpers' type Name = { name: string @@ -132,11 +136,6 @@ const names: Name[] = [ }, ], }, - { - name: 'desynced.eth', - namedOwner: 'owner', - customDuration: 2419200, - }, ] type ProcessedNameData = RegistrationParameters & { @@ -152,40 +151,6 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const publicResolver = await ethers.getContract('PublicResolver') const nameWrapper = await ethers.getContract('NameWrapper') - const makeData = ({ namedOwner, customDuration, fuses, name, subnames, ...rest }: Name) => { - const resolverAddress = publicResolver.address as Address - - const secret = - // eslint-disable-next-line no-restricted-syntax - '0x0000000000000000000000000000000000000000000000000000000000000000' as Address - const duration = customDuration || 31536000 - // 1659467455 is the approximate time of the transaction, this is for keeping block hashes the same - const wrapperExpiry = 1659467455 + duration - const owner = allNamedAccts[namedOwner] - - const processedSubnames: ProcessedSubname[] = - subnames?.map( - ({ label, namedOwner: subNamedOwner, fuses: subnameFuses, expiry: subnameExpiry }) => ({ - label, - owner: allNamedAccts[subNamedOwner], - expiry: subnameExpiry || wrapperExpiry, - fuses: subnameFuses || 0, - }), - ) || [] - - return { - resolverAddress, - secret, - duration, - owner, - name, - label: name.split('.')[0], - subnames: processedSubnames, - fuses: fuses || undefined, - ...rest, - } - } - const makeSubname = (nonce: number) => async ({ name, subnames, owner }: ProcessedNameData, index: number) => { @@ -207,7 +172,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { return subnames.length } - const allNameData = names.map(makeData) + const allNameData = names.map(makeWrappedData(publicResolver.address, allNamedAccts)) const getNonceAndApply = nonceManager(ethers, allNamedAccts, allNameData)