Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(console): add ens domains #88

Closed

Conversation

developerfred
Copy link

@developerfred developerfred commented May 25, 2022

close #87

using:

@vercel
Copy link

vercel bot commented May 25, 2022

@developerfred is attempting to deploy a commit to the Superfluid Finance Team on Vercel.

A member of the Team first needs to authorize it.

@developerfred developerfred marked this pull request as ready for review May 25, 2022 19:11
@developerfred developerfred changed the title feat(console): add ens lockup on redux slice [WIP] feat(console): add ens lockup on redux slice May 25, 2022
@developerfred developerfred changed the title feat(console): add ens lockup on redux slice feat(console): add ens domains May 25, 2022
const store = configureStore({
reducer: {
[rpcApi.reducerPath]: rpcApi.reducer,
[sfSubgraph.reducerPath]: sfSubgraph.reducer,
[themePreferenceSlice.name]: themePreferenceSlice.reducer,
[addressBookSlice.name]: addressBookReducer
[addressBookSlice.name]: addressBookReducer,
[ensApi.reducerPath]: ensReducer,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see much value in persisting ENS queries so we can just do [ensApi.reducerPath]: ensApi.reducer, here.

This part can be removed:

  const ensReducer = persistReducer(
    { key: "ens-lockup", version: 1, storage: storageLocal },
    ensApi.reducer
  )

return { data: null };
}

if (!name.endsWith('.eth')) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach is flawed: "The ENS namespace includes both .eth names (which are native to ENS) and DNS names imported into ENS. Because the DNS suffix namespace expands over time, a hardcoded list of name suffixes for recognizing ENS names will regularly be out of date, leading to your application not recognizing all valid ENS names. To remain future-proof, a correct integration of ENS treats any dot-separated name as a potential ENS name and will attempt a look-up." (source: https://docs.ens.domains/dapp-developer-guide/resolving-names)

We can do an early return when there's no "." in the given name but ".eth" is too specific.

src/redux/slices/ensResolver.slice.ts Outdated Show resolved Hide resolved
src/redux/slices/ensResolver.slice.ts Outdated Show resolved Hide resolved
src/pages/[_network]/accounts/[_id].tsx Outdated Show resolved Hide resolved
src/pages/[_network]/accounts/[_id].tsx Outdated Show resolved Hide resolved
src/hooks/useSearchSubgraphByTokenSymbol.ts Outdated Show resolved Hide resolved
src/components/AddressBook.tsx Outdated Show resolved Hide resolved
src/redux/slices/addressBook.slice.ts Outdated Show resolved Hide resolved
address
)

const getInitialNameTag = () => ensQuery.data?.name ?? existingEntry?.nameTag ?? "";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the custom name tag should take higher priority over ENS one.

return networks.map((network) => {
const addressBookEntries = useAppSelector((state) =>
searchTerm !== "" && !isSearchTermAddress
searchTerm !== "" && !isSearchTermAddress && !ensQuery
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still some !ensQuery-like calls in the code. This can never be correct because it's never going to be null or undefined. Either do !ensQuery.data or use any of the RTK-Query loading states: https://redux-toolkit.js.org/rtk-query/usage/queries#frequently-used-query-hook-return-values

searchTerm
)

const ensSearchTerm = ensQuery !== null ? ensQuery.address : searchTerm.toLocaleLowerCase()
Copy link
Collaborator

@kasparkallas kasparkallas May 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit confused -- shouldn't ensQuery.address be ensQuery.data?.address? If so, please run yarn build before pushing any code because this project uses TypeScript and it would catch such type errors straight away. An IDE like Visual Studio Code would also show this as an error.

@vmichalik
Copy link
Contributor

Hey @developerfred, any progress with this? Let us know if you have any follow up questions. We may move this one to a public bounty in the coming weeks otherwise.

@developerfred
Copy link
Author

@vmichalik I'm going back to work on the task, I had some impediments but I'm back.

@kasparkallas
Copy link
Collaborator

kasparkallas commented Jun 17, 2022

Hey @developerfred

We developed the ENS support for Dashboard v2. I can the snippets of code you can reverse-engineer to the Console if you like (you don't need to leave in anything that's not actually used in the Console). If you continue the development then I'd prefer if you started a new PR and used an IDE that catches TypeScript errors (or just run yarn build before commiting/pushing).

Here are the snippets:

ensApi.slice.ts

import { fakeBaseQuery } from "@reduxjs/toolkit/dist/query";
import { createApi } from "@reduxjs/toolkit/dist/query/react";
import { ethers } from "ethers";

export interface ResolveNameResult {
  address: string;
  name: string;
}

export const ensApi = createApi({
  reducerPath: "ens",
  baseQuery: fakeBaseQuery(),
  keepUnusedDataFor: 600, // Agressively cache the ENS queries
  endpoints: (builder) => {
    const mainnetProvider = new ethers.providers.FallbackProvider(
      [
        {
          provider: new ethers.providers.JsonRpcBatchProvider(
            "https://rpc-endpoints.superfluid.dev/eth-mainnet",
            "mainnet"
          ),
          priority: 1,
        },
        {
          provider: new ethers.providers.JsonRpcBatchProvider(
            "https://cloudflare-eth.com",
            "mainnet"
          ),
          priority: 2,
        },
      ],
      1
    );
    return {
      resolveName: builder.query<ResolveNameResult | null, string>({
        queryFn: async (name) => {
          if (!name.includes(".")) {
            return { data: null };
          }

          const address = await mainnetProvider.resolveName(name);
          return {
            data: address
              ? {
                  name,
                  address: address,
                }
              : null,
          };
        },
      }),
      lookupAddress: builder.query<
        { address: string; name: string } | null,
        string
      >({
        queryFn: async (address) => {
          const name = await mainnetProvider.lookupAddress(address);
          return {
            data: name
              ? {
                  name,
                  address: ethers.utils.getAddress(address),
                }
              : null,
          };
        },
      }),
      getAvatar: builder.query<any, string>({
        queryFn: async (address) => {
          const avatarUrl = await mainnetProvider.getAvatar(address);
          return {
            data: avatarUrl,
          };
        },
      }),
    };
  },
});

AddressName.ts (All addresses are displayed through this component.)

import { memo } from "react";
import useAddressName from "../../hooks/useAddressName";
import shortenHex from "../../utils/shortenHex";

export interface AddressNameProps {
  address: string;
  length?: "short" | "medium" | "long";
}

export default memo(function AddressName({
  address,
  length = "short",
}: AddressNameProps) {
  const addressName = useAddressName(address);
  if (addressName.name) {
    return <>{addressName.name}</>;
  } else {
    return (
      <>
        {length === "long"
          ? addressName.addressChecksummed
          : shortenHex(
              addressName.addressChecksummed,
              length === "medium" ? 8 : 4
            )}
      </>
    );
  }
});

AddressAvatar.tsx

import { Avatar, AvatarProps } from "@mui/material";
import { memo } from "react";
import { ensApi } from "../../features/ens/ensApi.slice";
import Blockies from "react-blockies";
import { getAddress } from "../../utils/memoizedEthersUtils";

interface AddressAvatarProps {
  address: string;
  AvatarProps?: AvatarProps;
}

export default memo(function AddressAvatar({
  address,
  AvatarProps = {},
}: AddressAvatarProps) {
  const { data: ensAvatarUrl } = ensApi.useGetAvatarQuery(address);
  if (ensAvatarUrl) {
    return (
      <Avatar
        alt="ens avatar"
        variant="rounded"
        src={ensAvatarUrl}
        {...AvatarProps}
      />
    );
  } else {
    return (
      <Avatar alt="generated blockie avatar" variant="rounded" {...AvatarProps}>
        {/* Read about the Blockies API here: https://github.com/stephensprinkle-zz/react-blockies */}
        <Blockies seed={getAddress(address)} size={12} scale={3} />
      </Avatar>
    );
  }
});

useAddressName.tsx (with an address book, the address book's name would take precedence over ENS)

import { ensApi } from "../features/ens/ensApi.slice";
import { getAddress } from "../utils/memoizedEthersUtils";

interface AddressNameResult {
  addressChecksummed: string;
  name: string | "";
}

const useAddressName = (address: string): AddressNameResult => {
  const ensLookupQuery = ensApi.useLookupAddressQuery(address);
  return {
    addressChecksummed: getAddress(address),
    name: ensLookupQuery.data?.name ?? "",
  };
};

export default useAddressName;

I think that's about it.

NOTE: "shortenHex" is the same as "shortenAddress" in the Console.

@developerfred
Copy link
Author

Ok @kasparkallas thanks!

@developerfred
Copy link
Author

@kasparkallas
I have some doubts about the function below, is it just an ethers.getAddress() or is there something else involved?
import { getAddress } from "../../utils/memoizedEthersUtils";

@kasparkallas
Copy link
Collaborator

@kasparkallas I have some doubts about the function below, is it just an ethers.getAddress() or is there something else involved? import { getAddress } from "../../utils/memoizedEthersUtils";

Yes, it's just ethers.getAddress().

@developerfred
Copy link
Author

thanks, can you tell me what is the vercel error log?

@vercel
Copy link

vercel bot commented Jun 27, 2022

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Updated
superfluid-console ❌ Failed (Inspect) Jun 27, 2022 at 7:30AM (UTC)

@kasparkallas
Copy link
Collaborator

kasparkallas commented Jun 27, 2022

thanks, can you tell me what is the vercel error log?

As log:

[10:28:40.807] Cloning github.com/superfluid-finance/superfluid-console (Branch: add-ens-resolver, Commit: c6d2d83)
[10:28:41.422] Cloning completed: 614.552ms
[10:28:41.913] Installing build runtime...
[10:28:43.751] Build runtime installed: 1.838s
[10:28:44.461] Looking up build cache...
[10:28:54.374] Build cache downloaded [215.97 MB]: 9300.271ms
[10:28:54.607] Installing dependencies...
[10:28:54.845] yarn install v1.22.17
[10:28:54.912] warning package-lock.json found. Your project contains lock files generated by tools other than Yarn. It is advised not to mix package managers in order to avoid resolution inconsistencies caused by unsynchronized lock files. To clear this warning, remove package-lock.json.
[10:28:54.916] [1/4] Resolving packages...
[10:28:55.778] [2/4] Fetching packages...
[10:29:31.696] [3/4] Linking dependencies...
[10:29:31.698] warning "@emotion/react > @emotion/babel-plugin@11.9.2" has unmet peer dependency "@babel/core@^7.0.0".
[10:29:31.698] warning "@emotion/react > @emotion/babel-plugin > @babel/plugin-syntax-jsx@7.17.12" has unmet peer dependency "@babel/core@^7.0.0-0".
[10:29:31.702] warning " > @mui/x-data-grid@5.11.1" has unmet peer dependency "@mui/system@^5.2.8".
[10:29:31.703] warning "graphiql > codemirror-graphql@1.3.0" has unmet peer dependency "@codemirror/language@^0.20.0".
[10:29:31.704] warning " > graphiql-explorer@0.9.0" has incorrect peer dependency "graphql@^0.6.0 || ^0.7.0 || ^0.8.0-b || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0".
[10:29:31.705] warning " > graphiql-explorer@0.9.0" has incorrect peer dependency "react@^15.6.0 || ^16.0.0".
[10:29:31.705] warning " > graphiql-explorer@0.9.0" has incorrect peer dependency "react-dom@^15.6.0 || ^16.0.0".
[10:30:10.001] [4/4] Building fresh packages...
[10:30:20.084] success Saved lockfile.
[10:30:20.089] Done in 85.25s.
[10:30:20.118] Detected Next.js version: 12.1.7-canary.23
[10:30:20.126] Running "yarn run build"
[10:30:20.407] yarn run v1.22.17
[10:30:20.434] $ next build
[10:30:21.147] info  - Linting and checking validity of types...
[10:30:25.729] 
[10:30:25.730] ./src/components/DetailsDialog.tsx
[10:30:25.730] 34:6  Warning: React Hook useEffect has missing dependencies: 'handleClose' and 'router.events'. Either include them or remove the dependency array. If 'handleClose' changes too often, find the parent component that defines it and wrap that definition in useCallback.  react-hooks/exhaustive-deps
[10:30:25.730] 
[10:30:25.731] ./src/components/IndexUpdatedEventDataGrid.tsx
[10:30:25.731] 89:5  Warning: React Hook useMemo has a missing dependency: 'network'. Either include it or remove the dependency array.  react-hooks/exhaustive-deps
[10:30:25.731] 
[10:30:25.731] ./src/components/SearchDialog.tsx
[10:30:25.732] 78:6  Warning: React Hook useEffect has missing dependencies: 'handleClose' and 'router.events'. Either include them or remove the dependency array.  react-hooks/exhaustive-deps
[10:30:25.732] 
[10:30:25.732] info  - Need to disable some ESLint rules? Learn more here: https://nextjs.org/docs/basic-features/eslint#disabling-rules
[10:30:39.964] Failed to compile.
[10:30:39.964] 
[10:30:39.964] ./src/components/AddressBook.tsx:66:9
[10:30:39.964] Type error: Type 'string | undefined' is not assignable to type 'string'.
[10:30:39.965]   Type 'undefined' is not assignable to type 'string'.
[10:30:39.965] 
[10:30:39.965] �[0m �[90m 64 | �[39m        open�[33m=�[39m{isDialogOpen}�[0m
[10:30:39.965] �[0m �[90m 65 | �[39m        handleClose�[33m=�[39m{() �[33m=>�[39m setIsDialogOpen(�[36mfalse�[39m)}�[0m
[10:30:39.965] �[0m�[31m�[1m>�[22m�[39m�[90m 66 | �[39m        description�[33m=�[39m{ensAddress�[33m.�[39mdata�[33m?�[39m�[33m.�[39mname}�[0m
[10:30:39.965] �[0m �[90m    | �[39m        �[31m�[1m^�[22m�[39m�[0m
[10:30:39.965] �[0m �[90m 67 | �[39m      �[33m/�[39m�[33m>�[39m�[0m
[10:30:39.965] �[0m �[90m 68 | �[39m      {avatarUrl�[33m.�[39mdata �[33m?�[39m �[33m<�[39m�[33mAvatar�[39m alt�[33m=�[39m{address} src�[33m=�[39m{avatarUrl�[33m.�[39mdata�[33m?�[39m�[33m.�[39mavatar} �[33m/�[39m�[33m>�[39m �[33m:�[39m �[32m''�[39m}�[0m
[10:30:39.965] �[0m �[90m 69 | �[39m    �[33m<�[39m�[33m/�[39m�[33m>�[39m�[0m
[10:30:45.257] Failed to compile.
[10:30:45.257] 
[10:30:45.257] ./src/components/AddressBook.tsx:66:9
[10:30:45.258] Type error: Type 'string | undefined' is not assignable to type 'string'.
[10:30:45.258]   Type 'undefined' is not assignable to type 'string'.
[10:30:45.258] 
[10:30:45.258] �[0m �[90m 64 | �[39m        open�[33m=�[39m{isDialogOpen}�[0m
[10:30:45.258] �[0m �[90m 65 | �[39m        handleClose�[33m=�[39m{() �[33m=>�[39m setIsDialogOpen(�[36mfalse�[39m)}�[0m
[10:30:45.258] �[0m�[31m�[1m>�[22m�[39m�[90m 66 | �[39m        description�[33m=�[39m{ensAddress�[33m.�[39mdata�[33m?�[39m�[33m.�[39mname}�[0m
[10:30:45.258] �[0m �[90m    | �[39m        �[31m�[1m^�[22m�[39m�[0m
[10:30:45.258] �[0m �[90m 67 | �[39m      �[33m/�[39m�[33m>�[39m�[0m
[10:30:45.258] �[0m �[90m 68 | �[39m      {avatarUrl�[33m.�[39mdata �[33m?�[39m �[33m<�[39m�[33mAvatar�[39m alt�[33m=�[39m{address} src�[33m=�[39m{avatarUrl�[33m.�[39mdata�[33m?�[39m�[33m.�[39mavatar} �[33m/�[39m�[33m>�[39m �[33m:�[39m �[32m''�[39m}�[0m
[10:30:45.258] �[0m �[90m 69 | �[39m    �[33m<�[39m�[33m/�[39m�[33m>�[39m�[0m
[10:30:50.608] Failed to compile.
[10:30:50.608] 
[10:30:50.609] ./src/components/AddressBook.tsx:66:9
[10:30:50.609] Type error: Type 'string | undefined' is not assignable to type 'string'.
[10:30:50.609]   Type 'undefined' is not assignable to type 'string'.
[10:30:50.609] 
[10:30:50.609] �[0m �[90m 64 | �[39m        open�[33m=�[39m{isDialogOpen}�[0m
[10:30:50.609] �[0m �[90m 65 | �[39m        handleClose�[33m=�[39m{() �[33m=>�[39m setIsDialogOpen(�[36mfalse�[39m)}�[0m
[10:30:50.609] �[0m�[31m�[1m>�[22m�[39m�[90m 66 | �[39m        description�[33m=�[39m{ensAddress�[33m.�[39mdata�[33m?�[39m�[33m.�[39mname}�[0m
[10:30:50.609] �[0m �[90m    | �[39m        �[31m�[1m^�[22m�[39m�[0m
[10:30:50.609] �[0m �[90m 67 | �[39m      �[33m/�[39m�[33m>�[39m�[0m
[10:30:50.609] �[0m �[90m 68 | �[39m      {avatarUrl�[33m.�[39mdata �[33m?�[39m �[33m<�[39m�[33mAvatar�[39m alt�[33m=�[39m{address} src�[33m=�[39m{avatarUrl�[33m.�[39mdata�[33m?�[39m�[33m.�[39mavatar} �[33m/�[39m�[33m>�[39m �[33m:�[39m �[32m''�[39m}�[0m
[10:30:50.609] �[0m �[90m 69 | �[39m    �[33m<�[39m�[33m/�[39m�[33m>�[39m�[0m
[10:30:55.855] Failed to compile.
[10:30:55.855] 
[10:30:55.855] ./src/components/AddressBook.tsx:66:9
[10:30:55.855] Type error: Type 'string | undefined' is not assignable to type 'string'.
[10:30:55.855]   Type 'undefined' is not assignable to type 'string'.
[10:30:55.855] 
[10:30:55.855] �[0m �[90m 64 | �[39m        open�[33m=�[39m{isDialogOpen}�[0m
[10:30:55.855] �[0m �[90m 65 | �[39m        handleClose�[33m=�[39m{() �[33m=>�[39m setIsDialogOpen(�[36mfalse�[39m)}�[0m
[10:30:55.856] �[0m�[31m�[1m>�[22m�[39m�[90m 66 | �[39m        description�[33m=�[39m{ensAddress�[33m.�[39mdata�[33m?�[39m�[33m.�[39mname}�[0m
[10:30:55.856] �[0m �[90m    | �[39m        �[31m�[1m^�[22m�[39m�[0m
[10:30:55.856] �[0m �[90m 67 | �[39m      �[33m/�[39m�[33m>�[39m�[0m
[10:30:55.856] �[0m �[90m 68 | �[39m      {avatarUrl�[33m.�[39mdata �[33m?�[39m �[33m<�[39m�[33mAvatar�[39m alt�[33m=�[39m{address} src�[33m=�[39m{avatarUrl�[33m.�[39mdata�[33m?�[39m�[33m.�[39mavatar} �[33m/�[39m�[33m>�[39m �[33m:�[39m �[32m''�[39m}�[0m
[10:30:55.857] �[0m �[90m 69 | �[39m    �[33m<�[39m�[33m/�[39m�[33m>�[39m�[0m
[10:30:55.883] 
[10:30:55.883] > Build error occurred
[10:30:55.884] Error: Call retries were exceeded
[10:30:55.885]     at ChildProcessWorker.initialize (/vercel/path0/node_modules/next/dist/compiled/jest-worker/index.js:1:11661)
[10:30:55.885]     at ChildProcessWorker._onExit (/vercel/path0/node_modules/next/dist/compiled/jest-worker/index.js:1:12599)
[10:30:55.885]     at ChildProcess.emit (events.js:400:28)
[10:30:55.885]     at ChildProcess.emit (domain.js:475:12)
[10:30:55.885]     at Process.ChildProcess._handle.onexit (internal/child_process.js:285:12) {
[10:30:55.885]   type: 'WorkerError'
[10:30:55.885] }
[10:30:55.905] error Command failed with exit code 1.
[10:30:55.905] info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
[10:30:55.919] Error: Command "yarn run build" exited with 1

As image:
image

You can run yarn build locally to find out the same TypeScript compilation errors.

@kasparkallas
Copy link
Collaborator

Closing this because the feature got implemented in: #105

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[CONSOLE] Add support for ENS domains
3 participants