From ca0e2d7daa4a010a9da5c9cb37b495e20473f99d Mon Sep 17 00:00:00 2001 From: bal7hazar Date: Thu, 19 Dec 2024 11:31:29 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20icons=20and=20hooks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/profile/package.json | 2 + packages/profile/src/hooks/account.ts | 89 ++++++++++++++++++- packages/profile/src/hooks/starknetid.ts | 15 ++++ packages/profile/src/hooks/wallet.ts | 62 +++++++++++++ .../icons/brand/{bravos.tsx => braavos.tsx} | 4 +- .../src/components/icons/brand/index.ts | 3 +- .../components/icons/brand/open-zeppelin.tsx | 26 ++++++ 7 files changed, 197 insertions(+), 4 deletions(-) create mode 100644 packages/profile/src/hooks/starknetid.ts create mode 100644 packages/profile/src/hooks/wallet.ts rename packages/ui-next/src/components/icons/brand/{bravos.tsx => braavos.tsx} (96%) create mode 100644 packages/ui-next/src/components/icons/brand/open-zeppelin.tsx diff --git a/packages/profile/package.json b/packages/profile/package.json index 9847ff24f..183ca8fef 100644 --- a/packages/profile/package.json +++ b/packages/profile/package.json @@ -18,6 +18,7 @@ "@cartridge/utils": "workspace:*", "@hookform/resolvers": "^3.9.1", "compare-versions": "^6.1.1", + "lodash": "^4.17.21", "posthog-js": "^1.181.0", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -31,6 +32,7 @@ }, "devDependencies": { "@eslint/js": "^9.9.0", + "@types/lodash": "^4.17.13", "@types/node": "^20.6.0", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", diff --git a/packages/profile/src/hooks/account.ts b/packages/profile/src/hooks/account.ts index 00e9f8b48..b37bb7048 100644 --- a/packages/profile/src/hooks/account.ts +++ b/packages/profile/src/hooks/account.ts @@ -3,8 +3,10 @@ import { useAccountNameQuery, useAddressByUsernameQuery, } from "@cartridge/utils/api/cartridge"; -import { useEffect, useMemo, useRef } from "react"; +import { useEffect, useMemo, useRef, useState } from "react"; import { useParams } from "react-router-dom"; +import { useStarkAddress } from "./starknetid"; +import { useWallet } from "./wallet"; export function useUsername({ address }: { address: string }) { const { data } = useAccountNameQuery({ address }); @@ -12,6 +14,91 @@ export function useUsername({ address }: { address: string }) { return { username: data?.accounts?.edges?.[0]?.node?.username ?? "" }; } +export function useAddress({ username }: { username: string }) { + const { isReady } = useIndexerAPI(); + const { data, isFetching } = useAddressByUsernameQuery( + { username }, + { enabled: isReady && !!username }, + ); + + return { + address: data?.account?.controllers?.edges?.[0]?.node?.address ?? "", + isFetching, + }; +} + +export function useAccountInfo({ nameOrAddress }: { nameOrAddress: string }) { + const [starkName, setStarkName] = useState(""); + const [controllerName, setControllerName] = useState(""); + + useEffect(() => { + // If the address ends with .stark, set the stark name + if (nameOrAddress.endsWith(".stark")) { + setStarkName(nameOrAddress); + setControllerName(""); + return; + } + // If the address matches a controller name, set the controller name + if ( + nameOrAddress.match(/^[a-z0-9]+$/) && + nameOrAddress.length >= 3 && + nameOrAddress.length <= 30 + ) { + setControllerName(nameOrAddress); + setStarkName(""); + return; + } + // Otherwise, clear the names + setStarkName(""); + setControllerName(""); + }, [nameOrAddress]); + + // Fetch the stark address + const { address: starkAddress, isFetching: isFetchingStarkAddress } = + useStarkAddress({ name: starkName }); + // Fetch the controller address + const { + address: controllerAddress, + isFetching: isFetchingControllerAddress, + } = useAddress({ username: controllerName }); + + const name = useMemo(() => { + if (starkName) { + return starkName; + } + if (controllerName) { + return controllerName; + } + return ""; + }, [starkName, controllerName]); + + const address = useMemo(() => { + if (starkAddress) { + return starkAddress; + } + if (controllerAddress) { + return controllerAddress; + } + if (nameOrAddress.startsWith("0x")) { + return nameOrAddress; + } + return ""; + }, [starkAddress, controllerAddress, nameOrAddress]); + + // Fetch class hash + const { wallet, isFetching: isFetchingClassHash } = useWallet({ address }); + + return { + name, + address, + wallet, + isFetching: + isFetchingStarkAddress || + isFetchingControllerAddress || + isFetchingClassHash, + }; +} + export function useAccount() { const params = useParams<{ username: string; diff --git a/packages/profile/src/hooks/starknetid.ts b/packages/profile/src/hooks/starknetid.ts new file mode 100644 index 000000000..443160f0f --- /dev/null +++ b/packages/profile/src/hooks/starknetid.ts @@ -0,0 +1,15 @@ +import { useQuery } from "react-query"; + +export const STARKNET_ID_API_URL = "https://api.starknet.id/domain_to_addr"; + +export const useStarkAddress = ({ name }: { name: string }) => { + const { data, isFetching } = useQuery({ + enabled: !!name && name.includes(".stark"), + staleTime: 5 * 60 * 1000, // 5 minutes + queryKey: ["starknetid", name], + queryFn: () => + fetch(`${STARKNET_ID_API_URL}?domain=${name}`).then((res) => res.json()), + }); + + return { address: data?.addr ?? "", isFetching }; +}; diff --git a/packages/profile/src/hooks/wallet.ts b/packages/profile/src/hooks/wallet.ts new file mode 100644 index 000000000..ea73523ad --- /dev/null +++ b/packages/profile/src/hooks/wallet.ts @@ -0,0 +1,62 @@ +import { useQuery } from "react-query"; +import { useConnection } from "./context"; +import { useMemo } from "react"; + +const ARGENT_CLASS_HASH = BigInt( + "0x036078334509b514626504edc9fb252328d1a240e4e948bef8d0c08dff45927f", +); +const BRAAVOS_BASE_ACCOUNT_CLASS_HASH: BigInt = BigInt( + "0x013bfe114fb1cf405bfc3a7f8dbe2d91db146c17521d40dcf57e16d6b59fa8e6", +); +const BRAAVOS_ACCOUNT_CLASS_HASH: BigInt = BigInt( + "0x00816dd0297efc55dc1e7559020a3a825e81ef734b558f03c83325d4da7e6253", +); +const OZ_ACCOUNT_CLASS_HASH: BigInt = BigInt( + "0x04a444ef8caf8fa0db05da60bf0ad9bae264c73fa7e32c61d245406f5523174b", +); +const CONTROLLER_CLASS_HASH: BigInt = BigInt( + "0x511dd75da368f5311134dee2356356ac4da1538d2ad18aa66d57c47e3757d59", +); + +export type Wallet = "Controller" | "ArgentX" | "Braavos" | "OpenZeppelin"; + +export function useWallet({ address }: { address: string }) { + const { provider } = useConnection(); + + const { data, isFetching } = useQuery({ + enabled: !!address && address.startsWith("0x"), + staleTime: 5 * 60 * 1000, // 5 minutes + queryKey: ["classhash", address], + queryFn: async () => { + try { + return await provider.getClassHashAt(BigInt(address)); + } catch (error) { + return null; + } + }, + }); + + const wallet: Wallet | null = useMemo(() => { + console.log("data", data); + if (!data) return null; + const classHash = BigInt(data); + if (classHash === ARGENT_CLASS_HASH) { + return "ArgentX"; + } + if ( + classHash === BRAAVOS_BASE_ACCOUNT_CLASS_HASH || + classHash === BRAAVOS_ACCOUNT_CLASS_HASH + ) { + return "Braavos"; + } + if (classHash === OZ_ACCOUNT_CLASS_HASH) { + return "OpenZeppelin"; + } + if (classHash === CONTROLLER_CLASS_HASH) { + return "Controller"; + } + return null; + }, [data]); + + return { wallet, isFetching }; +} diff --git a/packages/ui-next/src/components/icons/brand/bravos.tsx b/packages/ui-next/src/components/icons/brand/braavos.tsx similarity index 96% rename from packages/ui-next/src/components/icons/brand/bravos.tsx rename to packages/ui-next/src/components/icons/brand/braavos.tsx index 7384864b3..3eaf416f3 100644 --- a/packages/ui-next/src/components/icons/brand/bravos.tsx +++ b/packages/ui-next/src/components/icons/brand/braavos.tsx @@ -2,7 +2,7 @@ import { forwardRef, memo } from "react"; import { iconVariants } from "../utils"; import { IconProps } from "../types"; -export const BravosIcon = memo( +export const BraavosIcon = memo( forwardRef( ({ className, size, ...props }, forwardedRef) => ( ( + ({ className, size, ...props }, forwardedRef) => ( + + + + + + + + ), + ), +); + +OpenZeppelinIcon.displayName = "OpenZeppelinIcon"; + +