diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e3eb410 --- /dev/null +++ b/.env.example @@ -0,0 +1,28 @@ +# Blockchain explorers to fetch contract data from. +ETHERSCAN_EXPLORER_API_KEY= +POLYGONSCAN_EXPLORER_API_KEY= +BLOCKSCOUT_EXPLORER_API_KEY= + +# Infura key. +INFURA_PROJECT_ID= + +# Infura's IPFS basic auth key. +INFURA_IPFS_API_KEY=: + +# Chain id where the Rosette contract is deployed on. +CHAIN_ID= + +# RPC endpoint url. +RPC_URL= + +# Rosette's contract address. +ROSETTE_STONE_ADDRESS= + +# Rosette's subgraph uri. +SUBGRAPH_URI= + +# Bundlr network env + +BUNDLR_URI= +BUNDLR_FEE_TOKEN= +PRIVATE_KEY= \ No newline at end of file diff --git a/.gitignore b/.gitignore index 23b7972..ac35bcd 100644 --- a/.gitignore +++ b/.gitignore @@ -4,11 +4,14 @@ node_modules .env .vercel .output +.yalc /build/ /public/build /public/aragon-ui /api/index.js +/api/index.js.map +yalc.lock # Editor directories and files .vscode/* @@ -19,4 +22,6 @@ node_modules *.ntvs* *.njsproj *.sln -*.sw? \ No newline at end of file +*.sw? +# Local Netlify folder +.netlify diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..8db3575 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +strict-peer-dependencies = false \ No newline at end of file diff --git a/api/index.js.map b/api/index.js.map new file mode 100644 index 0000000..6c161a7 --- /dev/null +++ b/api/index.js.map @@ -0,0 +1,7 @@ +{ + "version": 3, + "sources": ["../node_modules/.pnpm/@remix-run+dev@1.6.3_zfpkqqvpckwvzdnrdcpprdboka/node_modules/@remix-run/dev/dist/compiler/shims/react.ts", "empty-module:~/utils/client/icons.client", "empty-module:~/utils/client/selection.client", "", "server-entry-module:@remix-run/dev/server-build", "../app/entry.server.tsx", "/Users/gabi/code/blossom/rosette-app/app/root.tsx", "../app/App.tsx", "../app/providers/AppReady.tsx", "../app/components/BlossomLabs.tsx", "../app/providers/Wagmi.tsx", "../app/components/AppLayout/index.tsx", "../app/components/AppLayout/BottomBar/index.tsx", "../app/components/AppLayout/TopBar/index.tsx", "../app/components/AccountModule/index.tsx", "../app/components/AccountModule/helpers.ts", "../app/components/AccountModule/use-account-module-store.ts", "../app/components/AccountModule/AccountButton.tsx", "../app/components/AccountModule/HeaderPopover.tsx", "../app/components/AccountModule/screens/index.ts", "../app/components/AccountModule/screens/ScreenPromptedAction.tsx", "../app/components/AccountModule/LoadingRing.tsx", "../app/components/AccountModule/screens/ScreenConnected.tsx", "../app/hooks/useCopyToClipboard.ts", "../app/components/AccountModule/screens/ScreenConnecting.tsx", "../app/components/AccountModule/screens/ScreenError.tsx", "../app/components/AccountModule/screens/ScreenWallets.tsx", "../app/components/AppLayout/TopBar/NavSection/index.tsx", "../app/components/AppLayout/TopBar/NavSection/CompactMenu/index.tsx", "../app/components/AppLayout/TopBar/NavSection/CompactMenu/MenuItem.tsx", "../app/components/AppLayout/TopBar/NavSection/CompactMenu/Sidebar.tsx", "../app/providers/RosetteStone.tsx", "/Users/gabi/code/blossom/rosette-app/app/routes/contract-descriptions-search.ts", "../app/utils/server/entries-data.server.ts", "../app/utils/ipfs.ts", "../app/utils/cache-store.ts", "../app/utils/server/subgraph.server.ts", "../app/types.ts", "/Users/gabi/code/blossom/rosette-app/app/routes/fn-descriptions-upload.ts", "../app/utils/server/ipfs.server.ts", "/Users/gabi/code/blossom/rosette-app/app/routes/describe.tsx", "../app/components/AppLayout/AppScreen.tsx", "../app/components/ContractDescriptorScreen/index.tsx", "../app/components/ContractDescriptorScreen/Pagination/index.tsx", "../app/components/ContractDescriptorScreen/Pagination/PaginationItem.tsx", "../app/components/ContractDescriptorScreen/Pagination/PaginationSeparator.tsx", "../app/components/ContractDescriptorScreen/use-contract-descriptor-store.ts", "../app/utils/web3.ts", "../app/components/ContractDescriptorScreen/useRosetteActions.ts", "../app/components/ContractDescriptorScreen/HelperFunctionsPicker/index.tsx", "../app/components/ContractDescriptorScreen/HelperFunctionsPicker/FunctionDetails/index.tsx", "../app/components/ContractDescriptorScreen/HelperFunctionsPicker/FunctionDetails/Entry.tsx", "../app/radspec-helper-functions.ts", "../app/components/ContractDescriptorScreen/FnDescriptorsCarousel.tsx", "../app/components/ContractDescriptorScreen/Carousel/index.tsx", "../app/components/ContractDescriptorScreen/Carousel/PrevNext.tsx", "../app/components/ContractDescriptorScreen/FunctionDescriptor/index.tsx", "../app/components/StatusLabel.tsx", "../app/components/ContractDescriptorScreen/FunctionDescriptor/DescriptionField.tsx", "../app/hooks/useDebounce.ts", "../app/components/ContractDescriptorScreen/FunctionDescriptorFilters.tsx", "../app/components/ContractSelectorScreen/index.tsx", "../app/components/ContractSelectorScreen/ContractItem.tsx", "../app/components/SmoothDisplayContainer.tsx", "../app/utils/server/contract-data.server.ts", "../app/utils/server/web3.server.ts", "../app/utils/server/blockchain-explorers.server.ts", "../app/utils/server/proxy-patterns.server.ts", "/Users/gabi/code/blossom/rosette-app/app/routes/entries.tsx", "/Users/gabi/code/blossom/rosette-app/app/routes/entries/$entry.tsx", "/Users/gabi/code/blossom/rosette-app/app/routes/entries/index.tsx", "/Users/gabi/code/blossom/rosette-app/app/routes/index.tsx", "/Users/gabi/code/blossom/rosette-app/app/routes/home.tsx", "../app/components/ContractForm/index.tsx", "/Users/gabi/code/blossom/rosette-app/app/routes/swap.tsx", "server-assets-manifest:@remix-run/dev/assets-manifest"], + "sourcesContent": ["// eslint-disable-next-line import/no-extraneous-dependencies\nimport * as React from \"react\";\nexport { React };\n", "module.exports = {};", "module.exports = {};", "export * from \"@remix-run/dev/server-build\";", "\nimport * as entryServer from \"/Users/gabi/code/blossom/rosette-app/app/entry.server.tsx\";\nimport * as route0 from \"/Users/gabi/code/blossom/rosette-app/app/root.tsx\";\nimport * as route1 from \"/Users/gabi/code/blossom/rosette-app/app/routes/contract-descriptions-search.ts\";\nimport * as route2 from \"/Users/gabi/code/blossom/rosette-app/app/routes/fn-descriptions-upload.ts\";\nimport * as route3 from \"/Users/gabi/code/blossom/rosette-app/app/routes/describe.tsx\";\nimport * as route4 from \"/Users/gabi/code/blossom/rosette-app/app/routes/entries.tsx\";\nimport * as route5 from \"/Users/gabi/code/blossom/rosette-app/app/routes/entries/$entry.tsx\";\nimport * as route6 from \"/Users/gabi/code/blossom/rosette-app/app/routes/entries/index.tsx\";\nimport * as route7 from \"/Users/gabi/code/blossom/rosette-app/app/routes/index.tsx\";\nimport * as route8 from \"/Users/gabi/code/blossom/rosette-app/app/routes/home.tsx\";\nimport * as route9 from \"/Users/gabi/code/blossom/rosette-app/app/routes/swap.tsx\";\n export { default as assets } from \"@remix-run/dev/assets-manifest\";\n export const entry = { module: entryServer };\n export const routes = {\n \"root\": {\n id: \"root\",\n parentId: undefined,\n path: \"\",\n index: undefined,\n caseSensitive: undefined,\n module: route0\n },\n \"routes/contract-descriptions-search\": {\n id: \"routes/contract-descriptions-search\",\n parentId: \"root\",\n path: \"contract-descriptions-search\",\n index: undefined,\n caseSensitive: undefined,\n module: route1\n },\n \"routes/fn-descriptions-upload\": {\n id: \"routes/fn-descriptions-upload\",\n parentId: \"root\",\n path: \"fn-descriptions-upload\",\n index: undefined,\n caseSensitive: undefined,\n module: route2\n },\n \"routes/describe\": {\n id: \"routes/describe\",\n parentId: \"root\",\n path: \"describe\",\n index: undefined,\n caseSensitive: undefined,\n module: route3\n },\n \"routes/entries\": {\n id: \"routes/entries\",\n parentId: \"root\",\n path: \"entries\",\n index: undefined,\n caseSensitive: undefined,\n module: route4\n },\n \"routes/entries/$entry\": {\n id: \"routes/entries/$entry\",\n parentId: \"routes/entries\",\n path: \":entry\",\n index: undefined,\n caseSensitive: undefined,\n module: route5\n },\n \"routes/entries/index\": {\n id: \"routes/entries/index\",\n parentId: \"routes/entries\",\n path: undefined,\n index: true,\n caseSensitive: undefined,\n module: route6\n },\n \"routes/index\": {\n id: \"routes/index\",\n parentId: \"root\",\n path: undefined,\n index: true,\n caseSensitive: undefined,\n module: route7\n },\n \"routes/home\": {\n id: \"routes/home\",\n parentId: \"root\",\n path: \"home\",\n index: undefined,\n caseSensitive: undefined,\n module: route8\n },\n \"routes/swap\": {\n id: \"routes/swap\",\n parentId: \"root\",\n path: \"swap\",\n index: undefined,\n caseSensitive: undefined,\n module: route9\n }\n };", "import ReactDOMServer from \"react-dom/server\";\nimport type { EntryContext } from \"@remix-run/node\";\nimport { RemixServer } from \"@remix-run/react\";\nimport { ServerStyleSheet } from \"styled-components\";\n\nexport default function handleRequest(\n request: Request,\n responseStatusCode: number,\n responseHeaders: Headers,\n remixContext: EntryContext\n) {\n const sheet = new ServerStyleSheet();\n\n let markup = ReactDOMServer.renderToString(\n sheet.collectStyles(\n \n )\n );\n const styles = sheet.getStyleTags();\n\n markup = markup.replace(\"__STYLES__\", styles);\n\n responseHeaders.set(\"Content-Type\", \"text/html\");\n\n return new Response(\"\" + markup, {\n status: responseStatusCode,\n headers: responseHeaders,\n });\n}\n", "import { Main } from \"@blossom-labs/rosette-ui\";\nimport {\n Links,\n LiveReload,\n Meta,\n Scripts,\n ScrollRestoration,\n useCatch,\n useLoaderData,\n} from \"@remix-run/react\";\nimport type { MetaFunction } from \"@remix-run/node\";\nimport { json } from \"@remix-run/node\";\nimport { App } from \"~/App\";\n\nexport const meta: MetaFunction = () => {\n return {\n title: \"Rosette\",\n charset: \"utf-8\",\n viewport: \"width=device-width,initial-scale=1\",\n };\n};\n\nexport async function loader() {\n return json({\n ENV: {\n CHAIN_ID: process.env.CHAIN_ID,\n RPC_URL: process.env.RPC_URL,\n INFURA_ID: process.env.INFURA_ID,\n ROSETTE_STONE_ADDRESS: process.env.ROSETTE_STONE_ADDRESS,\n },\n });\n}\n\ninterface DocumentProps {\n children: React.ReactNode;\n}\n\nconst Document = ({ children }: DocumentProps) => {\n const data = useLoaderData();\n\n return (\n \n \n \n \n {typeof document === \"undefined\" ? \"__STYLES__\" : null}\n \n \n {children}\n \n \n \n \n \n \n );\n};\n\nexport default function WrapperApp() {\n return (\n \n \n \n \n \n );\n}\n\n// https://remix.run/docs/en/v1/api/conventions#catchboundary\nexport function CatchBoundary() {\n const caught = useCatch();\n\n let message;\n switch (caught.status) {\n case 401:\n message = (\n

\n Oops! Looks like you tried to visit a page that you do not have access\n to.\n

\n );\n break;\n case 404:\n message = (\n

Oops! Looks like you tried to visit a page that does not exist.

\n );\n break;\n\n default:\n throw new Error(caught.data || caught.statusText);\n }\n\n return (\n
\n

\n {caught.status}: {caught.statusText}\n

\n {message}\n
\n );\n}\n\nexport function ErrorBoundary({ error }: { error: Error }) {\n return (\n
\n

[ErrorBoundary]: There was an error: {error.message}

\n
\n );\n}\n", "import { useTheme } from \"@blossom-labs/rosette-ui\";\nimport { ThemeProvider } from \"styled-components\";\nimport { useState } from \"react\";\n\nimport { AppReady } from \"~/providers/AppReady\";\nimport Wagmi from \"~/providers/Wagmi\";\nimport { AppLayout } from \"~/components/AppLayout\";\nimport { Outlet } from \"@remix-run/react\";\nimport { RosetteStone } from \"./providers/RosetteStone\";\n\nexport type AppContext = {\n displayTopBar(display: boolean): void;\n displayBottomBar(display: boolean): void;\n};\n\nexport const App = () => {\n const theme = useTheme();\n\n const [displayTopBar, setDisplayTopBar] = useState(true);\n const [displayBottomBar, setDisplayBottomBar] = useState(true);\n\n return (\n \n \n \n \n \n \n \n \n \n \n \n );\n};\n", "import { springs } from \"@blossom-labs/rosette-ui\";\nimport styled from \"styled-components\";\nimport { createContext, useContext, useEffect, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport type { TransitionFn } from \"@react-spring/web\";\nimport { a, useTransition } from \"@react-spring/web\";\n\nimport { BlossomLabsIcon } from \"~/components/BlossomLabs\";\n\nexport type AppReadyTransition = TransitionFn<\n boolean,\n {\n progress: number;\n topBarTransform: string;\n bottomBarTransform: string;\n screenTransform: string;\n }\n>;\n\ntype AppReadyContextProps = {\n appReady: boolean;\n appReadyTransition: AppReadyTransition;\n};\n\nconst AppReadyContext = createContext(\n {} as AppReadyContextProps\n);\n\ntype AppReadyProps = { children: ReactNode };\n\nexport function AppReady({ children }: AppReadyProps) {\n const [ready, setReady] = useState(false);\n\n const appReadyTransition = useTransition(ready, {\n config: springs.lazy,\n from: {\n progress: 0,\n topBarTransform: \"translate3d(0, -100%, 0)\",\n bottomBarTransform: \"translate3d(0, 100%, 0)\",\n screenTransform: \"scale3d(0.9, 0.9, 1)\",\n },\n enter: {\n progress: 1,\n topBarTransform: \"translate3d(0, 0, 0)\",\n bottomBarTransform: \"translate3d(0, 0, 0)\",\n screenTransform: \"scale3d(1, 1, 1)\",\n },\n leave: {\n progress: 0,\n topBarTransform: \"translate3d(0, -100%, 0)\",\n bottomBarTransform: \"translate3d(0, 100%, 0)\",\n screenTransform: \"scale3d(0.9, 0.9, 1)\",\n },\n });\n\n const splashTransition = useTransition(!ready, {\n config: springs.swift,\n from: {\n opacity: 1,\n logoTransform: \" rotate3d(0, 0, 1, 0deg)\",\n },\n enter: {\n opacity: 1,\n logoTransform: \"rotate3d(0, 0, 1, 360deg)\",\n },\n leave: {\n opacity: 0,\n // logoTransform: \"rotate3d(0, 0, 1, 360deg)\",\n },\n });\n\n useEffect(() => {\n const id = setTimeout(() => setReady(true), 400);\n return () => clearTimeout(id);\n }, []);\n\n return (\n \n {splashTransition(\n ({ opacity, logoTransform }, loading) =>\n loading && (\n \n \n \n \n \n )\n )}\n {children}\n \n );\n}\n\nexport function useAppReady() {\n return useContext(AppReadyContext);\n}\n\nconst AnimatedSplashContainer = styled(a.div)`\n position: fixed;\n transition-duration: 500ms;\n z-index: 9;\n inset: 0;\n display: grid;\n place-items: center;\n`;\n", "import styled from \"styled-components\";\n\nimport blossomLabsLogo from \"~/assets/blossom-logo.svg\";\nimport blossomLabsIcon from \"~/assets/blossom-icon.svg\";\n\nexport const BlossomLabsLogo = () => (\n \n \n \"\"\n \n \n);\n\nexport const BlossomLabsIcon = () => (\n \n);\n\nconst OutterWrapper = styled.span`\n display: inline-block;\n vertical-align: middle;\n`;\n", "import { providers } from \"ethers\";\nimport { useMemo } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { allChains, WagmiProvider } from \"wagmi\";\nimport type { Connector } from \"wagmi\";\nimport { InjectedConnector } from \"wagmi/connectors/injected\";\nimport { WalletConnectConnector } from \"wagmi/connectors/walletConnect\";\n\nconst getConnectors = (chainId: string): Connector[] => {\n const chain = allChains.find((chain) => chain.id === Number(chainId));\n\n if (!chain) {\n throw new Error(\"CHAIN_ID not found: \" + chainId);\n }\n\n return [\n new InjectedConnector({\n chains: [chain],\n options: { shimDisconnect: true },\n }),\n new WalletConnectConnector({\n chains: [chain],\n options: {\n infuraId: window.ENV.INFURA_ID,\n qrcode: true,\n },\n }),\n ];\n};\n\nconst Wagmi = ({ children }: { children: ReactNode }) => {\n const connectors = useMemo(() => getConnectors(window.ENV.CHAIN_ID), []);\n const ethersProvider = useMemo(\n () => new providers.JsonRpcProvider(window.ENV.RPC_URL),\n []\n );\n\n return (\n \n {children}\n \n );\n};\n\nexport default Wagmi;\n", "import { useViewport } from \"@blossom-labs/rosette-ui\";\nimport styled from \"styled-components\";\nimport type { ReactNode } from \"react\";\n\nimport { BottomBar } from \"./BottomBar\";\nimport { TopBar } from \"./TopBar\";\n\nimport background from \"~/assets/background.png\";\nimport tablet from \"~/assets/tablet.png\";\nimport mobile from \"~/assets/mobile.png\";\n\ntype AppLayoutProps = {\n children: ReactNode;\n displayTopBar?: boolean;\n displayBottomBar?: boolean;\n};\n\nexport const AppLayout = ({\n children,\n displayTopBar = true,\n displayBottomBar = true,\n}: AppLayoutProps) => {\n const { below, within } = useViewport();\n\n const compactMode = below(\"medium\");\n const tabletMode = within(\"medium\", \"large\");\n\n return (\n \n {displayTopBar && (\n
\n \n
\n )}\n {children}\n {displayBottomBar && (\n
\n \n
\n )}\n
\n );\n};\n\nconst Container = styled.div<{ compactMode: boolean; tabletMode: boolean }>`\n position: relative;\n height: 100vh;\n margin: 0 auto;\n display: flex;\n flex-direction: column;\n overflow: auto;\n background-size: cover;\n background-image: url(${({ compactMode, tabletMode }) =>\n compactMode ? mobile : tabletMode ? tablet : background});\n`;\n\nconst ChildrenWrapper = styled.div`\n display: flex;\n flex: auto;\n flex-direction: column;\n scroll-behavior: smooth;\n`;\n", "import { GU, useTheme, useViewport } from \"@blossom-labs/rosette-ui\";\nimport { a } from \"@react-spring/web\";\nimport styled from \"styled-components\";\nimport { useAppReady } from \"~/providers/AppReady\";\nimport { BlossomLabsLogo } from \"~/components/BlossomLabs\";\n\nconst OPACITY = 0.8;\nexport const BottomBar = () => {\n const { below } = useViewport();\n const theme = useTheme();\n const { appReadyTransition } = useAppReady();\n const compactMode = below(\"large\");\n\n return (\n \n {appReadyTransition(\n ({ progress, bottomBarTransform }, ready) =>\n ready && (\n \n
\n powered by \n
\n \n )\n )}\n
\n );\n};\n\nconst Container = styled.div`\n position: relative;\n height: ${7 * GU}px;\n`;\n\nconst AnimatedContainer = styled(a.div)<{ $compactMode: boolean }>`\n position: absolute;\n inset: 0;\n display: flex;\n padding: 0 ${7 * GU}px;\n\n justify-content: ${({ $compactMode }) =>\n $compactMode ? \"center\" : \"flex-start\"};\n`;\n", "import { GU, useViewport } from \"@blossom-labs/rosette-ui\";\nimport { a } from \"@react-spring/web\";\nimport styled from \"styled-components\";\n\nimport { useAppReady } from \"~/providers/AppReady\";\nimport { AccountModule } from \"~/components/AccountModule\";\nimport { NavSection } from \"./NavSection\";\n\nexport const TopBar = () => {\n const { appReadyTransition } = useAppReady();\n const { below } = useViewport();\n const compactMode = below(\"large\");\n const mobileMode = below(\"medium\");\n\n return (\n \n {appReadyTransition(\n ({ progress, topBarTransform }, ready) =>\n ready && (\n \n \n \n \n )\n )}\n \n );\n};\n\nconst NavContainer = styled.nav`\n position: relative;\n margin: 0 auto;\n height: ${8 * GU}px;\n`;\n\nconst AnimatedContainer = styled(a.div)<{ $compactMode: boolean }>`\n position: absolute;\n inset: 0;\n z-index: 1;\n border-bottom: ${({ $compactMode, theme }) =>\n $compactMode ? `1px solid ${theme.border}` : \"\"};\n ${({ $compactMode }) =>\n $compactMode\n ? `\n padding-right: ${1 * GU}px;\n `\n : `\n padding-right: ${6 * GU}px;\n padding-left: ${6 * GU}px;\n `};\n display: flex;\n justify-content: space-between;\n`;\n", "import { Button, GU, IconConnect } from \"@blossom-labs/rosette-ui\";\nimport { useEffect, useRef, useState } from \"react\";\nimport styled from \"styled-components\";\nimport { useAccount, useConnect, useNetwork } from \"wagmi\";\nimport type { Chain, Connector } from \"wagmi\";\nimport { getNetworkError } from \"./helpers\";\nimport {\n ScreenType,\n actions,\n useAccounModuleStore,\n} from \"./use-account-module-store\";\nimport type { PromptedAction } from \"./use-account-module-store\";\nimport { AccountButton } from \"./AccountButton\";\nimport { HeaderPopover } from \"./HeaderPopover\";\nimport {\n ScreenConnected,\n ScreenConnecting,\n ScreenError,\n ScreenPromptedAction,\n ScreenWallets,\n} from \"./screens\";\n\nconst SCREENS = [\n ScreenWallets,\n ScreenConnecting,\n ScreenError,\n ScreenPromptedAction,\n ScreenConnected,\n];\n\nconst { Connecting, Error, Action, Connected } = ScreenType;\n\nexport const AccountModule = ({ compact }: { compact?: boolean }) => {\n const buttonRef = useRef();\n const timer = useRef();\n const [{ data: accountData, error: accountError }, disconnect] = useAccount();\n const [{ error: connectError }, connect] = useConnect();\n const [{ data: networkData, error: networkError }, switchNetwork] =\n useNetwork();\n const [promptedActionSucceeded, setPromptedActionSucceeded] = useState(false);\n const error =\n getNetworkError(networkError, networkData) || accountError || connectError;\n\n const { currentScreen, opened, screenDirection } = useAccounModuleStore();\n\n const displayAccountButton =\n currentScreen === Connected && !error && accountData?.address;\n\n const Screen = SCREENS[currentScreen];\n\n const handlePopoverClose = () => {\n if (\n currentScreen === Connecting ||\n currentScreen === Error ||\n currentScreen === Action\n ) {\n // Reject closing the popover\n return false;\n }\n actions.opened(false);\n };\n\n const handleSwitchNetwork = (switchAction: PromptedAction, chain: Chain) => {\n if (switchNetwork) {\n actions.promptedAction(switchAction);\n actions.goToScreen(Action);\n switchNetwork(chain.id)\n .then(({ data, error }) => {\n if (data) {\n setPromptedActionSucceeded(true);\n } else {\n console.error(error);\n actions.goToInitialScreen();\n actions.promptedAction(null);\n }\n })\n .catch((err) => console.error(err));\n }\n };\n\n const handleConnect = (connector: Connector) => {\n actions.selectedConnector(connector);\n actions.goToScreen(Connecting);\n /**\n * Set a timer to always display connecting screen for\n * a period of time\n */\n timer.current = setTimeout(() => {\n connect(connector).then(({ data, error }) => {\n if (data?.chain?.unsupported || error) {\n actions.goToScreen(Error);\n } else {\n actions.goToScreen(Connected);\n }\n });\n }, 500);\n };\n\n const handleBack = () => {\n actions.goToInitialScreen();\n disconnect();\n };\n\n useEffect(() => {\n return () => {\n if (timer.current) {\n clearTimeout(timer.current);\n }\n };\n }, []);\n\n /**\n * Wait until network switching process is completed before going to\n * the connected screen.\n */\n useEffect(() => {\n if (promptedActionSucceeded && !networkData.chain?.unsupported) {\n actions.goToScreen(Connected);\n actions.promptedAction(null);\n setPromptedActionSucceeded(false);\n }\n }, [promptedActionSucceeded, networkData.chain]);\n\n return (\n \n {displayAccountButton ? (\n \n ) : (\n }\n label=\"Connect account\"\n onClick={actions.toggleOpened}\n display={compact ? \"icon\" : \"all\"}\n />\n )}\n \n \n \n \n );\n};\n\nconst Container = styled.div`\n display: flex;\n align-items: center;\n justify-content: space-around;\n outline: 0;\n`;\n\nconst ConnectButton = styled(Button)`\n border: 1px solid ${({ theme }) => theme.content};\n`;\n", "import { ChainNotConfiguredError, UserRejectedRequestError } from \"wagmi\";\nimport type { Chain } from \"wagmi\";\n\nimport metamask from \"./assets/metamask.svg\";\nimport walletconnect from \"./assets/walletconnect.svg\";\n\nconst chainNotConfiguredError = new ChainNotConfiguredError();\n\nexport const getWalletIconPath = (\n id: string | undefined\n): string | undefined => {\n switch (id) {\n case \"injected\":\n return metamask;\n case \"walletConnect\":\n return walletconnect;\n default:\n return;\n }\n};\n\nexport const getNetworkError = (\n networkError: Error | undefined,\n networkData: { chain?: { unsupported: boolean | undefined }; chains: Chain[] }\n): Error | undefined => {\n if (networkError) {\n return networkError;\n }\n\n if (networkData.chain?.unsupported) {\n return chainNotConfiguredError;\n }\n};\n\nexport const isUserRejectedRequestError = (error: Error) =>\n error instanceof UserRejectedRequestError;\n", "import { createStore } from \"@udecode/zustood\";\nimport type { Connector } from \"wagmi\";\n\nexport enum ScreenType {\n Wallets,\n Connecting,\n Error,\n Action,\n Connected,\n}\n\nexport type PromptedAction = {\n title: string;\n subtitle?: string;\n image?: string;\n};\n\ntype AccountModuleState = {\n opened: boolean;\n promptedAction: PromptedAction | null;\n selectedConnector: Connector | null;\n currentScreen: ScreenType;\n screenDirection: -1 | 1;\n};\n\nconst initialState: AccountModuleState = {\n opened: false,\n promptedAction: null,\n selectedConnector: null,\n currentScreen: ScreenType.Wallets,\n screenDirection: -1,\n};\n\nconst accountModuleStore = createStore(\"account-module\")(initialState, {\n devtools: { enabled: process.env.NODE_ENV === \"development\" },\n})\n .extendActions((set, get) => ({\n toggleOpened: () => set.opened(!get.opened()),\n reset: () => {\n const {\n opened,\n promptedAction,\n selectedConnector,\n currentScreen,\n screenDirection,\n } = initialState;\n\n set.opened(opened);\n set.promptedAction(promptedAction);\n set.selectedConnector(selectedConnector);\n set.currentScreen(currentScreen);\n set.screenDirection(screenDirection);\n },\n goToScreen: (screen: ScreenType) => {\n const previousScreen = get.currentScreen();\n const screenDirection = previousScreen > screen ? -1 : 1;\n\n set.currentScreen(screen);\n set.screenDirection(screenDirection);\n },\n }))\n .extendActions((set) => ({\n goToInitialScreen: () => {\n set.reset();\n set.opened(true);\n },\n }));\n\nexport const useAccounModuleStore = accountModuleStore.useStore;\nexport const actions = accountModuleStore.set;\n", "import {\n ButtonBase,\n EthIdenticon,\n GU,\n IconDown,\n BIG_RADIUS,\n shortenAddress,\n textStyle,\n useTheme,\n useViewport,\n} from \"@blossom-labs/rosette-ui\";\nimport { Fragment } from \"react\";\nimport type { ReactNode } from \"react\";\nimport styled from \"styled-components\";\nimport { useAccount } from \"wagmi\";\n\ntype AccountButtonWrapperProps = {\n content: ReactNode;\n hasPopover: boolean;\n icon?: string | ReactNode;\n onClick?: () => void;\n};\n\nconst AccountButtonWrapper = ({\n content,\n hasPopover = false,\n icon,\n onClick,\n}: AccountButtonWrapperProps) => {\n const { above } = useViewport();\n const theme = useTheme();\n\n return (\n \n \n <>\n {icon}\n {above(\"medium\") && (\n \n \n {content}\n \n {hasPopover && (\n \n )}\n \n )}\n \n \n \n );\n};\n\ntype AccountButtonProps = {\n onClick: () => void;\n};\n\nexport const AccountButton = ({ onClick }: AccountButtonProps) => {\n const { above } = useViewport();\n const [{ data: accountData }] = useAccount({ fetchEns: true });\n const { address, ens } = accountData || {};\n\n return (\n \n \n \n \n \n }\n content={\n <>\n {!!address && (\n \n \n {ens?.name || shortenAddress(address ?? \"\")}\n \n \n )}\n \n }\n />\n \n );\n};\n\nconst Container = styled.div<{ large: boolean }>`\n ${({ large, theme }) => large && `border: 1px solid ${theme.content};`}\n border-radius: 8px;\n`;\n\nconst AccountButtonBase = styled(ButtonBase)`\n height: 100%;\n padding: ${0.2 * GU}px;\n ${(props) =>\n props.onClick !== undefined\n ? `&:active { background: ${props.theme.surfacePressed}; }`\n : `cursor: auto;`};\n`;\n\nconst InnerContainer = styled.div`\n display: flex;\n align-items: center;\n text-align: left;\n padding: ${1 * GU}px ${1.75 * GU}px ${1 * GU}px ${1 * GU}px;\n`;\n\nconst ConnectedCircle = styled.div`\n position: absolute;\n bottom: -3px;\n right: -3px;\n width: 10px;\n height: 10px;\n background: ${({ theme }) => theme.positive};\n border: 2px solid #141313;\n border-radius: 50%;\n`;\n\nconst LabelWrapper = styled.div`\n margin-bottom: -5px;\n color: ${({ theme }) => theme.content};\n ${textStyle(\"body2\")};\n`;\n\nconst LabelInnerWrapper = styled.div`\n overflow: hidden;\n max-width: 128px;\n text-overflow: ellipsis;\n white-space: nowrap;\n`;\n", "import { GU, Popover, springs, useViewport } from \"@blossom-labs/rosette-ui\";\nimport { useEffect, useRef, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { a, Spring, useTransition } from \"@react-spring/web\";\nimport styled from \"styled-components\";\n\ntype PopoverProps = {\n children: ReactNode;\n direction: -1 | 1;\n heading?: string;\n onClose: () => void;\n opener: ReactNode;\n screenId: number | string;\n visible: boolean;\n width: number | string;\n};\n\nexport const HeaderPopover = ({\n children,\n direction,\n onClose,\n opener,\n screenId,\n visible,\n width,\n}: PopoverProps) => {\n const { above } = useViewport();\n const [height, setHeight] = useState(30 * GU);\n\n // Prevents to lose the focus on the popover when a screen leaves while an\n // element inside is focused (e.g. when clicking on the \u201Cdisconnect\u201D button).\n const popoverFocusElement = useRef();\n\n const popupTransition = useTransition(screenId, {\n config: springs.smooth,\n keys: screenId,\n from: {\n opacity: 0,\n transform: `translate3d(${10 * GU * direction}px, 0, 0)`,\n },\n enter: {\n opacity: 1,\n transform: `translate3d(0, 0, 0)`,\n },\n });\n\n useEffect(() => {\n if (popoverFocusElement.current) {\n popoverFocusElement.current.focus();\n }\n }, [screenId]);\n\n return (\n \n \n \n {({ height }) => (\n \n {popupTransition(({ opacity, transform }) => (\n {\n if (elt) {\n setHeight(elt.clientHeight);\n }\n }}\n style={{\n opacity,\n transform,\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n }}\n >\n {children}\n \n ))}\n \n )}\n \n \n \n );\n};\n\nconst Container = styled.section`\n display: flex;\n flex-direction: column;\n overflow: hidden;\n`;\n\nconst StyledPopover = styled(Popover)<{ large: boolean }>`\n margin-top: ${({ large }) => (large ? \"17px\" : \"8px\")};\n width: ${(props) => `${props.width}px`};\n`;\n", "export { ScreenPromptedAction } from \"./ScreenPromptedAction\";\nexport { ScreenConnected } from \"./ScreenConnected\";\nexport { ScreenConnecting } from \"./ScreenConnecting\";\nexport { ScreenError } from \"./ScreenError\";\nexport { ScreenWallets } from \"./ScreenWallets\";\n", "import { GU, textStyle } from \"@blossom-labs/rosette-ui/\";\nimport styled from \"styled-components\";\n\nimport { LoadingRing } from \"../LoadingRing\";\nimport { useAccounModuleStore } from \"../use-account-module-store\";\n\nexport const ScreenPromptedAction = () => {\n const { promptedAction } = useAccounModuleStore();\n\n if (!promptedAction) {\n return null;\n }\n\n const { title, subtitle, image } = promptedAction;\n\n return (\n \n \n \n \n {image && }\n \n {title}\n {subtitle}\n \n \n );\n};\n\nconst Container = styled.section`\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: ${2 * GU}px;\n height: 100%;\n`;\n\nconst ActionContainer = styled.div`\n flex-grow: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n text-align: center;\n`;\n\nconst SpinnerContainer = styled.div`\n position: relative;\n width: ${10.5 * GU}px;\n height: ${10.5 * GU}px;\n`;\n\nconst ActionImage = styled.div<{ src: string }>`\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: 50% 50% / auto ${5 * GU}px no-repeat url(${(props) => props.src});\n`;\n\nconst Title = styled.h1`\n padding-top: ${2 * GU}px;\n font-weight: 600;\n ${textStyle(\"body1\")};\n color: ${({ theme }) => theme.content};\n`;\n\nconst Subtitle = styled.p`\n width: ${36 * GU}px;\n margin-top: ${1 * GU}px;\n color: ${(props) => props.theme.surfaceContentSecondary};\n`;\n", "import styled, { keyframes } from \"styled-components\";\n\nimport loadingRing from \"./assets/loading-ring.svg\";\n\nconst spin = keyframes`\n from {\n transform: rotate(0deg);\n }\n to {\n transform: rotate(360deg);\n }\n`;\n\nexport const LoadingRing = styled.div`\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: url(${loadingRing}) no-repeat 0 0;\n animation-duration: 1s;\n animation-iteration-count: infinite;\n animation-timing-function: linear;\n animation-name: ${spin};\n // prevents flickering on Firefox\n backface-visibility: hidden;\n`;\n", "import {\n Button,\n ButtonBase,\n GU,\n IconCheck,\n IconCopy,\n IdentityBadge,\n RADIUS,\n textStyle,\n useTheme,\n} from \"@blossom-labs/rosette-ui\";\nimport { useCallback, useEffect } from \"react\";\nimport styled from \"styled-components\";\nimport { useAccount, useNetwork } from \"wagmi\";\n\nimport { useCopyToClipboard } from \"~/hooks/useCopyToClipboard\";\nimport { getWalletIconPath } from \"../helpers\";\nimport { ScreenType, actions } from \"../use-account-module-store\";\n\ntype ScreenConnectedProps = {\n onBack(): void;\n};\n\nexport const ScreenConnected = ({ onBack }: ScreenConnectedProps) => {\n const theme = useTheme();\n const copy = useCopyToClipboard();\n const [{ data: networkData }] = useNetwork();\n const [{ data: accountData }] = useAccount();\n\n const chain = networkData.chain;\n const wallet = accountData?.connector;\n const accountAddress = accountData?.address;\n\n useEffect(() => {\n if (networkData.chain?.unsupported || !accountAddress) {\n actions.goToScreen(ScreenType.Error);\n }\n }, [networkData.chain, accountAddress]);\n\n const handleCopyAddress = useCallback(\n () => copy(accountAddress),\n [accountAddress, copy]\n );\n\n return (\n \n Active Wallet\n \n \n \n \n {wallet?.id === \"unknown\" ? \"Wallet\" : wallet?.name}\n \n \n
\n \n \n \n \n
\n \n
\n \n \n \n {`Connected to ${chain ? chain.name : \"Unknown\"} Network`}\n \n \n
\n \n \n );\n};\n\nconst Title = styled.h4`\n color: ${(props) => props.theme.border};\n margin-bottom: ${2 * GU}px;\n ${textStyle(\"body4\")};\n`;\n\nconst WalletIcon = styled.img<{ size: string | number }>`\n ${({ size }) => size && `width: ${size}px; height: ${size}px;`};\n margin-right: ${0.5 * GU}px;\n transform: translateY(-2px);\n`;\n\nconst CopyButton = styled(ButtonBase)`\n display: flex;\n align-items: center;\n &:active {\n color: ${(props) => props.theme.focus};\n }\n`;\n\nconst NetworkInfo = styled.div`\n display: flex;\n align-items: center;\n color: ${(props) => props.theme.positive};\n ${textStyle(\"body4\")};\n`;\n\nconst WalletName = styled.span`\n color: ${(props) => props.theme.border};\n ${textStyle(\"body2\")};\n`;\n", "import { useCallback } from \"react\";\nimport { useToast } from \"@blossom-labs/rosette-ui\";\nimport { writeText as copy } from \"clipboard-polyfill\";\n\nexport function useCopyToClipboard() {\n const toast = useToast();\n\n return useCallback(\n (text, confirmationMessage = \"Copied\") => {\n copy(text);\n if (confirmationMessage) {\n toast(confirmationMessage);\n }\n },\n [toast]\n );\n}\n", "import { GU, textStyle, useTheme } from \"@blossom-labs/rosette-ui\";\nimport styled from \"styled-components\";\n\nimport { getWalletIconPath } from \"../helpers\";\nimport { LoadingRing } from \"../LoadingRing\";\nimport { useAccounModuleStore } from \"../use-account-module-store\";\n\nexport const ScreenConnecting = () => {\n const theme = useTheme();\n const { selectedConnector: wallet } = useAccounModuleStore();\n\n if (!wallet) {\n return null;\n }\n\n const walletIcon = getWalletIconPath(wallet.id);\n\n return (\n \n \n \n {walletIcon && }\n \n Connecting to {wallet.name}\n \n Log into {wallet.name || \"Unknown\"}. You may be temporarily redirected\n to a new screen.\n

\n
\n );\n};\n\nconst Container = styled.section`\n display: flex;\n flex-grow: 1;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: ${2 * GU}px;\n height: 100%;\n text-align: center;\n`;\n\nconst SpinnerContainer = styled.div`\n position: relative;\n width: ${10.5 * GU}px;\n height: ${10.5 * GU}px;\n`;\n\nconst SpinnerImage = styled.div<{ src: string }>`\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: 50% 50% / auto ${5 * GU}px no-repeat url(${(props) => props.src});\n`;\n\nconst Title = styled.h1`\n padding-top: ${2 * GU}px;\n font-weight: 600;\n ${textStyle(\"body1\")};\n color: ${({ theme }) => theme.content};\n`;\n", "import {\n Button,\n GU,\n IconError,\n Link,\n textStyle,\n useTheme,\n} from \"@blossom-labs/rosette-ui\";\nimport { useMemo } from \"react\";\nimport styled from \"styled-components\";\nimport {\n ChainNotConfiguredError,\n useAccount,\n useConnect,\n useNetwork,\n} from \"wagmi\";\nimport type { Chain } from \"wagmi\";\n\nimport { getNetworkLogo } from \"~/utils/client/icons.client\";\nimport { getNetworkError } from \"../helpers\";\nimport type { PromptedAction } from \"../use-account-module-store\";\n\nconst buildSwitchAction = (chain: Chain): PromptedAction => ({\n title: `Connecting to ${chain.name} network`,\n subtitle: `Creating and/or switching to the ${chain.name} network (id: ${chain.id}) in your wallet. You may be temporarily redirected to a new screen.`,\n image: getNetworkLogo(chain.id),\n});\n\ntype ScreenErrorProps = {\n onSwitchNetwork(switchAction: PromptedAction, chain: Chain): void;\n onBack(): void;\n};\n\nexport const ScreenError = ({ onSwitchNetwork, onBack }: ScreenErrorProps) => {\n const theme = useTheme();\n const [{ data: networkData, error: networkError }, switchNetwork] =\n useNetwork();\n const [{ error: connectError }] = useConnect();\n const [{ error: accountError }] = useAccount();\n\n const error =\n getNetworkError(networkError, networkData) || connectError || accountError;\n\n const [title, secondary] = useMemo(() => {\n if (\n error instanceof ChainNotConfiguredError ||\n networkData.chain?.unsupported\n ) {\n return [\n \"Wrong network\",\n \n Please select one of these networks:{\" \"}\n {networkData.chains.map((chain, index, chains) =>\n switchNetwork ? (\n {\n onSwitchNetwork(buildSwitchAction(chain), chain);\n }}\n >\n {`${chain.name}${index < chains.length - 1 ? \", \" : \"\"}`}\n \n ) : (\n <>{chain.name}\n )\n )}\n ,\n ];\n }\n\n return [\n \"Failed to enable your account\",\n \"You can try another Ethereum wallet.\",\n ];\n }, [error, networkData, onSwitchNetwork, switchNetwork]);\n\n return (\n \n \n
\n \n
\n {title}\n \n {secondary}\n

\n
\n \n \n \n
\n );\n};\n\nconst Container = styled.section`\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: ${2 * GU}px;\n padding-top: ${1 * GU}px;\n height: 100%;\n`;\n\nconst ErrorContainer = styled.div`\n flex-grow: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n text-align: center;\n`;\n\nconst Title = styled.h1`\n font-weight: 600;\n ${textStyle(\"body1\")};\n color: ${({ theme }) => theme.content};\n`;\n\nconst ButtonContainer = styled.div`\n flex-grow: 0;\n margin-top: ${1 * GU}px;\n width: 100%;\n`;\n", "import {\n ButtonBase,\n GU,\n Link,\n RADIUS,\n textStyle,\n} from \"@blossom-labs/rosette-ui\";\nimport styled from \"styled-components\";\nimport { useConnect } from \"wagmi\";\nimport type { Connector } from \"wagmi\";\n\nimport { getWalletIconPath } from \"../helpers\";\n\nexport const ScreenWallets = ({\n onConnect,\n}: {\n onConnect(connector: Connector): void;\n}) => {\n const [{ data: connectData }] = useConnect();\n\n return (\n
\n Ethereum Wallets\n \n \n {connectData.connectors.map((connector) => {\n return (\n onConnect(connector)}\n />\n );\n })}\n \n \n \n What is a wallet?\n \n \n \n
\n );\n};\n\ntype WalletButtonProps = {\n icon?: string;\n name?: string;\n ready?: boolean;\n onClick: (connector: Connector) => void;\n};\n\nfunction WalletButton({\n icon,\n name,\n ready = true,\n onClick,\n}: WalletButtonProps): JSX.Element {\n return (\n \n {name && (\n <>\n {icon && (\n \"\"\n )}\n {name}\n {!ready && (Unsupported)}\n \n )}\n \n );\n}\n\nconst MainHeader = styled.h4`\n padding-top: ${2 * GU}px;\n padding-left: ${2 * GU}px;\n color: ${({ theme }) => theme.border};\n margin-bottom: ${2 * GU}px;\n ${textStyle(\"body2\")};\n`;\n\nconst Container = styled.div`\n display: flex;\n flex-direction: column;\n justify-content: center;\n width: 100%;\n padding: ${2 * GU}px ${2 * GU}px 0;\n`;\n\nconst WalletButtonsContainer = styled.div`\n display: grid;\n grid-gap: ${1.5 * GU}px;\n grid-auto-flow: row;\n grid-template-columns: repeat(2, 1fr);\n`;\n\nconst LinkContainer = styled.div`\n display: flex;\n justify-content: center;\n margin-top: ${2 * GU}px;\n margin-bottom: ${1 * GU}px;\n`;\n\nconst WalletButtonBase = styled(ButtonBase)`\n position: relative;\n opacity: ${(props) => (props.ready ? 1 : 0.6)};\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: ${12 * GU}px;\n border: 1px solid ${(props) => props.theme.border};\n &:hover {\n background-color: ${({ theme }) => theme.surfaceUnder.alpha(0.1)};\n }\n box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.15);\n &:active {\n top: 1px;\n box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1);\n }\n`;\n\nconst WalletName = styled.div`\n margin-top: ${1 * GU}px;\n ${textStyle(\"body1\")};\n color: ${({ theme }) => theme.content};\n`;\n\nconst Subtitle = styled.div`\n margin-top: ${-0.5 * GU}px;\n ${textStyle(\"body4\")};\n color: ${({ theme }) => theme.contentSecondary};\n`;\n", "import { GU, textStyle } from \"@blossom-labs/rosette-ui\";\nimport { NavLink, useLocation } from \"@remix-run/react\";\nimport styled from \"styled-components\";\nimport { CompactMenu } from \"./CompactMenu\";\n\nimport homeIcon from \"~/assets/sidebar-home.svg\";\nimport entriesIcon from \"~/assets/sidebar-entries.svg\";\nimport guidelinesIcon from \"~/assets/sidebar-guidelines.svg\";\nimport swapIcon from \"~/assets/sidebar-swap.svg\";\n\nexport type NavigationItem = {\n icon: string;\n label: string;\n to: string;\n};\n\nconst navigationItem: NavigationItem[] = [\n { icon: homeIcon, label: \"Home\", to: \"/home\" },\n { icon: entriesIcon, label: \"All Entries\", to: \"/entries\" },\n { icon: guidelinesIcon, label: \"Guidelines\", to: \"/guidelines\" },\n { icon: swapIcon, label: \"Swap RST\", to: \"/swap\" },\n];\n\nexport const NavSection = ({ compact }: { compact: boolean }) => {\n const { pathname } = useLocation();\n\n return compact ? (\n \n ) : (\n \n {navigationItem.map(({ label, to }) => {\n const renderBottom = to !== \"/home\" && pathname === to;\n\n return (\n \n {label === \"Home\" ? \"rosette\" : label}\n \n );\n })}\n \n );\n};\n\nconst LiStyled = styled.li<{ renderBottom: boolean }>`\n ${({ renderBottom }) =>\n renderBottom && \"padding-bottom: 2px; border-bottom: 1px solid;\"}\n`;\n\nconst NavLinksList = styled.ul`\n display: flex;\n align-items: center;\n gap: ${6 * GU}px;\n list-style: none;\n ${textStyle(\"body2\")};\n color: ${(props) => props.theme.content};\n\n li:first-child {\n ${textStyle(\"title2\")};\n }\n\n > li {\n transition: all 200ms ease;\n padding-bottom: 2px;\n &:not(:first-child):hover {\n border-bottom: 1px solid;\n }\n }\n\n li > * {\n text-decoration: none;\n }\n`;\n", "import {\n ButtonBase,\n GU,\n IconMenu,\n useViewport,\n} from \"@blossom-labs/rosette-ui\";\nimport { useState } from \"react\";\nimport { useLocation } from \"@remix-run/react\";\nimport styled from \"styled-components\";\nimport type { NavigationItem } from \"..\";\nimport { MenuItem } from \"./MenuItem\";\nimport { Sidebar } from \"./Sidebar\";\n\nconst MenuButton = ({ onClick }: { onClick(): void }) => (\n \n \n \n \n \n);\n\nconst MIN_SIDEBAR_WIDTH = 240;\n\nexport const CompactMenu = ({ items }: { items: NavigationItem[] }) => {\n const { pathname } = useLocation();\n const { width } = useViewport();\n const [displaySidebar, setDisplaySidebar] = useState(false);\n\n const sidebarWidth = Math.min(MIN_SIDEBAR_WIDTH, width * 0.6);\n\n const toggleSidebar = () => setDisplaySidebar((prev) => !prev);\n\n return (\n
\n \n \n \n {items.map((i) => (\n
  • \n \n
  • \n ))}\n
    \n \n
    \n );\n};\n\nconst ButtonContainer = styled.div`\n width: ${8 * GU}px;\n height: 100%;\n border-right: 1px solid ${({ theme }) => theme.border};\n display: flex;\n align-self: stretch;\n justify-content: center;\n align-items: center;\n`;\n\nconst StyledMenuIcon = styled(IconMenu)`\n width: ${4 * GU}px;\n height: ${4 * GU}px;\n color: ${({ theme }) => theme.border};\n`;\n\nconst NavContainer = styled.ul`\n list-style: none;\n`;\n", "import { GU, useTheme } from \"@blossom-labs/rosette-ui\";\nimport { Link } from \"@remix-run/react\";\nimport styled from \"styled-components\";\nimport type { NavigationItem } from \"..\";\n\ntype MenuItemProps = {\n item: NavigationItem;\n active?: boolean;\n onClick(): void;\n};\n\nexport const MenuItem = ({\n item: { icon, label, to },\n active = false,\n onClick,\n}: MenuItemProps) => {\n const theme = useTheme();\n\n return (\n
    \n {active && }\n \n \n {icon && (\n \n )}\n {label}\n \n \n
    \n );\n};\n\nconst ActiveBar = styled.div`\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n width: 4px;\n background: #7c5fe0;\n`;\n\nconst InnerNavLink = styled.div`\n display: flex;\n align-items: center;\n gap: ${1 * GU}px;\n padding: ${1.5 * GU}px;\n\n & img {\n width: 20px;\n height: 20px;\n }\n\n & > span {\n color: ${({ theme }) => theme.content};\n }\n`;\n", "import { RootPortal } from \"@blossom-labs/rosette-ui\";\nimport type { ReactNode } from \"react\";\nimport { a, useTransition } from \"@react-spring/web\";\nimport styled from \"styled-components\";\n\ntype SidebarProps = {\n children: ReactNode;\n show: boolean;\n width: number;\n onToggle(): void;\n};\n\nexport const Sidebar = ({ children, show, width, onToggle }: SidebarProps) => {\n const sidebarTransition = useTransition(show, {\n from: {\n marginLeft: `-${width}px`,\n opacity: 0,\n },\n enter: {\n marginLeft: \"0\",\n opacity: 1,\n },\n leave: {\n marginLeft: `-${width}px`,\n opacity: 0,\n },\n unique: true,\n config: { mass: 5, tension: 1500, friction: 200 },\n delay: 100,\n });\n\n return sidebarTransition((styles, show) => {\n return (\n show && (\n \n \n {children}\n \n \n \n )\n );\n });\n};\n\nconst AnimatedSidebar = styled(a.div)<{ width: number; $zIndex: number }>`\n height: 100vh;\n position: absolute;\n border-right: 1px solid ${({ theme }) => theme.border};\n background-color: ${({ theme }) => theme.background};\n ${({ width, $zIndex }) => `\n z-index: ${$zIndex};\n width: ${width}px;\n top: 0;\n bottom: 0;\n left: 0;\n `};\n`;\n\nconst OpaqueBackground = styled(a.div)<{ $offset: number; $show: boolean }>`\n position: absolute;\n top: 0;\n right: 0;\n left: ${({ $offset }) => $offset}px;\n z-index: 1;\n height: 100%;\n background: ${({ theme }) => theme.overlay.alpha(0.7)};\n pointer-events: ${({ $show }) => ($show ? \"auto\" : \"none\")};\n`;\n", "import { utils } from \"ethers\";\nimport type { BigNumber } from \"ethers\";\nimport { createContext, useContext, useEffect, useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { useContractRead } from \"wagmi\";\n\nimport rosetteStoneAbi from \"../abi/RosetteStone.json\";\n\ntype RosetteStoneContextProps = {\n currentGuideline?: {\n collateralAmount: BigNumber;\n cooldownPeriod: BigNumber;\n guidelinesCID: string;\n };\n isLoading: boolean;\n};\n\nconst RosetteStoneContext = createContext(\n {} as RosetteStoneContextProps\n);\n\nexport const RosetteStone = ({ children }: { children: ReactNode }) => {\n const [currentGuideline, setCurrentGuideline] =\n useState();\n const [isResConsumed, setIsResConsumed] = useState(false);\n const [guidelineResponse] = useContractRead(\n {\n addressOrName: window.ENV.ROSETTE_STONE_ADDRESS,\n contractInterface: rosetteStoneAbi,\n },\n \"getCurrentGuideline\"\n );\n\n useEffect(() => {\n if (isResConsumed || !guidelineResponse.data || guidelineResponse.loading) {\n return;\n }\n\n const [cooldownPeriod, collateralAmount, rawGuidelinesCID] =\n guidelineResponse.data;\n\n setCurrentGuideline({\n collateralAmount,\n cooldownPeriod,\n guidelinesCID: utils.toUtf8String(rawGuidelinesCID),\n });\n setIsResConsumed(true);\n }, [isResConsumed, guidelineResponse]);\n\n return (\n \n {children}\n \n );\n};\n\nexport const useRosetteStone = () => {\n return useContext(RosetteStoneContext);\n};\n", "import type { LoaderFunction } from \"@remix-run/node\";\nimport { getSanitizedEntriesData } from \"~/utils/server/entries-data.server\";\nimport { fetchContractFnEntries } from \"~/utils/server/subgraph.server\";\n\nexport const loader: LoaderFunction = async ({ request }) => {\n const { searchParams } = new URL(request.url);\n const bytecodeHash = searchParams.get(\"bytecodeHash\");\n\n if (!bytecodeHash) {\n throw new Response(\"Expected contract param\", {\n status: 400,\n statusText: \"Expected search params\",\n });\n }\n\n const subgraphFnEntriesData = await fetchContractFnEntries(bytecodeHash);\n\n return getSanitizedEntriesData(subgraphFnEntriesData);\n};\n", "import type { FnEntry } from \"~/types\";\nimport { ipfsResolver } from \"~/utils/ipfs\";\n\nconst ipfs = ipfsResolver();\n\nexport const getSanitizedEntriesData = async (\n fns: FnEntry[]\n): Promise => {\n const responses = (await Promise.allSettled(\n fns.filter((f) => !f.notice).map((f) => fetchFallbackData(f))\n )) as PromiseSettledResult[];\n\n const fulfilledResponses = responses.filter(\n (r) => r.status === \"fulfilled\"\n ) as PromiseFulfilledResult[];\n\n const fnsWithFallbackData = fulfilledResponses.map((r) => r.value);\n\n return fns.map((f) => {\n const fnFallback = fnsWithFallbackData.find(\n ({ sigHash }) => f.sigHash == sigHash\n );\n if (fnFallback) {\n return fnFallback;\n }\n return f;\n });\n};\n\nexport const fetchFallbackData = async (f: FnEntry): Promise => {\n // fallback to fetch data from IPFS\n const data = await ipfs.json(f.cid);\n\n return {\n ...f,\n ...data,\n };\n};\n", "import { createCacheStore } from \"./cache-store\";\n\nexport type IpfsResolver = {\n url: (cid: string, path?: string) => Promise;\n json: (cid: string, path?: string) => Promise;\n};\n\nexport const DEFAULT_IPFS_CACHED_ITEMS = 100;\nexport const DEFAULT_IPFS_URL =\n \"https://rosette-stone.infura-ipfs.io/ipfs/{cid}{path}\";\n\nexport function ipfsResolver(\n urlTemplate: string = DEFAULT_IPFS_URL,\n cache: number = DEFAULT_IPFS_CACHED_ITEMS\n): IpfsResolver {\n const cacheStore = cache === 0 ? null : createCacheStore(cache);\n\n return {\n async json(cid: string, path?): Promise {\n const url = await this.url(cid, path);\n\n const fetchJson = async () => {\n let response;\n let data;\n\n try {\n response = await fetch(url);\n } catch (_) {\n throw new Error(`Couldn\u2019t fetch ${url}.`);\n }\n\n try {\n data = await response.json();\n } catch (_) {\n throw new Error(`Couldn\u2019t parse the result of ${url} as JSON.`);\n }\n\n return data;\n };\n\n return cacheStore?.get(url, fetchJson) || fetchJson();\n },\n async url(cid: string, path?): Promise {\n const url = urlTemplate.replace(/\\{cid\\}/, cid);\n if (!path) {\n return url.replace(/\\{path\\}/, \"\");\n }\n if (!path.startsWith(\"/\")) {\n path = `/${path}`;\n }\n return url.replace(/\\{path\\}/, path);\n },\n };\n}\n", "type CacheStoreCallback = () => Promise;\n\nexport function createCacheStore(\n limit = 10\n): {\n cachedIndex: (id: string) => number;\n clear: () => void;\n get: (id: string, callback: CacheStoreCallback) => Promise;\n touch: (index: number) => void;\n} {\n const cache: [string, T][] = Array(limit);\n\n return {\n clear(): void {\n cache.length = 0;\n },\n cachedIndex(id: string): number {\n return cache.findIndex((entry) => id === entry?.[0]);\n },\n touch(index: number): void {\n cache.unshift(cache.splice(index, 1)[0]);\n },\n async get(id: string, callback: CacheStoreCallback): Promise {\n let cachedIndex = this.cachedIndex(id);\n if (cachedIndex > -1) {\n this.touch(cachedIndex);\n return cache[0][1];\n }\n\n const data = await callback();\n\n // Prevents to cache the same value multiple times, in case the same\n // value gets loaded several times in parallel.\n cachedIndex = this.cachedIndex(id);\n if (cachedIndex > -1) {\n this.touch(cachedIndex);\n return cache[0][1];\n }\n\n cache.unshift([id, data]);\n cache.length = limit;\n\n return data;\n },\n };\n}\n", "import type { FnEntry } from \"~/types\";\nimport { FnDescriptionStatus } from \"~/types\";\n\ntype FunctionResult = {\n id: string;\n abi: string;\n cid: string;\n contract: {\n scope: string;\n };\n notice: string;\n sigHash: string;\n submitter: {\n address: string;\n };\n disputed: boolean;\n guideline: {\n cooldownPeriod: number;\n };\n upsertAt: number;\n};\n\ntype QueryContractResult = {\n data: {\n contract: {\n functions: FunctionResult[];\n };\n };\n errors?: { message: string }[];\n};\n\ntype QueryFnsResult = {\n data: {\n functions: FunctionResult[];\n };\n errors?: { message: string }[];\n};\n\ntype QueryFnResult = {\n data: {\n function: FunctionResult;\n };\n errors?: { message: string }[];\n};\n\nconst gql = String.raw;\n\nconst buildContractId = (\n rosetteStoneAddress: string,\n bytecodeHash: string\n): string => `${rosetteStoneAddress.toLowerCase()}-${bytecodeHash}`;\n\nconst fetchFromGraphQL = async (query: string) => {\n if (!process.env.SUBGRAPH_URI) {\n throw new Error(\"SUBGRAPH_URI is required\");\n }\n\n const body: any = { query };\n\n return fetch(process.env.SUBGRAPH_URI, {\n body: JSON.stringify(body),\n headers: { \"Content-Type\": \"application/json\" },\n method: \"POST\",\n });\n};\n\nconst getEntryStatus = (fnResult: FunctionResult): FnDescriptionStatus => {\n const date = Date.now() / 1000;\n\n if (fnResult.disputed) {\n return FnDescriptionStatus.Challenged;\n }\n\n const timeSince = fnResult.upsertAt + fnResult.guideline.cooldownPeriod;\n\n if (date > timeSince) {\n return FnDescriptionStatus.Added;\n } else {\n return FnDescriptionStatus.Pending;\n }\n};\n\nconst parseFnResult = (fnResult: FunctionResult): FnEntry => {\n const { id, abi, cid, contract, notice, sigHash, submitter, upsertAt } =\n fnResult;\n\n return {\n id,\n abi,\n cid,\n contract: contract.scope,\n notice,\n sigHash,\n status: getEntryStatus(fnResult),\n submitter: submitter.address,\n upsertAt,\n };\n};\n\nexport const fetchContractFnEntries = async (\n bytecodeHash: string\n): Promise => {\n const contractId = buildContractId(\n process.env.ROSETTE_STONE_ADDRESS!,\n bytecodeHash\n );\n\n try {\n const rawResponse = await fetchFromGraphQL(\n gql`\n {\n contract(id: \"${contractId}\") {\n functions {\n id\n cid\n contract {\n scope\n }\n notice\n sigHash\n submitter {\n address\n }\n guideline {\n cooldownPeriod\n }\n upsertAt\n }\n }\n }\n `\n );\n\n const result = (await rawResponse.json()) as QueryContractResult;\n\n if (result.errors?.length) {\n const err = result.errors[0];\n console.error(err);\n throw new Error(err.message);\n }\n\n if (!result.data.contract) {\n return [];\n }\n\n return result.data.contract.functions.map(parseFnResult);\n } catch (err) {\n throw new Response(\n \"There was an error fetching the contract's function descriptions\",\n { status: 500, statusText: \"Subgraph Error\" }\n );\n }\n};\n\nexport const fetchFnEntries = async (): Promise => {\n try {\n const rawResponse = await fetchFromGraphQL(\n gql`\n {\n functions {\n id\n abi\n contract {\n scope\n }\n cid\n notice\n sigHash\n submitter {\n address\n }\n guideline {\n cooldownPeriod\n }\n upsertAt\n }\n }\n `\n );\n\n const result = (await rawResponse.json()) as QueryFnsResult;\n\n if (result.errors?.length) {\n const err = result.errors[0];\n console.error(err);\n throw new Error(err.message);\n }\n\n if (!result.data.functions) {\n return [];\n }\n\n return result.data.functions.map(parseFnResult);\n } catch (err) {\n throw new Response(\n \"There was an error fetching the contract's function descriptions\",\n { status: 500, statusText: \"Subgraph Error\" }\n );\n }\n};\n\nexport const fetchFnEntry = async (\n entryId: string\n): Promise => {\n try {\n const rawResponse = await fetchFromGraphQL(\n gql`\n {\n function(id: \"${entryId}\") {\n id\n abi\n contract {\n scope\n }\n cid\n notice\n sigHash\n submitter {\n address\n }\n guideline {\n cooldownPeriod\n }\n upsertAt\n }\n }\n `\n );\n\n const result = (await rawResponse.json()) as QueryFnResult;\n\n if (result.errors?.length) {\n const err = result.errors[0];\n console.error(err);\n throw new Error(err.message);\n }\n\n if (!result.data.function) {\n return undefined;\n }\n\n return parseFnResult(result.data.function);\n } catch (err) {\n throw new Response(\n \"There was an error fetching the contract's function descriptions\",\n { status: 500, statusText: \"Subgraph Error\" }\n );\n }\n};\n", "import type { Chain } from \"wagmi\";\n\nexport enum FnDescriptionStatus {\n Available = \"available\",\n Pending = \"pending\",\n Added = \"added\",\n Challenged = \"challenged\",\n}\n\nexport type FnEntry = {\n id: string;\n abi: string;\n cid: string;\n contract: string;\n notice: string;\n sigHash: string;\n status: FnDescriptionStatus;\n submitter: string;\n upsertAt: number;\n};\n\nexport type ContractData = {\n abi: string;\n address: string;\n bytecode: string;\n name: string;\n network: Chain;\n};\n\nexport type AggregateContract = {\n proxy?: ContractData;\n implementation?: ContractData;\n};\n", "import type { ActionFunction } from \"@remix-run/node\";\nimport { json } from \"@remix-run/node\";\nimport { ipfs } from \"~/utils/server/ipfs.server\";\n\ntype IPFSResponseData = Record;\n\nexport type IPFSData = {\n bytecodeHash: string;\n functions: {\n description: string;\n fullName: string;\n sigHash: string;\n }[];\n};\n\nexport const action: ActionFunction = async ({ request }) => {\n const uploadData = await request.formData();\n\n if (!uploadData.has(\"functions\") || !uploadData.has(\"bytecodeHash\")) {\n return new Response(\"Function descriptions not provided\", { status: 400 });\n }\n\n const bytecodeHash = uploadData.get(\"bytecodeHash\");\n const fnsString = uploadData.get(\"functions\")!.toString();\n const fnDescriptionsData = JSON.parse(fnsString) as IPFSData[\"functions\"];\n\n const uploadRequests = fnDescriptionsData.map(({ description, fullName }) => {\n const descriptionJson = JSON.stringify({\n bytecodeHash,\n abi: fullName,\n notice: description,\n });\n\n return ipfs.add(descriptionJson);\n });\n\n try {\n const addResults = await Promise.all(uploadRequests);\n\n const responseData = addResults.reduce((data, addResult, index) => {\n const fnDescription = fnDescriptionsData[index];\n data[fnDescription.sigHash] = addResult.cid.toString();\n return data;\n }, {} as IPFSResponseData);\n\n return json(responseData);\n } catch (err) {\n throw new Response(\n `An error occured when uploading the function descriptions: ${err}`,\n { status: 500 }\n );\n }\n};\n", "import type { IPFSHTTPClient, Options } from \"ipfs-http-client\";\nimport { create } from \"ipfs-http-client\";\n\ndeclare global {\n var __ipfs: IPFSHTTPClient | undefined;\n}\n\nlet ipfs: IPFSHTTPClient;\n\nconst INFURA_IPFS_API_KEY = process.env.INFURA_IPFS_API_KEY;\n\nif (!INFURA_IPFS_API_KEY) {\n throw new Error(\"INFURA_IPFS_API_KEY not found\");\n}\n\nconst codifiedBasicAuth = Buffer.from(INFURA_IPFS_API_KEY).toString(\"base64\");\n\nconst config: Options = {\n url: \"https://ipfs.infura.io:5001\",\n headers: { Authorization: `Basic ${codifiedBasicAuth}` },\n};\n\nif (process.env.NODE_ENV === \"production\") {\n ipfs = create(config);\n} else {\n if (!global.__ipfs) {\n global.__ipfs = create(config);\n }\n ipfs = global.__ipfs;\n}\n\nexport { ipfs };\n", "import { utils } from \"ethers\";\nimport { useEffect, useState } from \"react\";\nimport { json } from \"@remix-run/node\";\nimport type { LoaderFunction } from \"@remix-run/node\";\nimport { useFetcher, useLoaderData } from \"@remix-run/react\";\nimport styled from \"styled-components\";\nimport { AppScreen } from \"~/components/AppLayout/AppScreen\";\nimport { ContractDescriptorScreen } from \"~/components/ContractDescriptorScreen\";\nimport { ContractSelectorScreen } from \"~/components/ContractSelectorScreen\";\nimport { SmoothDisplayContainer } from \"~/components/SmoothDisplayContainer\";\nimport type { ContractData, AggregateContract } from \"~/types\";\nimport { fetchContracts } from \"~/utils/server/contract-data.server\";\n\ntype LoaderData = {\n contractAddress: string;\n contracts: AggregateContract[];\n};\n\nexport const loader: LoaderFunction = async ({ request }) => {\n const { searchParams } = new URL(request.url);\n const contractAddress = searchParams.get(\"contract\");\n\n if (!contractAddress) {\n throw new Response(\"Expected contract param\", {\n status: 400,\n statusText: \"Expected search params\",\n });\n }\n\n const contracts = await fetchContracts(contractAddress);\n\n return json({\n contracts,\n contractAddress,\n });\n};\n\nexport default function Describe() {\n const { contracts, contractAddress } = useLoaderData();\n const contractDescriptionsFetcher = useFetcher();\n const [selectedContractData, setSelectedContractData] =\n useState();\n\n useEffect(() => {\n if (\n !selectedContractData?.bytecode ||\n contractDescriptionsFetcher.type !== \"init\"\n ) {\n return;\n }\n\n contractDescriptionsFetcher.load(\n `/contract-descriptions-search?bytecodeHash=${utils.id(\n selectedContractData.bytecode\n )}`\n );\n }, [selectedContractData?.bytecode, contractDescriptionsFetcher]);\n\n return (\n \n \n {selectedContractData && contractDescriptionsFetcher.type === \"done\" ? (\n \n \n \n ) : (\n \n )}\n \n \n );\n}\n\nconst Container = styled.div`\n display: flex;\n justify-content: center;\n align-items: start;\n height: 100%;\n width: 100%;\n`;\n", "import type { ReactNode } from \"react\";\nimport { useEffect } from \"react\";\nimport { a } from \"@react-spring/web\";\nimport { useOutletContext } from \"@remix-run/react\";\nimport styled from \"styled-components\";\nimport { useAppReady } from \"~/providers/AppReady\";\nimport type { AppContext } from \"~/App\";\n\ntype AppScreenProps = {\n children: ReactNode;\n hideBottomBar?: boolean;\n hideTopBar?: boolean;\n};\n\nexport const AppScreen = ({\n children,\n hideBottomBar = false,\n hideTopBar = false,\n}: AppScreenProps) => {\n const { appReadyTransition } = useAppReady();\n const { displayBottomBar, displayTopBar } = useOutletContext();\n\n useEffect(() => {\n displayBottomBar(!hideBottomBar);\n\n displayTopBar(!hideTopBar);\n }, [hideBottomBar, hideTopBar, displayBottomBar, displayTopBar]);\n\n return appReadyTransition(\n ({ progress, screenTransform }, ready) =>\n ready && (\n \n {children}\n \n )\n );\n};\n\nconst AnimatedContainer = styled(a.div)`\n display: flex;\n flex-direction: column;\n flex-grow: 1;\n width: 100%;\n margin: 0 auto;\n`;\n", "import { Button, GU, LoadingRing, useViewport } from \"@blossom-labs/rosette-ui\";\nimport { useFetcher } from \"@remix-run/react\";\nimport type { Fetcher } from \"@remix-run/react/transition\";\nimport { utils } from \"ethers\";\nimport { useCallback, useEffect, useState } from \"react\";\nimport styled from \"styled-components\";\nimport { useAccount } from \"wagmi\";\n\nimport scrollIcon from \"./assets/scroll-icon.svg\";\nimport handIcon from \"./assets/hand-icon.svg\";\nimport { Pagination } from \"./Pagination\";\nimport {\n actions,\n selectors,\n useContractDescriptorStore,\n} from \"./use-contract-descriptor-store\";\nimport type {\n Function,\n UserFnDescription,\n} from \"./use-contract-descriptor-store\";\nimport type { ContractData, FnEntry } from \"~/types\";\nimport useRosetteActions from \"./useRosetteActions\";\nimport { HelperFunctionsPicker } from \"./HelperFunctionsPicker\";\nimport { FnDescriptorsCarousel } from \"./FnDescriptorsCarousel\";\nimport type { IPFSData } from \"~/routes/fn-descriptions-upload\";\nimport debounce from \"lodash.debounce\";\nimport { FunctionDescriptorFilters } from \"./FunctionDescriptorFilters\";\n\nconst FN_DESCRIPTOR_DEFAULT_HEIGHT = \"527px\";\n\ntype ContractDescriptorScreenProps = {\n contractAddress: string;\n contractData: ContractData;\n currentFnEntries: FnEntry[];\n};\n\nconst buildIPFSUploadData = (\n fnDescriptorEntries: Function[],\n userFnDescriptions: Record\n): IPFSData[\"functions\"] => {\n return Object.keys(userFnDescriptions).map((sigHash) => {\n const fullName = fnDescriptorEntries.find(\n (e) => e.sigHash === sigHash\n )?.fullName;\n\n if (!fullName) {\n throw new Error(\n `Couldn't upload to IPFS: function ${sigHash} not found.`\n );\n }\n\n return {\n description: userFnDescriptions[sigHash].description,\n fullName,\n sigHash,\n };\n });\n};\n\nconst uploadFetcherReturnedData = (fetcher: Fetcher): boolean =>\n fetcher.state === \"loading\" &&\n fetcher.type === \"actionReload\" &&\n fetcher.data;\n\nexport const ContractDescriptorScreen = ({\n contractAddress,\n contractData: { abi, bytecode },\n currentFnEntries,\n}: ContractDescriptorScreenProps) => {\n const [callingContract, setCallingContract] = useState(false);\n const { below } = useViewport();\n const [{ data: accountData }] = useAccount();\n const { fnSelected, filteredFnDescriptorEntries, userFnDescriptions } =\n useContractDescriptorStore();\n const actionFetcher = useFetcher();\n const { upsertEntries } = useRosetteActions();\n const bytecodeHash = utils.id(bytecode);\n const fnDescriptionsCounter = selectors.fnDescriptionsCounter();\n const compactMode = below(\"large\");\n const submittingEntries =\n actionFetcher.state === \"submitting\" ||\n actionFetcher.state === \"loading\" ||\n callingContract;\n const submitDisabled =\n !accountData?.address || fnDescriptionsCounter === 0 || submittingEntries;\n\n const handleSubmit = useCallback(\n (event) => {\n event.preventDefault();\n\n const fnsData = buildIPFSUploadData(\n filteredFnDescriptorEntries,\n userFnDescriptions\n );\n\n actionFetcher.submit(\n { bytecodeHash, functions: JSON.stringify(fnsData) },\n {\n method: \"post\",\n action: \"/fn-descriptions-upload\",\n }\n );\n },\n [\n actionFetcher,\n bytecodeHash,\n filteredFnDescriptorEntries,\n userFnDescriptions,\n ]\n );\n\n useEffect(() => {\n const submitEntries = async () => {\n try {\n const sigs = Object.keys(userFnDescriptions).map((sigHash) => sigHash);\n const scopes = new Array(sigs.length).fill(bytecodeHash);\n const cids: string[] = Object.values(actionFetcher.data);\n\n setCallingContract(true);\n await upsertEntries(scopes, sigs, cids);\n window.alert(\"Entries submitted!\"); // TODO: Use tx feedback implementation\n\n // Should we redirect to the entries page?\n } catch (err) {\n console.error(`Error submitting entries: ${err}`);\n } finally {\n setCallingContract(false);\n }\n };\n\n /**\n * It keeps this effect from running more than once after action\n * fetcher returns data.\n */\n if (uploadFetcherReturnedData(actionFetcher)) {\n submitEntries();\n }\n }, [\n actionFetcher,\n bytecodeHash,\n contractAddress,\n upsertEntries,\n userFnDescriptions,\n ]);\n\n useEffect(() => {\n if (abi && currentFnEntries) {\n actions.setUpFnDescriptorEntries(abi, currentFnEntries);\n }\n }, [abi, currentFnEntries]);\n\n useEffect(() => {\n const onWheel = (e: WheelEvent) => {\n if (e.deltaY < 0) {\n actions.goToPrevFn();\n } else {\n actions.goToNextFn();\n }\n };\n\n const debouncedOnWheel = debounce(onWheel, 100);\n\n window.addEventListener(\"wheel\", debouncedOnWheel);\n\n return () => {\n window.removeEventListener(\"wheel\", debouncedOnWheel);\n };\n }, []);\n\n return (\n
    \n \n \n \n \n {filteredFnDescriptorEntries.length > 1 && (\n \n \n \n \n )}\n \n {filteredFnDescriptorEntries.length ? (\n \n ) : (\n No functions found.\n )}\n \n \n \n \n \n \n \n Submitting entries\u2026\n \n ) : (\n `Submit (${fnDescriptionsCounter})`\n )\n }\n type=\"submit\"\n mode=\"strong\"\n wide\n disabled={submitDisabled}\n />\n \n \n
    \n );\n};\n\nconst Layout = styled.div<{ compactMode: boolean }>`\n display: grid;\n height: 100%;\n width: 100%;\n padding: ${4 * GU}px;\n grid-gap: ${1 * GU}px;\n\n ${({ compactMode }) => `\n ${FiltersContainer} {\n grid-area: filters;\n }\n\n ${PaginationContainer} {\n grid-area: pagination;\n ${\n compactMode\n ? `\n flex-direction: column-reverse;\n `\n : `\n justify-self: start;\n `\n }\n }\n\n ${CarouselContainer} {\n grid-area: carousel;\n }\n\n ${FunctionsPickerContainer} {\n grid-area: picker;\n }\n\n ${SubmitContainer} {\n grid-area: submit;\n ${\n compactMode\n ? `\n width: 100%;\n `\n : `\n width: 230px;\n `\n };\n place-self: end;\n\n }\n ${\n compactMode\n ? `grid: \n [row1-start] \"filters\" 1fr [row1-end]\n [row2-start] \"picker\" 0.5fr [row2-end]\n [row3-start] \"carousel\" 5fr [row3-end]\n [row4-start] \"pagination\" 1fr [row4-end]\n [row5-start] \"submit\" 1fr [row5-end]\n / minmax(200px,${FN_DESCRIPTOR_DEFAULT_HEIGHT});\n\n justify-content: center;\n `\n : `grid: \n [row1-start] \"filters filters filters\" 1.5fr [row1-end]\n [row2-start] \"pagination carousel picker\" 8fr [row2-end]\n [row3-start] \". . submit\" 1fr [row3-end]\n / 1fr minmax(200px,${FN_DESCRIPTOR_DEFAULT_HEIGHT}) 1fr;\n `\n }\n `};\n`;\n\nconst FiltersContainer = styled.div`\n justify-self: center;\n color: ${({ theme }) => theme.content};\n`;\n\nconst PaginationContainer = styled.div`\n display: flex;\n align-items: center;\n gap: ${1 * GU}px;\n`;\n\nconst PaginationIcon = styled.img<{ size: number }>`\n ${({ size }) => `width: ${size}px; height: ${size}px;`};\n color: ${({ theme }) => theme.border};\n`;\n\nconst CarouselContainer = styled.div`\n min-width: 100%;\n height: 100%;\n display: flex;\n justify-content: center;\n align-items: center;\n`;\n\nconst FunctionsPickerContainer = styled.div`\n justify-self: end;\n`;\n\nconst SubmitContainer = styled.div``;\n\nconst SubmitButton = styled(Button)`\n box-sizing: border-box;\n padding: ${3 * GU}px;\n ${({ wide }) => wide && \"width: 100%;\"};\n`;\n\nconst EmptyContainer = styled.div`\n color: ${({ theme }) => theme.surfaceContent};\n`;\n", "import { GU } from \"@blossom-labs/rosette-ui\";\nimport styled from \"styled-components\";\nimport { PaginationItem } from \"./PaginationItem\";\nimport { PaginationSeparator } from \"./PaginationSeparator\";\n\nfunction paginationItems(\n pages: number,\n selected: number,\n inbetweenRange: number\n): number[] {\n // visibleItems + first and final item + ellipses\n const totalVisibleItems = inbetweenRange + 2 + 2;\n const all = [...Array(pages)].map((_, i) => i);\n /**\n * When having odd lengths use the previous number to get\n * an even distribution amount of visible items on both sides of the\n * selected number.\n */\n const normalizedLength =\n inbetweenRange % 2 === 0 ? inbetweenRange : inbetweenRange - 1;\n\n if (all.length <= totalVisibleItems) {\n return all;\n }\n\n const first = 0;\n const last = all.length - 1;\n const amountBySide = Math.floor(inbetweenRange / 2);\n const itemsToLast = last - selected;\n const prevSize =\n Math.min(amountBySide, Math.max(0, selected - first)) +\n /**\n * Add the missing items on one side or the other to keep\n * the visible items length at all times.\n */\n (itemsToLast < amountBySide ? amountBySide - itemsToLast : 0);\n const nextSize = normalizedLength - prevSize;\n\n let lowerPrevBound = Math.min(all.length, selected - prevSize);\n let upperNextBound = Math.min(all.length, selected + nextSize);\n\n const items = [];\n\n items.push(...all.slice(lowerPrevBound, upperNextBound + 1));\n\n // Add ellipses\n let leftEllipsisAdded = false,\n rightEllipsisAdded = false;\n\n if (lowerPrevBound > first + 1) {\n items.unshift(-1);\n leftEllipsisAdded = true;\n }\n if (upperNextBound < last - 1) {\n items.push(-1);\n rightEllipsisAdded = true;\n }\n\n // Always display the first & last items\n if (lowerPrevBound >= first + 1) {\n items.unshift(all[0]);\n }\n if (upperNextBound <= last - 1) {\n items.push(all[all.length - 1]);\n }\n\n const missingItems = totalVisibleItems - items.length;\n\n /**\n * To keep a constant pagination size we always add enough items\n * to reach the total total amount of visible items.\n * We add them on the left or right side of the selected item based on\n * where we put the ellipsis.\n */\n if (missingItems > 0 && !(leftEllipsisAdded && rightEllipsisAdded)) {\n if (leftEllipsisAdded) {\n items.splice(\n 2,\n 0,\n ...all.slice(lowerPrevBound - missingItems, lowerPrevBound)\n );\n } else {\n items.splice(\n items.length - 2,\n 0,\n ...all.slice(upperNextBound + 1, upperNextBound + missingItems + 1)\n );\n }\n }\n\n return items;\n}\n\ntype PaginationProps = {\n direction?: \"horizontal\" | \"vertical\";\n pages: number;\n // Length of elements between ellipses.\n inbetweenRange?: number;\n selected: number;\n size: number;\n touchMode?: boolean;\n onChange(index: number): void;\n};\n\nexport const Pagination = ({\n direction = \"horizontal\",\n pages,\n inbetweenRange = 9,\n selected,\n size,\n touchMode = false,\n onChange,\n}: PaginationProps) => {\n const items = paginationItems(pages, selected, inbetweenRange);\n const isHorizontal = direction === \"horizontal\";\n\n return (\n \n {items.map((pageIndex, i) =>\n pageIndex === -1 ? (\n \n ) : (\n \n )\n )}\n \n );\n};\n\nconst Container = styled.div<{ isHorizontal: boolean; touchMode: boolean }>`\n display: flex;\n flex-direction: ${({ isHorizontal }) => (isHorizontal ? \"row\" : \"column\")};\n gap: ${({ touchMode }) => (touchMode ? 0 : `${2 * GU}px`)};\n`;\n", "import { ButtonBase, RADIUS, textStyle } from \"@blossom-labs/rosette-ui\";\nimport { useCallback } from \"react\";\nimport styled from \"styled-components\";\n\ntype Direction = \"horizontal\" | \"vertical\";\n\ntype PaginationItemProps = {\n direction?: Direction;\n index: number;\n selected: boolean;\n size: number;\n onChange(index: number): void;\n};\n\nexport const PaginationItem = ({\n direction = \"horizontal\",\n selected,\n size,\n index,\n onChange,\n}: PaginationItemProps) => {\n const handleClick = useCallback(() => {\n onChange(index);\n }, [index, onChange]);\n\n return (\n
    \n {selected && }\n \n {index + 1}\n \n
    \n );\n};\n\nconst Dot = styled.div<{ direction: Direction }>`\n width: 3px;\n height: 3px;\n position: absolute;\n ${({ direction }) =>\n direction === \"horizontal\"\n ? `\n bottom: calc(-20%);\n left: calc(50% - 2px);\n `\n : `\n top: calc(50% - 1px);\n left: calc(-10%);\n `};\n background: ${({ theme }) => theme.content};\n`;\n\nconst StyledButtonBase = styled(ButtonBase)<{\n selected: boolean;\n}>`\n border-radius: ${RADIUS}px;\n ${({ selected, theme, size }) => `\n width: ${size}px;\n height: ${size}px;\n color: ${theme.border};\n ${textStyle(\"title3\")};\n ${\n selected &&\n `\n && {\n color: ${theme.content};\n }\n `\n };\n `}\n`;\n", "import { IconEllipsis } from \"@blossom-labs/rosette-ui\";\nimport { memo } from \"react\";\nimport styled from \"styled-components\";\n\ntype PaginationSeparatorProps = {\n isHorizontal?: boolean;\n size: number;\n};\n\nexport const PaginationSeparator = memo(function PaginationSeparator({\n isHorizontal = false,\n size,\n}: PaginationSeparatorProps) {\n return (\n \n \n \n );\n});\n\nconst Container = styled.div<{ isHorizontal: boolean; size: number }>`\n display: flex;\n align-items: center;\n justify-content: center;\n ${({ size }) => `\n width: ${size}px;\n height: ${size}px;\n `};\n transform: rotate(${({ isHorizontal }) => (isHorizontal ? 0 : \"90deg\")});\n`;\n\nconst StyledIconEllipsis = styled(IconEllipsis)`\n color: ${({ theme }) => theme.surfaceContentSecondary.alpha(0.4)};\n`;\n", "import { createStore } from \"@udecode/zustood\";\nimport type { SetImmerState, SetRecord, StoreApiGet } from \"@udecode/zustood\";\nimport { utils } from \"ethers\";\nimport type { FunctionFragment } from \"ethers/lib/utils\";\nimport type { FnEntry } from \"~/types\";\nimport { FnDescriptionStatus } from \"~/types\";\nimport { getFnSelector } from \"~/utils\";\n\nexport type Function = {\n fullName: string;\n sigHash: string;\n entry?: FnEntry;\n};\n\nexport type UserFnDescription = {\n sigHash: string;\n description: string;\n};\n\ntype ContractDescriptorState = {\n fnSelected: number;\n fnDescriptorEntries: Function[];\n filteredFnDescriptorEntries: Function[];\n /**\n * Use an object to index descriptions to facilitate state updates at\n * the expense of having some function data duplication (sigHash, minimalName, etc)\n */\n userFnDescriptions: Record;\n readyToFocus: boolean;\n lastCaretPos: number;\n filters: Record;\n};\n\nconst initialState: ContractDescriptorState = {\n fnSelected: 0,\n fnDescriptorEntries: [],\n filteredFnDescriptorEntries: [],\n userFnDescriptions: {},\n readyToFocus: false,\n lastCaretPos: 0,\n filters: {\n added: false,\n available: true,\n challenged: false,\n pending: false,\n },\n};\n\nconst removeUserFnDescription = (\n sigHash: string,\n set: SetRecord & {\n state: SetImmerState;\n },\n get: StoreApiGet\n) => {\n const newUserFnDescriptions = { ...get.userFnDescriptions() };\n delete newUserFnDescriptions[sigHash];\n set.userFnDescriptions(newUserFnDescriptions);\n};\n\nconst contractDescriptorStore = createStore(\"contract-descriptor\")(\n initialState,\n {\n devtools: { enabled: process.env.NODE_ENV === \"development\" },\n }\n)\n .extendActions((set, get) => ({\n setUpFnDescriptorEntries: (abi: string, entries: FnEntry[]) => {\n const abiInterface = new utils.Interface(abi);\n const fnFragments = abiInterface.fragments.filter(\n (f) => f.type === \"function\"\n ) as FunctionFragment[];\n\n const fns = fnFragments\n .filter((f) => !f.constant) // Only consider functions that does not change state\n .map((f) => {\n const sigHash = getFnSelector(f);\n return {\n fullName: f.format(\"full\"),\n sigHash,\n entry: entries?.find((e) => e.sigHash === sigHash),\n };\n });\n\n set.fnDescriptorEntries(fns);\n set.filteredFnDescriptorEntries(fns.filter((fn) => !fn.entry));\n },\n goToNextFn: () => {\n const prevFnSelected = get.fnSelected();\n const fns = get.filteredFnDescriptorEntries();\n\n set.fnSelected(Math.min(fns.length - 1, prevFnSelected + 1));\n },\n goToPrevFn: () => {\n const prevFnSelected = get.fnSelected();\n set.fnSelected(Math.max(0, prevFnSelected - 1));\n },\n toggleFilter: (filterName: keyof ContractDescriptorState[\"filters\"]) => {\n const newFilters = { ...get.filters() };\n\n newFilters[filterName] = !newFilters[filterName];\n\n const newFilteredDescriptorEntries = get\n .fnDescriptorEntries()\n .filter((fnDescriptor) => {\n const entry = fnDescriptor.entry;\n const isAvailable = !entry;\n\n if (isAvailable) {\n return newFilters[FnDescriptionStatus.Available];\n }\n\n return newFilters[entry!.status];\n });\n\n set.fnSelected(0);\n set.filteredFnDescriptorEntries(newFilteredDescriptorEntries);\n set.filters(newFilters);\n },\n upsertFnDescription: (sigHash: string, description: string) => {\n const prevUserFnDescriptions = get.userFnDescriptions();\n\n /**\n * Don't insert a description that equals an already existing description entry.\n */\n if (\n get\n .fnDescriptorEntries()\n .find(\n (fn) => fn.sigHash === sigHash && fn.entry?.notice === description\n )\n ) {\n /**\n * The user may edit descriptions already inserted by them and then revert the changes back to the original\n * value. When this happens, we need to remove the description.\n */\n if (prevUserFnDescriptions[sigHash]) {\n removeUserFnDescription(sigHash, set, get);\n }\n return;\n }\n\n if (prevUserFnDescriptions[sigHash]?.description === description) {\n return;\n }\n\n // Delete empty descriptions\n if (!description && prevUserFnDescriptions[sigHash]) {\n removeUserFnDescription(sigHash, set, get);\n return;\n }\n\n // Upsert the new description\n set.userFnDescriptions({\n ...prevUserFnDescriptions,\n [sigHash]: {\n ...prevUserFnDescriptions[sigHash],\n description,\n },\n });\n },\n }))\n .extendActions((set, get) => ({\n addHelperFunction: (fnSignature: string) => {\n const selectedEntry = get.filteredFnDescriptorEntries()[get.fnSelected()];\n const selectedUserFnDescription =\n get.userFnDescriptions()[selectedEntry.sigHash];\n const fieldCaretPos = get.lastCaretPos();\n const oldDescription =\n selectedUserFnDescription?.description ??\n selectedEntry.entry?.notice ??\n \"\";\n const newDescription = `${oldDescription.slice(\n 0,\n fieldCaretPos\n )}\\`${fnSignature}\\`${oldDescription.slice(fieldCaretPos)}`;\n\n set.upsertFnDescription(selectedEntry.sigHash, newDescription);\n\n // Wait a little bit for the description to update.\n setTimeout(() => set.readyToFocus(true), 100);\n setTimeout(() => set.readyToFocus(false), 100);\n },\n }))\n .extendSelectors((_, get) => ({\n fnDescriptionsCounter: () => Object.keys(get.userFnDescriptions()).length,\n currentFnDescriptorEntry: (): Function | undefined =>\n get.filteredFnDescriptorEntries()[get.fnSelected()],\n }));\n\nexport const useContractDescriptorStore = contractDescriptorStore.useStore;\nexport const selectors = contractDescriptorStore.use;\nexport const actions = contractDescriptorStore.set;\n", "import { utils } from \"ethers\";\nimport type { Fragment } from \"ethers/lib/utils\";\n\nexport const getFnSelector = (fragment: Fragment): string =>\n utils.id(fragment.format(\"sighash\")).substring(0, 10);\n", "import { useCallback } from \"react\";\nimport { utils } from \"ethers\";\nimport { useContract, useSigner } from \"wagmi\";\nimport rosetteStoneAbi from \"~/abi/RosetteStone.json\";\nimport { useRosetteStone } from \"~/providers/RosetteStone\";\n\nconst GAS_LIMIT = 6000000;\n\nexport default function useRosetteActions() {\n const { currentGuideline } = useRosetteStone();\n const [{ data }] = useSigner();\n const rosetteContract = useContract({\n addressOrName: window.ENV.ROSETTE_STONE_ADDRESS,\n contractInterface: rosetteStoneAbi,\n signerOrProvider: data,\n });\n\n const upsertEntries = useCallback(\n async (scopes: string[], sigs: string[], cids: string[]) => {\n if (!currentGuideline?.collateralAmount) {\n throw new Error(\"Unknown collateral amount needed to upsert an entry\");\n }\n\n const tx = await rosetteContract.upsertEntries(\n scopes,\n sigs,\n cids.map((cid) => utils.hexlify(utils.toUtf8Bytes(cid))),\n { gasLimit: GAS_LIMIT, value: currentGuideline.collateralAmount }\n );\n await tx.wait();\n },\n [rosetteContract, currentGuideline?.collateralAmount]\n );\n\n return { upsertEntries };\n}\n", "import {\n addressesEqual,\n Button,\n GU,\n IconMenu,\n Popover,\n SearchInput,\n textStyle,\n} from \"@blossom-labs/rosette-ui\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { WheelEventHandler } from \"react\";\nimport styled from \"styled-components\";\nimport { FunctionDetails } from \"./FunctionDetails\";\nimport { HELPER_FUNCTIONS } from \"~/radspec-helper-functions\";\nimport type { HelperFunction } from \"~/radspec-helper-functions\";\n\nimport { actions, selectors } from \"../use-contract-descriptor-store\";\nimport { SELECTION_SEPARATOR } from \"~/utils/client/selection.client\";\nimport { useAccount } from \"wagmi\";\n\ntype Placement =\n | \"top\"\n | \"bottom\"\n | \"left\"\n | \"right\"\n | \"left-end\"\n | \"left-start\";\n\nconst computeFnSignature = (fn: HelperFunction): string => {\n const params = fn.params\n // Filter out optional parameters\n ?.filter((p) => p.defaultValue === undefined)\n .map((p) => `${SELECTION_SEPARATOR}${p.name}`)\n .join(\",\");\n\n return `${fn.name}(${params})`;\n};\n\nexport const HelperFunctionsPicker = ({\n popoverPlacement,\n}: {\n popoverPlacement?: Placement;\n}) => {\n const [{ data: accountData }] = useAccount();\n const opener = useRef();\n const [visible, setVisible] = useState(false);\n const [searchTerm, setSearchTerm] = useState(\"\");\n const filteredHelperFunctions = useMemo(\n () =>\n HELPER_FUNCTIONS.filter((fn) =>\n fn.name.toLowerCase().includes(searchTerm.toLowerCase())\n ),\n [searchTerm]\n );\n const currentEntrySubmitter =\n selectors.currentFnDescriptorEntry()?.entry?.submitter;\n const pickerDisabled =\n currentEntrySubmitter &&\n !addressesEqual(currentEntrySubmitter, accountData?.address ?? \"\");\n\n const handleUseFunction = useCallback((fn: HelperFunction) => {\n actions.addHelperFunction(computeFnSignature(fn));\n setVisible(false);\n }, []);\n\n const handlePickerWheelEvent = useCallback((e) => {\n e.stopPropagation();\n }, []);\n\n useEffect(() => {\n if (!visible) {\n // Wait a little bit for the popover to close.\n setTimeout(() => setSearchTerm(\"\"), 100);\n }\n }, [visible]);\n\n return (\n
    \n }\n onClick={() => setVisible(true)}\n />\n setVisible(false)}\n >\n \n \n
    Functions library
    \n \n \n {filteredHelperFunctions.map((fn) => (\n \n ))}\n \n
    \n
    \n \n
    \n );\n};\n\nconst PickerButton = styled(Button)`\n border: 1px solid;\n`;\n\nconst PopoverWrapper = styled.div`\n max-height: 700px;\n overflow: auto;\n &::-webkit-scrollbar {\n width: 12px;\n background-color: ${(props) => props.theme.surfaceSelected};\n }\n\n &::-webkit-scrollbar-thumb {\n border-radius: 10px;\n box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.1);\n -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.1);\n background-color: ${(props) => props.theme.surfaceIcon};\n }\n`;\n\nconst PopoverLayout = styled.div`\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n width: 400px;\n gap: ${2 * GU}px;\n box-sizing: border-box;\n padding: ${2 * GU}px;\n`;\n\nconst Header = styled.div`\n color: ${({ theme }) => theme.content};\n ${textStyle(\"body2\")};\n`;\n\nconst FunctionsSection = styled.div`\n width: 100%;\n display: flex;\n flex-direction: column;\n gap: ${1 * GU}px;\n`;\n", "import {\n ButtonBase,\n GU,\n IconRight,\n noop,\n textStyle,\n useTheme,\n} from \"@blossom-labs/rosette-ui\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { a, useSpring } from \"@react-spring/web\";\nimport styled from \"styled-components\";\nimport type { HelperFunction } from \"~/radspec-helper-functions\";\nimport { Entry } from \"./Entry\";\n\ntype FunctionDetailsProps = {\n fn: HelperFunction;\n onUse?(fn: HelperFunction): void;\n};\n\nexport const FunctionDetails = ({ fn, onUse = noop }: FunctionDetailsProps) => {\n const theme = useTheme();\n const contentRef = useRef(null);\n const contentHeight = useRef(0);\n const animate = useRef(false);\n const [opened, setOpened] = useState(false);\n\n const { openProgress } = useSpring({\n from: { openProgress: 0 },\n to: { openProgress: Number(opened) },\n immediate: !animate,\n });\n\n const handleToggle = () => {\n setOpened((prevOpened) => !prevOpened);\n };\n\n const updateHeight = () => {\n if (contentRef.current) {\n contentHeight.current = contentRef.current.clientHeight;\n }\n };\n\n const handleContentRef = (element: HTMLDivElement) => {\n contentRef.current = element;\n updateHeight();\n };\n\n // Update the height\n useEffect(updateHeight, [opened, updateHeight]);\n\n // Animate after the initial render\n useEffect(() => {\n animate.current = true;\n }, []);\n\n return (\n \n theme.helpContent.alpha(v)),\n }}\n >\n onUse(fn)}>Use\n \n \n
    \n `rotate(${v * 90}deg)`),\n transformOrigin: \"50% calc(50% - 0.5px)\",\n marginRight: 0.5 * GU,\n }}\n >\n \n \n
    \n \n \n
    {fn.name}
    \n
    \n
    \n \n \n theme.helpContent.alpha(v)\n ),\n height: openProgress.to((v) => `${v * contentHeight.current}px`),\n }}\n >\n
    \n \n
    \n \n \n \n
    \n );\n};\n\nconst Container = styled.section`\n cursor: pointer;\n position: relative;\n margin: 0;\n`;\n\nconst EntryButton = styled.div`\n position: relative;\n width: 100%;\n`;\n\nconst FnNameWrapper = styled.h1`\n display: flex;\n justify-content: flex-start;\n align-items: center;\n height: ${5 * GU}px;\n margin-left: ${0.5 * GU}px;\n color: ${({ theme }) => theme.surfaceContent};\n font-weight: bold;\n ${textStyle(\"body2\")};\n`;\n\nconst AnimatedContainer = styled(a.div)`\n display: flex;\n flex-direction: column;\n justify-content: flex-end;\n`;\n\nconst ActionButtonWrapper = styled(ButtonBase)`\n position: absolute;\n top: 10px;\n right: 10px;\n color: ${({ theme }) => theme.link};\n z-index: 2;\n ${textStyle(\"body2\")};\n`;\n", "import { GU } from \"@blossom-labs/rosette-ui\";\nimport styled from \"styled-components\";\nimport type { Param } from \"~/radspec-helper-functions\";\n\ntype DescriptionProps = {\n description: string;\n params?: Param[];\n};\n\nexport const Entry = ({ description, params = [] }: DescriptionProps) => (\n \n
    {description}
    \n {params.length && (\n \n
    Parameters
    \n
      \n {params.map(({ name, defaultValue, description, type }) => (\n \n {name}:\n \n {type.toString()} \n {defaultValue && (optional)}\n \n\n
      {description}
      \n {defaultValue && Default: {defaultValue}}\n
      \n ))}\n
    \n
    \n )}\n
    \n);\n\nconst Container = styled.div`\n display: flex;\n flex-direction: column;\n gap: ${1 * GU}px;\n\n & > div {\n color: ${({ theme }) => theme.surfaceContentSecondary};\n }\n`;\n\nconst ParametersContainer = styled.div`\n & > div:first-child {\n color: ${({ theme }) => theme.surfaceContent};\n }\n\n & > ul {\n margin-left: ${1 * GU}px;\n }\n\n & > ul > li {\n margin-left: ${1 * GU}px;\n }\n`;\n\nconst Parameter = styled.li`\n & > span:nth-child(1) {\n color: ${({ theme }) => theme.surfaceContent};\n }\n & > span:nth-child(2) {\n font-style: italic;\n }\n`;\n", "export enum ParamType {\n Address = \"address\",\n Bool = \"bool\",\n Bytes = \"bytes\",\n Uint = \"uint\",\n String = \"string\",\n}\n\nexport type Param = {\n name: string;\n type: ParamType;\n description: string;\n defaultValue?: string | number;\n};\nexport type HelperFunction = {\n name: string;\n description: string;\n params?: Param[];\n};\n\nexport const HELPER_FUNCTIONS: HelperFunction[] = [\n {\n name: \"@formatDate\",\n description: \"Format a timestamp as a string.\",\n params: [\n {\n name: \"timestamp\",\n type: ParamType.Uint,\n description: \" Unix timestamp in seconds.\",\n },\n {\n name: \"format\",\n type: ParamType.String,\n description:\n 'Format for the date, defaults to a format like \"Jan. 1st 2000\".',\n defaultValue: \"MMM. do y\",\n },\n ],\n },\n {\n name: \"@formatPct\",\n description: \"Format a percentage amount.\",\n params: [\n {\n name: \"value\",\n type: ParamType.Uint,\n description: \"The number to be formatted as a percentage.\",\n },\n {\n name: \"format\",\n type: ParamType.Uint,\n description:\n \"The number that is considered to be 100% when calculating the percentage.\",\n defaultValue: \"10^18\",\n },\n {\n name: \"precision\",\n type: ParamType.Uint,\n description: \"The number of decimal places to format to.\",\n defaultValue: 2,\n },\n ],\n },\n {\n name: \"@fromHex\",\n description: \"Returns the string representation of a given hex value.\",\n params: [\n {\n name: \"hex\",\n type: ParamType.String,\n description: \"The hex string.\",\n },\n {\n name: \"to\",\n type: ParamType.String,\n description:\n \"The type to convert the hex from (supported types: 'utf8', 'number').\",\n },\n ],\n },\n {\n name: \"@radspec\",\n description: \"Interpret calldata using radspec recursively.\",\n params: [\n {\n name: \"addr\",\n type: ParamType.Address,\n description: \"The target address of the call.\",\n },\n {\n name: \"data\",\n type: ParamType.Bytes,\n description: \"The calldata of the call.\",\n },\n ],\n },\n {\n name: \"@tokenAmount\",\n description: \"Format token amounts taking decimals into account.\",\n params: [\n {\n name: \"tokenAddress\",\n type: ParamType.Address,\n description: \"The address of the token.\",\n },\n {\n name: \"amount\",\n type: ParamType.Uint,\n description: \"The absolute amount for the token quantity (wei).\",\n },\n {\n name: \"showSymbol\",\n type: ParamType.Bool,\n description:\n \"Whether the token symbol will be printed after the amount.\",\n defaultValue: \"true\",\n },\n {\n name: \"precision\",\n type: ParamType.Uint,\n description:\n \"The number of decimal places to format to. If set, the precision is always enforced.\",\n defaultValue: 18,\n },\n ],\n },\n {\n name: \"@transformTime\",\n description: \"Transform between time units.\",\n params: [\n {\n name: \"time\",\n type: ParamType.Uint,\n description: \"The base time amount.\",\n },\n {\n name: \"toUnit\",\n type: ParamType.String,\n description:\n \"The unit to convert the time to (supported units: 'second', 'minute', 'hour', 'day', 'week', 'month', 'year').\",\n defaultValue: \"'best'\",\n },\n {\n name: \"fromUnit\",\n type: ParamType.String,\n description:\n \"The unit to convert the time from (supported units: 'millisecond', 'second', 'minute', 'hour', 'day', 'week', 'month', 'year').\",\n defaultValue: \"'second'\",\n },\n ],\n },\n {\n name: \"@withDecimals\",\n description: \"Format an numerical amount with its decimals.\",\n params: [\n {\n name: \"amount\",\n type: ParamType.Uint,\n description: \"The absolute amount, without any decimals.\",\n },\n {\n name: \"decimals\",\n type: ParamType.Uint,\n description: \"The number of decimal places to format to.\",\n defaultValue: 18,\n },\n ],\n },\n];\n", "import { createRef, useEffect, useRef, useState } from \"react\";\nimport type { RefObject } from \"react\";\nimport { Carousel } from \"./Carousel\";\nimport { FunctionDescriptor } from \"./FunctionDescriptor\";\nimport {\n actions,\n useContractDescriptorStore,\n} from \"./use-contract-descriptor-store\";\nimport { getSelectionRange } from \"~/utils/client/selection.client\";\n\ntype FnDescriptorsCarouselProps = {\n compactMode: boolean;\n};\n\nexport const FnDescriptorsCarousel = ({\n compactMode,\n}: FnDescriptorsCarouselProps) => {\n const {\n filteredFnDescriptorEntries,\n fnSelected,\n lastCaretPos,\n userFnDescriptions,\n readyToFocus,\n } = useContractDescriptorStore();\n const descriptorRefs = useRef[]>([]);\n const prevDescriptorIndexRef = useRef(-1);\n const currentDescriptorIndexRef = useRef(0);\n const [carouselMoveAnimationEnded, setCarouselMoveEnded] = useState(false);\n\n /**\n * Generate descriptor refs\n */\n useEffect(() => {\n descriptorRefs.current = filteredFnDescriptorEntries.map(\n (_, i) => descriptorRefs.current[i] ?? createRef()\n );\n\n if (!descriptorRefs.current.length) {\n return;\n }\n // Wait a little bit for the ref to be attached before focusing first element\n setTimeout(() => descriptorRefs.current[0].current?.focus(), 200);\n }, [filteredFnDescriptorEntries]);\n\n /**\n * Focus on current element once the transition animation ended.\n */\n useEffect(() => {\n if (\n carouselMoveAnimationEnded &&\n descriptorRefs.current[fnSelected]?.current\n ) {\n descriptorRefs.current[fnSelected]?.current?.focus();\n setCarouselMoveEnded(false);\n }\n }, [carouselMoveAnimationEnded, fnSelected]);\n\n /**\n * Lose focus on last element when a new one was selected to not mess up\n * the transition animation if user types in.\n */\n useEffect(() => {\n if (!descriptorRefs.current.length) {\n return;\n }\n\n if (prevDescriptorIndexRef.current === -1) {\n prevDescriptorIndexRef.current = fnSelected;\n } else {\n prevDescriptorIndexRef.current = currentDescriptorIndexRef.current;\n\n descriptorRefs.current[prevDescriptorIndexRef.current]?.current?.blur();\n }\n\n currentDescriptorIndexRef.current = fnSelected;\n }, [fnSelected]);\n\n /**\n * Focus on current element when function was selected and set\n * selection on first parameter.\n */\n useEffect(() => {\n if (\n !readyToFocus ||\n !descriptorRefs.current.length ||\n !descriptorRefs.current[fnSelected]?.current\n ) {\n return;\n }\n\n const selectedDescriptor = descriptorRefs.current[fnSelected].current!;\n\n selectedDescriptor.focus();\n\n const [start, end] = getSelectionRange(\n selectedDescriptor.value,\n lastCaretPos\n );\n\n selectedDescriptor.setSelectionRange(start, end);\n }, [lastCaretPos, readyToFocus, fnSelected]);\n\n return (\n (\n \n ))}\n direction={compactMode ? \"horizontal\" : \"vertical\"}\n itemSpacing={450}\n onTransitionEnd={() => setCarouselMoveEnded(true)}\n />\n );\n};\n", "import { GU, noop, useViewport } from \"@blossom-labs/rosette-ui\";\nimport type { ReactNode } from \"react\";\nimport { useCallback, useEffect, useState, useRef } from \"react\";\nimport { a, useSpring } from \"@react-spring/web\";\nimport styled from \"styled-components\";\nimport { PrevNext } from \"./PrevNext\";\n\ntype CarouselProps = {\n items: ReactNode[];\n selected: number;\n compactMode?: boolean;\n direction?: \"horizontal\" | \"vertical\";\n itemSpacing?: number;\n customSideSpace?: number;\n showPrevNext?: boolean;\n onItemSelected?(selected: number): void;\n onTransitionEnd?(): void;\n};\n\nconst DEFAULT_SIZE = { width: 0, height: 0 };\n\nexport const Carousel = ({\n items,\n selected = 0,\n compactMode = false,\n direction = \"vertical\",\n itemSpacing = 3 * GU,\n customSideSpace,\n showPrevNext = false,\n onItemSelected = noop,\n onTransitionEnd = noop,\n}: CarouselProps) => {\n const [containerSize, setContainerSize] = useState({ ...DEFAULT_SIZE });\n const container = useRef(null);\n const { width: vw } = useViewport();\n const isHorizontal = direction === \"horizontal\";\n const itemSize = containerSize[isHorizontal ? \"width\" : \"height\"];\n // The space on one side of the visible items\n const sideSpace =\n customSideSpace && customSideSpace >= 0 ? customSideSpace : 0;\n\n // const allowDrag = compactMode || (items && items.length > visibleItems);\n\n useEffect(() => {\n onItemSelected(selected);\n }, [onItemSelected, selected]);\n\n const updateContainerSize = useCallback((element) => {\n setContainerSize(\n element\n ? { width: element.clientWidth, height: element.clientHeight }\n : { ...DEFAULT_SIZE }\n );\n }, []);\n\n useEffect(() => {\n if (container.current) {\n updateContainerSize(container.current);\n }\n }, [vw, updateContainerSize]);\n\n const handleContainerRef = useCallback(\n (element) => {\n container.current = element;\n updateContainerSize(element);\n },\n [updateContainerSize]\n );\n\n // Get the container x position from an item index\n const xFromItem = useCallback(\n (index) => {\n return sideSpace - (itemSize + itemSpacing) * index;\n },\n [sideSpace, itemSize, itemSpacing]\n );\n\n // The current x position, before the drag\n const selectedX = xFromItem(selected);\n\n // The x position of the last item, before the drag\n // const lastX = xFromItem(items.length - 1);\n\n // Handles the actual x position, with the drag\n const [{ x, drag }, setX] = useSpring(() => ({\n x: selectedX,\n drag: Number(false),\n immediate: true,\n onRest: onTransitionEnd,\n }));\n\n // TODO: Implement dragging feature for mobile screens\n // Update the transition during drag\n // const bindDrag = useDrag(({ down, delta }) => {\n // const updatedX = Math.max(lastX, Math.min(sideSpace, selectedX + delta[0]));\n\n // if (!allowDrag) {\n // return;\n // }\n\n // if (down) {\n // setX({\n // x: updatedX,\n // immediate: true,\n // });\n // } else {\n // let target = selected;\n // if (Math.abs(delta[0]) > itemWidth / 2) {\n // const sP = delta[0] > 0 ? -sideSpace : sideSpace;\n // const deltaSelected = Math.abs(\n // Math.round((delta[0] + sP) / (itemWidth + itemSpacing))\n // );\n // // Moving to the left\n // if (delta[0] > 0) {\n // target = Math.max(0, selected - deltaSelected);\n // // Move to the right\n // } else {\n // target = Math.min(\n // items.length - visibleItems,\n // selected + deltaSelected\n // );\n // }\n // }\n\n // setX({\n // x: xFromItem(target),\n // immediate: false,\n // });\n // setSelected(target);\n // }\n // });\n\n // Update the transition when the base x position updates\n useEffect(() => {\n setX({\n x: selectedX,\n immediate: false,\n });\n }, [selectedX, setX]);\n\n return (\n \n {showPrevNext && (\n <>\n {selected > 0 && (\n onItemSelected(Math.max(0, selected - 1))}\n isHorizontal={isHorizontal}\n />\n )}\n {selected < items.length - 1 && (\n \n onItemSelected(Math.min(items.length - 1, selected + 1))\n }\n isHorizontal={isHorizontal}\n />\n )}\n \n )}\n\n \n `translate3d(${\n isHorizontal ? `${x || 0}px, 0, 0` : `0, ${x || 0}px, 0`\n })`\n ),\n }}\n >\n {items.map((item, i) => (\n {\n return drag || (i >= selected && i < selected + 1) ? 1 : 0.15;\n }),\n }}\n $isFirst={i === 0}\n $isHorizontal={isHorizontal}\n $itemHeight={containerSize.height}\n $itemWidth={containerSize.width}\n $itemSpacing={itemSpacing}\n >\n {item}\n \n ))}\n \n \n );\n};\n\nconst Container = styled.div<{\n isHorizontal: boolean;\n}>`\n position: relative;\n overflow: hidden;\n touch-action: none;\n height: 100%;\n width: 100%;\n`;\n\nconst AnimatedContainer = styled(a.div)<{ $isHorizontal: boolean }>`\n position: absolute;\n display: flex;\n width: 100%;\n box-sizing: border-box;\n\n align-items: center;\n touch-action: none;\n ${(props) =>\n props.$isHorizontal\n ? `\n flex-direction: row;\n justify-content: flex-start;\n height: 100%;\n `\n : `\n flex-direction: column;\n justify-content: center;\n `};\n`;\n\nconst AnimatedItemContainer = styled(a.div)<{\n $isHorizontal: boolean;\n $isFirst: boolean;\n $itemWidth: number;\n $itemHeight: number;\n $itemSpacing: number;\n}>`\n width: ${(props) => props.$itemWidth}px;\n height: ${(props) => props.$itemHeight}px;\n transition: opacity 150ms ease-in-out;\n flex-grow: 0;\n flex-shrink: 0;\n ${(props) => (props.$isHorizontal ? \"margin-left\" : \"margin-top\")}: ${({\n $isFirst,\n $itemSpacing: itemSpacing,\n}) => (!$isFirst ? `${itemSpacing}px` : 0)}};\n`;\n", "import {\n GU,\n ButtonIcon,\n IconDown,\n IconUp,\n IconRight,\n IconLeft,\n} from \"@blossom-labs/rosette-ui\";\nimport styled from \"styled-components\";\n\ntype PrevNextProps = {\n isHorizontal?: boolean;\n type: \"next\" | \"previous\";\n onClick: () => void;\n};\nexport const PrevNext = ({\n isHorizontal = true,\n type,\n onClick,\n}: PrevNextProps) => {\n const NextIcon = isHorizontal ? IconRight : IconDown;\n const PrevIcon = isHorizontal ? IconLeft : IconUp;\n const next = type === \"next\";\n const Icon = next ? NextIcon : PrevIcon;\n\n return (\n \n \n \n \n \n );\n};\n\nconst Container = styled.div<{ isHorizontal: boolean; next: boolean }>`\n position: absolute;\n z-index: 1;\n ${({ isHorizontal, next }) =>\n isHorizontal\n ? `\n top: calc(50% - ${5 * GU}px);\n ${next ? \"right\" : \"left\"}: ${0.5 * GU}px;\n `\n : `\n top: calc(${next ? `100% - ${5 * GU}px` : \"0\"});\n left: 50%;\n `};\n color: ${({ theme }) => theme.surfaceContentSecondary};\n height: ${6 * GU}px;\n`;\n", "import {\n Button,\n Modal,\n GU,\n Field,\n TextInput,\n Header,\n textStyle,\n addressesEqual,\n} from \"@blossom-labs/rosette-ui\";\nimport { forwardRef, memo, useCallback, useState } from \"react\";\nimport type {\n FocusEventHandler,\n KeyboardEventHandler,\n ChangeEvent,\n} from \"react\";\nimport styled from \"styled-components\";\nimport { StatusLabel } from \"~/components/StatusLabel\";\nimport { FnDescriptionStatus } from \"~/types\";\nimport { actions } from \"../use-contract-descriptor-store\";\nimport type { Function } from \"../use-contract-descriptor-store\";\nimport { DescriptionField } from \"./DescriptionField\";\nimport { canTab, getSelectionRange } from \"~/utils/client/selection.client\";\nimport { useAccount } from \"wagmi\";\n\ntype FunctionDescriptorProps = {\n fnDescriptorEntry: Function;\n description?: string;\n onEntryChange(sigHash: string, description: string): void;\n};\n\nexport const FunctionDescriptor = memo(\n forwardRef(\n ({ fnDescriptorEntry, description, onEntryChange, ...props }, ref) => {\n const [{ data: accountData }] = useAccount();\n const [showModal, setShowModal] = useState(false);\n const [callData, setCallData] = useState(\"\");\n const entry = fnDescriptorEntry.entry;\n const { notice, status, submitter } = entry || {};\n const descriptorStatus = status || FnDescriptionStatus.Available;\n const disableDescriptionField =\n !!submitter && !addressesEqual(submitter, accountData?.address ?? \"\");\n\n const handleKeyDown: KeyboardEventHandler = (e) => {\n const textArea = e.target as HTMLTextAreaElement;\n const text = textArea.value;\n const offset = textArea.selectionStart;\n\n if (e.code === \"Tab\" && text && canTab(text, offset)) {\n e.preventDefault();\n const [start, end] = getSelectionRange(text, offset);\n\n textArea.setSelectionRange(start, end);\n }\n };\n\n const handleBlur: FocusEventHandler = (e) => {\n actions.lastCaretPos(e.target.selectionStart);\n };\n\n /**\n * Need to wrap handler on callback to make helper function use logic\n * work.\n */\n const handleOnChange = useCallback(\n (value) => {\n onEntryChange(fnDescriptorEntry.sigHash, value);\n },\n [onEntryChange, fnDescriptorEntry.sigHash]\n );\n\n const handleTestModal = () => {\n setShowModal(true);\n };\n\n const handleDescribeCalldata = () => {};\n\n return (\n \n \n Description\n \n \n \n \n {fnDescriptorEntry.fullName.split(\" returns (\")[0]}\n \n \n setShowModal(false)}>\n
    \n \n ) => {\n setCallData(e.target.value);\n }}\n size=\"medium\"\n wide\n />\n \n \n \n \n );\n }\n )\n);\n\nFunctionDescriptor.displayName = \"FunctionDescriptor\";\n\nconst Container = styled.div`\n display: flex;\n flex-direction: column;\n height: 100%;\n`;\n\nconst DescriptorHeader = styled.div`\n display: flex;\n justify-content: space-between;\n margin-bottom: ${1 * GU}px;\n color: ${({ theme }) => theme.content};\n ${textStyle(\"body2\")};\n`;\n\nconst DescriptorEntry = styled.div`\n margin: ${2 * GU}px 0;\n word-break: break-word;\n color: ${({ theme }) => theme.content};\n ${textStyle(\"title4\")};\n`;\n", "import { textStyle, GU } from \"@blossom-labs/rosette-ui\";\nimport styled from \"styled-components\";\nimport { FnDescriptionStatus } from \"~/types\";\nimport { getFnEntryStatusIconData } from \"~/utils/client/icons.client\";\n\nexport const StatusLabel = ({ status }: { status: FnDescriptionStatus }) => {\n const iconData = getFnEntryStatusIconData(status);\n\n if (!iconData) {\n return null;\n }\n\n const { Icon, color } = iconData;\n\n return (\n \n
    \n \n
    \n {status}\n \n );\n};\n\nconst LabelContainer = styled.div<{\n status: FnDescriptionStatus;\n visible: boolean;\n}>`\n display: flex;\n visibility: ${({ visible }) => (visible ? \"visible\" : \"hidden\")};\n align-items: center;\n height: 24px;\n border: 1px solid ${({ color }) => color};\n width: fit-content;\n text-transform: lowercase;\n border-radius: 4px;\n padding-right: ${1 * GU}px;\n ${textStyle(\"body4\")};\n color: ${({ theme }) => theme.content};\n\n div:first-child {\n display: flex;\n height: 100%;\n align-items: center;\n color: ${({ theme }) => theme.content};\n background-color: ${({ color }) => color};\n margin-right: ${1 * GU}px;\n }\n`;\n", "import { GU, RADIUS, textStyle } from \"@blossom-labs/rosette-ui\";\nimport { forwardRef, useEffect, useState } from \"react\";\nimport type { FocusEventHandler, KeyboardEventHandler } from \"react\";\nimport styled from \"styled-components\";\nimport { useDebounce } from \"~/hooks/useDebounce\";\n\ntype DescriptionFieldProps = {\n description?: string;\n disabled?: boolean;\n height?: string;\n placeholder?: string;\n textSize?: string;\n onChange(value: string): void;\n onBlur?: FocusEventHandler;\n onKeyDown: KeyboardEventHandler;\n};\n\nexport const DescriptionField = forwardRef<\n HTMLTextAreaElement,\n DescriptionFieldProps\n>(\n (\n {\n description,\n disabled = false,\n height = `${10 * GU}px`,\n textSize = \"title4\",\n placeholder = \"Add description\u2026\",\n onChange,\n ...props\n },\n ref\n ) => {\n const [value, setValue] = useState(description);\n const debouncedValue = useDebounce(value, 400);\n\n useEffect(() => {\n if (debouncedValue !== undefined) {\n onChange(debouncedValue);\n }\n }, [debouncedValue, onChange]);\n\n /**\n * Keep inner description value in sync as it can be updated from other places\n * of the component tree (e.g. adding a function from the picker)\n */\n useEffect(() => {\n setValue(description);\n }, [description]);\n\n return (\n setValue(e.target.value)}\n {...props}\n />\n );\n }\n);\n\nDescriptionField.displayName = \"DescriptionField\";\n\nconst DescriptionTextArea = styled.textarea<{\n height: string;\n textSize: string;\n}>`\n height: ${(props) => props.height};\n padding: ${1 * GU}px ${1.5 * GU}px;\n background: ${(props) => props.theme.surface.alpha(0.5)};\n color: ${(props) => props.theme.contentSecondary};\n appearance: none;\n border-radius: ${RADIUS}px;\n width: 100%;\n outline: none;\n resize: none;\n ${(props) => textStyle(props.textSize)};\n\n &:focus {\n outline: none;\n border-color: ${(props) => props.theme.focus};\n }\n &:read-only {\n color: ${(props) => props.theme.hint};\n border-color: ${(props) => props.theme.borderDark};\n }\n &::placeholder {\n color: ${(props) => props.theme.border};\n opacity: 0.5;\n }\n &:invalid {\n box-shadow: none;\n }\n\n // TODO: Check with Paulo how the following styles render to check if we need to update the colors\n &::-webkit-scrollbar {\n width: 12px;\n background-color: ${(props) => props.theme.surfaceSelected};\n }\n\n &::-webkit-scrollbar-thumb {\n border-radius: 10px;\n box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.1);\n -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.1);\n background-color: ${(props) => props.theme.surfaceIcon};\n }\n`;\n", "import { useEffect, useState } from \"react\";\n\n/**\n * Source: https://usehooks.com/useDebounce/\n */\nexport function useDebounce(value: T, delay: number): T {\n // State and setters for debounced value\n const [debouncedValue, setDebouncedValue] = useState(value);\n useEffect(\n () => {\n // Update debounced value after delay\n const handler = setTimeout(() => {\n setDebouncedValue(value);\n }, delay);\n // Cancel the timeout if value changes (also on delay change or unmount)\n // This is how we prevent debounced value from updating if value is changed ...\n // .. within the delay period. Timeout gets cleared and restarted.\n return () => {\n clearTimeout(handler);\n };\n },\n [value, delay] // Only re-call effect if value or delay changes\n );\n return debouncedValue;\n}\n", "import {\n IconCheck,\n GU,\n Button,\n useTheme,\n IconCross,\n} from \"@blossom-labs/rosette-ui\";\nimport styled from \"styled-components\";\nimport { FnDescriptionStatus } from \"~/types\";\nimport { useContractDescriptorStore } from \"./use-contract-descriptor-store\";\nimport { actions } from \"./use-contract-descriptor-store\";\n\ntype FilterButtonProps = {\n label: string;\n active?: boolean;\n onClick(): void;\n};\n\nconst { Added, Available, Challenged, Pending } = FnDescriptionStatus;\n\nconst { toggleFilter } = actions;\n\nconst FilterButton = ({\n label,\n active = false,\n onClick,\n}: FilterButtonProps) => {\n const theme = useTheme();\n\n return (\n \n ) : (\n \n )\n }\n label={label}\n active={active}\n onClick={onClick}\n />\n );\n};\n\nconst ChipButton = styled(Button)<{ active: boolean }>`\n border-radius: 50px;\n\n &:focus:after {\n border-radius: 50px;\n }\n`;\n\nexport const FunctionDescriptorFilters = ({\n compactMode,\n}: {\n compactMode: boolean;\n}) => {\n const { filters } = useContractDescriptorStore();\n const { added, available, challenged, pending } = filters;\n\n return (\n \n \n
    Show functions:
    \n toggleFilter(Available)}\n />\n toggleFilter(Added)}\n />\n toggleFilter(Challenged)}\n />\n toggleFilter(Pending)}\n />\n
    \n
    \n );\n};\n\nconst Container = styled.div<{ compactMode: boolean }>`\n overflow: hidden;\n max-width: 100vw;\n overflow-x: auto;\n padding: ${2 * GU}px;\n\n &::-webkit-scrollbar {\n display: none;\n }\n\n /* -webkit-mask-size: 32px; */\n --mask-width: 30px;\n --mask-image-content: linear-gradient(\n to right,\n transparent,\n black var(--mask-width),\n black calc(100% - var(--mask-width)),\n transparent\n );\n\n padding-left: var(--mask-width);\n mask-image: var(--mask-image-content);\n`;\nconst FlexContainer = styled.div`\n display: inline-flex;\n align-items: center;\n gap: ${1 * GU}px;\n /* width: 100%; */\n`;\n", "import { GU, textStyle } from \"@blossom-labs/rosette-ui\";\nimport styled from \"styled-components\";\nimport { a, useTransition } from \"@react-spring/web\";\nimport type { ContractData, AggregateContract } from \"~/types\";\nimport { ContractItem } from \"./ContractItem\";\n\ntype ContractSelectorScreenProps = {\n contracts: AggregateContract[];\n loaderText?: string;\n onContractDataSelected(contractData: ContractData): void;\n};\n\nexport const ContractSelectorScreen = ({\n contracts,\n loaderText,\n onContractDataSelected,\n}: ContractSelectorScreenProps) => {\n const transition = useTransition(contracts, {\n trail: 100 / contracts.length,\n from: { opacity: 0, scale: 0 },\n enter: { opacity: 1, scale: 1 },\n });\n\n return (\n \n
    The following contracts have been found for the given address:
    \n {transition((styles, item) => (\n \n \n \n ))}\n
    \n );\n};\n\nconst Container = styled.div`\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: ${2 * GU}px;\n padding: ${1 * GU}px;\n margin-top: ${15 * GU}px;\n margin-bottom: ${3 * GU}px;\n\n & > div:first-child {\n ${textStyle(\"body1\")};\n color: ${({ theme }) => theme.content};\n margin: 0 ${2 * GU}px;\n margin-bottom: ${3 * GU}px;\n }\n`;\n", "import {\n GU,\n LoadingRing,\n Switch,\n Tag,\n textStyle,\n} from \"@blossom-labs/rosette-ui\";\nimport type { MouseEventHandler } from \"react\";\nimport { useEffect, useState } from \"react\";\nimport { a, useTransition } from \"@react-spring/web\";\nimport styled from \"styled-components\";\nimport type { ContractData, AggregateContract } from \"~/types\";\nimport { getNetworkLogo } from \"~/utils/client/icons.client\";\n\ntype ContractItemProps = {\n contract: AggregateContract;\n loaderText?: string;\n onClick(contractData: ContractData): void;\n};\n\nexport const ContractItem = ({\n contract: { proxy, implementation },\n loaderText = \"Fetching data\u2026\",\n onClick,\n}: ContractItemProps) => {\n const [itemClicked, setItemClicked] = useState(false);\n const [displayProxy, setDisplayProxy] = useState(!implementation);\n const contractData = displayProxy ? proxy : implementation;\n const { address, name, network } = contractData!;\n const implementationFound = !!implementation;\n const displaySwitch = !!proxy && !!implementation;\n\n const clickTransition = useTransition(itemClicked, {\n from: {\n loaderOpacity: 0,\n },\n enter: {\n loaderOpacity: 1,\n },\n });\n\n useEffect(() => {\n if (!itemClicked || !contractData) {\n return;\n }\n\n /**\n * Wait a little bit before handling click to display loader\n * for a period of time.\n */\n let timeoutId = setTimeout(() => onClick(contractData), 500);\n\n return () => {\n clearTimeout(timeoutId);\n };\n }, [contractData, itemClicked, onClick]);\n\n const handleViewClick: MouseEventHandler = (e) => {\n e.stopPropagation();\n\n setDisplayProxy((prev) => !prev);\n };\n\n return (\n {\n setItemClicked(true);\n }}\n isClicked={itemClicked}\n >\n {clickTransition(({ loaderOpacity }, clicked) => (\n <>\n \n \n \n }\n />\n {displaySwitch && (\n <>\n \n Show proxy:{\" \"}\n
    \n \n
    \n
    \n \n )}\n
    \n \n
    {name}
    \n
    {address}
    \n
    \n {!implementationFound && (\n *Implementation source code not found.\n )}\n
    \n {clicked && (\n \n {loaderText}\n \n )}\n \n ))}\n \n );\n};\n\nconst Wrapper = styled.div<{ isClicked: boolean }>`\n position: relative;\n padding: ${2 * GU}px;\n background-color: ${({ theme }) => theme.surface.alpha(0.5)};\n border-radius: 10px;\n border: 1px solid ${({ theme }) => theme.borderDark};\n transition: background-color ease-out 200ms;\n min-width: 45vmin;\n max-width: 85vmin;\n cursor: pointer;\n\n ${({ isClicked, theme }) =>\n !isClicked\n ? `&:hover {\n background-color: ${theme.surfaceUnder.alpha(0.1)};\n }`\n : \"\"};\n`;\n\nconst ItemHeader = styled.div`\n display: flex;\n justify-content: space-between;\n margin-bottom: ${1.5 * GU}px;\n`;\n\nconst ItemContent = styled.div`\n & > div:nth-child(1) {\n ${textStyle(\"body1\")};\n color: ${({ theme }) => theme.content};\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n & > div:nth-child(2) {\n ${textStyle(\"body2\")};\n color: ${({ theme }) => theme.contentSecondary};\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n`;\n\nconst InfoError = styled.span`\n color: ${({ theme }) => theme.negative};\n margin-top: ${1 * GU}px;\n ${textStyle(\"body4\")};\n`;\n\nconst ProxySwitchWrapper = styled.div`\n display: flex;\n gap: ${0.5 * GU}px;\n color: ${({ theme }) => theme.contentSecondary};\n ${textStyle(\"body3\")};\n\n & > div {\n display: flex;\n align-items: center;\n }\n`;\n\nconst Loader = styled(a.div)`\n width: 100%;\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n display: flex;\n justify-content: center;\n align-items: center;\n flex-wrap: nowrap;\n gap: ${1 * GU}px;\n ${textStyle(\"body2\")};\n color: ${({ theme }) => theme.contentSecondary};\n`;\n\nconst NetworkImg = styled.img<{ isTestnet: boolean }>`\n border-radius: 50%;\n width: 20px;\n height: 20px;\n margin-left: -${1.2 * GU}px;\n margin-right: ${0.5 * GU}px;\n ${(props) => (props.isTestnet ? \"filter: grayscale(80%);\" : \"\")};\n`;\n", "import type { ReactNode } from \"react\";\nimport { a, useSpring } from \"@react-spring/web\";\n\nexport const SmoothDisplayContainer = ({\n children,\n}: {\n children: ReactNode;\n}) => {\n const smoothDisplayStyles = useSpring({\n from: { opacity: 0 },\n to: { opacity: 1 },\n });\n\n return (\n \n {children}\n \n );\n};\n", "import type { ContractData, AggregateContract } from \"~/types\";\nimport { getProvider, NETWORKS } from \"./web3.server\";\nimport {\n buildExplorerFetchRequest,\n processExplorerResponse,\n} from \"./blockchain-explorers.server\";\nimport {\n fetchImplementationAddress,\n getProxyPattern,\n} from \"./proxy-patterns.server\";\nimport { constants } from \"ethers\";\nimport type { Chain } from \"wagmi\";\n\nconst processBytecodeRequest = (response: any): string => {\n const bytecode = response as string;\n\n if (!bytecode || bytecode === \"0x\") {\n throw new Response(\n !bytecode\n ? \"Contract bytecode not found\"\n : \"Address provided is not a contract\",\n { status: 500 }\n );\n }\n\n return bytecode;\n};\n\nexport const fetchContracts = async (\n contractAddress: string\n): Promise => {\n const responses = (await Promise.allSettled(\n NETWORKS.map((n) => fetchContractData(contractAddress, n))\n )) as PromiseSettledResult[];\n\n const fulfilledResponses = responses.filter(\n (r) => r.status === \"fulfilled\"\n ) as PromiseFulfilledResult[];\n const contracts = fulfilledResponses.map((r) => r.value);\n const proxiesAndImplementations = [];\n\n // Fetch proxy's implementation contract.\n for (const c of contracts) {\n let proxy, implementation;\n const proxyPattern = getProxyPattern(c.name);\n\n if (proxyPattern !== undefined) {\n proxy = c;\n const implementationAddress = await fetchImplementationAddress(\n proxy,\n proxyPattern\n );\n if (implementationAddress !== constants.AddressZero) {\n try {\n implementation = await fetchContractData(\n implementationAddress,\n c.network\n );\n } catch (err) {}\n }\n } else {\n implementation = c;\n }\n\n proxiesAndImplementations.push({ proxy, implementation });\n }\n\n return proxiesAndImplementations;\n};\n\nexport const fetchContractData = async (\n contractAddress: string,\n network: Chain\n): Promise => {\n const networkId = network.id;\n const provider = getProvider(networkId);\n const explorerRequest = fetch(\n buildExplorerFetchRequest(contractAddress, networkId)\n );\n\n const requests = [provider.getCode(contractAddress), explorerRequest];\n const responses = await Promise.all(requests);\n\n const bytecode = processBytecodeRequest(responses[0]);\n const explorerResult = await processExplorerResponse(\n responses[1] as Response\n );\n\n return {\n abi: explorerResult.ABI,\n bytecode,\n address: contractAddress,\n name: explorerResult.ContractName,\n network,\n };\n};\n", "import { providers } from \"ethers\";\nimport type { Chain } from \"wagmi\";\nimport { allChains } from \"wagmi\";\n\nconst INFURA_PROJECT_ID = process.env.INFURA_PROJECT_ID;\n\nconst STATIC_PROVIDERS_CACHE = new Map<\n number,\n providers.StaticJsonRpcProvider\n>();\n\nconst networkExplorerRegex = /([A-Z]+)_EXPLORER_API_KEY/;\n\nconst availableExplorers = Object.keys(process.env)\n .filter((key) => networkExplorerRegex.test(key))\n .map((key) => key.split(\"_\")[0].toLowerCase());\n\nexport const NETWORKS = allChains.filter(\n (chain) =>\n // Check if chain has the same explorer\n !!chain.blockExplorers?.find(({ name }) =>\n availableExplorers.includes(name.toLowerCase())\n )\n);\n\nconst buildRpcEndpoint = (network: Chain): string => {\n const infuraRpcEndpoints = network.rpcUrls.filter((rpcUrl) =>\n rpcUrl.includes(\"infura\")\n );\n\n if (infuraRpcEndpoints.length) {\n return `${infuraRpcEndpoints[0]}/${INFURA_PROJECT_ID}`;\n }\n\n return network.rpcUrls[0];\n};\n\nexport const getProvider = (networkId: number): providers.Provider => {\n if (STATIC_PROVIDERS_CACHE.has(networkId)) {\n return STATIC_PROVIDERS_CACHE.get(networkId)!;\n }\n\n const network = NETWORKS.find((n) => n.id === networkId)!;\n\n const provider = new providers.StaticJsonRpcProvider(\n buildRpcEndpoint(network),\n networkId\n );\n\n STATIC_PROVIDERS_CACHE.set(networkId, provider);\n\n return provider;\n};\n", "export type ExplorerAPIData = {\n baseUrl: string;\n apiKey: string;\n};\n\nexport type ExplorerResult = {\n ABI: string;\n Address: string;\n ContractName: string;\n};\n\ntype ExplorerResponse = {\n message: string;\n result: ExplorerResult[];\n status: string;\n};\n\nexport const getExplorerAPIData = (networkId: number): ExplorerAPIData => {\n let apiKey, baseUrl;\n\n switch (networkId) {\n case 1:\n apiKey = process.env.ETHERSCAN_EXPLORER_API_KEY;\n baseUrl = \"https://api.etherscan.io/api\";\n break;\n case 4:\n apiKey = process.env.ETHERSCAN_EXPLORER_API_KEY;\n baseUrl = \"https://api-rinkeby.etherscan.io/api\";\n break;\n case 100:\n apiKey = \"\";\n baseUrl = \"https://blockscout.com/xdai/mainnet/api\";\n break;\n case 137:\n apiKey = process.env.POLYGONSCAN_EXPLORER_API_KEY;\n baseUrl = \"https://api.polygonscan.com/api\";\n break;\n case 80001:\n apiKey = process.env.POLYGONSCAN_EXPLORER_API_KEY;\n baseUrl = \"https://api-testnet.polygonscan.com/api\";\n break;\n }\n\n if (!apiKey || !baseUrl) {\n throw new Response(\n `Blockchain explorer not found for given chain ${networkId}`,\n {\n status: 500,\n }\n );\n }\n\n return {\n baseUrl,\n apiKey,\n };\n};\n\nexport const processExplorerResponse = async (\n response: Response\n): Promise => {\n const explorerResponse = (await response.json()) as ExplorerResponse;\n\n if (explorerResponse.status === \"0\") {\n throw new Response(explorerResponse.message, { status: 500 });\n }\n\n const result = explorerResponse.result[0];\n\n if (\n !result.ABI ||\n // Check ABI field as Blockscout sets it when contract is not verified\n result.ABI === \"Contract source code not verified\"\n ) {\n throw new Response(`Contract is not verified`, { status: 500 });\n }\n\n return result;\n};\n\nexport const buildExplorerFetchRequest = (\n contractAddress: string,\n networkId: number\n): string => {\n const { baseUrl, apiKey } = getExplorerAPIData(networkId);\n\n return `${baseUrl}?module=contract&action=getsourcecode&address=${contractAddress}${\n apiKey ? `&apikey=${apiKey}` : \"\"\n }`;\n};\n", "import type { providers } from \"ethers\";\nimport { BigNumber, constants, Contract, utils } from \"ethers\";\nimport type { ContractData } from \"~/types\";\nimport { getProvider } from \"./web3.server\";\n\nenum ProxyPattern {\n UUPSProxy,\n UpgradeabilityStorage,\n EternalStorage,\n TransparentUpgradeableProxy,\n AppProxyUpgradeable,\n UnknownProxy,\n}\n\ntype ProxyPatternHandler = (\n proxy: ContractData,\n provider: providers.Provider\n) => Promise;\n\nconst {\n UUPSProxy,\n UpgradeabilityStorage,\n EternalStorage,\n TransparentUpgradeableProxy,\n AppProxyUpgradeable,\n UnknownProxy,\n} = ProxyPattern;\n\n/**\n * Standarized storage slot determined by bytes32(uint256(keccak256(\"eip1967.proxy.implementation\")) - 1)\n * and defined on EIP-1967 (https://eips.ethereum.org/EIPS/eip-1967#logic-contract-address)\n */\nconst EIP1967_STORAGE_SLOT = utils.hexlify(\n BigNumber.from(utils.id(\"eip1967.proxy.implementation\")).sub(1)\n);\n\nconst getAddressFromStorageSlot = async (\n contractAddress: string,\n storageSlot: string | number,\n provider: providers.Provider\n): Promise => {\n const rawImplementationAddress = await provider.getStorageAt(\n contractAddress,\n storageSlot\n );\n return utils.hexStripZeros(rawImplementationAddress);\n};\n\nconst PROXY_PATTERN_HANDLERS: Record = {\n [UUPSProxy]: async ({ address }, provider) => {\n let implementationAddress = await getAddressFromStorageSlot(\n address,\n EIP1967_STORAGE_SLOT,\n provider\n );\n\n /**\n * Try to fetch the address from the keccak256(\"PROXIABLE\") slot as defined on\n * the EIP-1822 UUPS proposal (https://eips.ethereum.org/EIPS/eip-1822#proxiable-contract).\n */\n if (implementationAddress === constants.AddressZero) {\n implementationAddress = await getAddressFromStorageSlot(\n address,\n utils.id(\"PROXIABLE\"),\n provider\n );\n return implementationAddress;\n }\n\n return implementationAddress;\n },\n [UpgradeabilityStorage]: async (proxy, provider) => {\n return \"\";\n },\n [EternalStorage]: async (proxy, provider) => {\n return \"\";\n },\n [TransparentUpgradeableProxy]: async ({ address }, provider) => {\n return getAddressFromStorageSlot(address, EIP1967_STORAGE_SLOT, provider);\n },\n [AppProxyUpgradeable]: ({ address, abi }, provider) => {\n const proxyContract = new Contract(address, abi, provider);\n\n return proxyContract.implementation();\n },\n [UnknownProxy]: async ({ address, abi }, provider) => {\n let implementationAddress: string;\n\n try {\n const proxyContract = new Contract(address, abi, provider);\n // Try fetching address from method\n implementationAddress = await proxyContract.implementation();\n } catch (err) {\n implementationAddress = await getAddressFromStorageSlot(\n address,\n EIP1967_STORAGE_SLOT,\n provider\n );\n }\n\n return implementationAddress;\n },\n};\n\nexport const getProxyPattern = (\n contractName: string\n): ProxyPattern | undefined => {\n const normalizedContractName = contractName.toLowerCase();\n const proxyPatternKeys = Object.keys(ProxyPattern).map((k) =>\n k.toLowerCase()\n );\n const proxyPatternIndex = proxyPatternKeys.indexOf(normalizedContractName);\n\n if (proxyPatternIndex === -1) {\n if (normalizedContractName.includes(\"proxy\")) {\n return UnknownProxy;\n }\n return;\n }\n\n return Object.values(ProxyPattern)[\n proxyPatternIndex\n ] as unknown as ProxyPattern;\n};\n\nexport const fetchImplementationAddress = (\n proxy: ContractData,\n proxyPattern: ProxyPattern\n): Promise =>\n PROXY_PATTERN_HANDLERS[proxyPattern](proxy, getProvider(proxy.network.id));\n", "import { Outlet, useOutletContext } from \"@remix-run/react\";\nimport type { AppContext } from \"~/App\";\n\nexport default function EntriesRoute() {\n const context = useOutletContext();\n\n return (\n <>\n
    \n \n
    \n \n );\n}\n", "import type { LoaderFunction } from \"@remix-run/node\";\nimport { json } from \"@remix-run/node\";\nimport { useLoaderData } from \"@remix-run/react\";\nimport styled from \"styled-components\";\nimport { AppScreen } from \"~/components/AppLayout/AppScreen\";\nimport type { FnEntry } from \"~/types\";\nimport { fetchFnEntry } from \"~/utils/server/subgraph.server\";\n\nexport const loader: LoaderFunction = async ({ params }) => {\n const entryId = params.entry;\n\n if (!entryId) {\n throw new Response(\"Expected entryId param\", {\n status: 400,\n statusText: \"Expected search params\",\n });\n }\n\n const entry = await fetchFnEntry(entryId);\n\n return json({ entry });\n};\n\ntype LoaderData = {\n entry?: FnEntry;\n};\n\nexport default function EntryRoute() {\n const { entry } = useLoaderData();\n\n return (\n \n {entry?.sigHash}\n \n );\n}\n\nconst Container = styled.div`\n display: flex;\n justify-content: center;\n align-items: start;\n height: 100%;\n width: 100%;\n`;\n", "import { GU, textStyle, useViewport } from \"@blossom-labs/rosette-ui\";\nimport type { LoaderFunction } from \"@remix-run/node\";\nimport { json } from \"@remix-run/node\";\nimport { useLoaderData, useNavigate } from \"@remix-run/react\";\nimport styled from \"styled-components\";\nimport { AppScreen } from \"~/components/AppLayout/AppScreen\";\nimport { SmoothDisplayContainer } from \"~/components/SmoothDisplayContainer\";\nimport { StatusLabel } from \"~/components/StatusLabel\";\nimport type { FnEntry } from \"~/types\";\nimport { fetchFnEntries } from \"~/utils/server/subgraph.server\";\n\nexport const loader: LoaderFunction = async () => {\n const fns = await fetchFnEntries();\n\n return json({ fns });\n};\n\ntype LoaderData = {\n fns: FnEntry[];\n};\n\nexport default function Entries() {\n const { fns } = useLoaderData();\n const { below, within } = useViewport();\n\n const compactMode = below(\"medium\");\n const tabletMode = within(\"medium\", \"large\");\n\n return (\n \n \n \n {fns.length ? (\n \n {fns.map((f) => (\n \n ))}\n \n ) : (\n
    No entries found
    \n )}\n
    \n
    \n
    \n );\n}\n\nfunction EntryCard({ fn }: { fn: FnEntry }) {\n const navigate = useNavigate();\n\n return (\n navigate(`/entries/${fn.id}`)}>\n {fn.notice}\n \n {fn.sigHash}\n \n \n \n );\n}\n\nconst Container = styled.div<{ compactMode: boolean; tabletMode: boolean }>`\n display: flex;\n justify-content: center;\n align-items: start;\n padding-top: ${({ compactMode, tabletMode }) =>\n compactMode ? 3 * GU : tabletMode ? 5 * GU : 9 * GU}px;\n height: 100%;\n width: 100%;\n`;\n\nconst ListContainer = styled.div<{ compactMode: boolean; tabletMode: boolean }>`\n display: grid;\n grid-gap: ${({ compactMode, tabletMode }) =>\n compactMode ? 3 * GU : tabletMode ? 3 * GU : 5 * GU}px;\n grid-template-columns: ${({ compactMode, tabletMode }) =>\n `repeat(${compactMode ? \"1\" : tabletMode ? \"2\" : \"3\"}, ${\n compactMode ? \"327\" : tabletMode ? \"336\" : \"350\"\n }px)`};\n margin-bottom: ${5 * GU}px;\n`;\n\nconst EntryContainer = styled.div`\n padding: ${3 * GU}px;\n background: ${(props) => props.theme.surface};\n border: 1px solid ${(props) => props.theme.border};\n border-radius: ${2.5 * GU}px;\n box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.15);\n cursor: pointer;\n display: flex;\n flex-direction: column;\n justify-content: space-between;\n height: 223px;\n`;\n\nconst NoticeContainer = styled.div`\n display: -webkit-box;\n -webkit-line-clamp: 3;\n -webkit-box-orient: vertical;\n word-wrap: break-word;\n overflow: hidden;\n ${textStyle(\"title3\")};\n color: ${({ theme }) => theme.content};\n`;\n\nconst InfoContainer = styled.div`\n display: flex;\n justify-content: space-between;\n align-items: center;\n border-top: 1px solid ${(props) => props.theme.border};\n padding-top: ${4 * GU}px;\n`;\n\nconst Hash = styled.div`\n ${textStyle(\"title4\")};\n color: ${({ theme }) => theme.content};\n`;\n", "import { useEffect } from \"react\";\nimport { useNavigate } from \"@remix-run/react\";\n\nexport default function Index() {\n const navigate = useNavigate();\n\n useEffect(() => navigate(\"/home\"), [navigate]);\n\n return null;\n}\n", "import { GU, useViewport } from \"@blossom-labs/rosette-ui\";\nimport {\n useCatch,\n useNavigate,\n useResolvedPath,\n useTransition,\n} from \"@remix-run/react\";\nimport styled from \"styled-components\";\nimport { AppScreen } from \"~/components/AppLayout/AppScreen\";\nimport { ContractForm } from \"~/components/ContractForm\";\n\nexport default function Home() {\n const { below } = useViewport();\n const navigate = useNavigate();\n\n const transition = useTransition();\n const pathDescribe = useResolvedPath(\"/describe\");\n\n const describeIsPending =\n transition.state === \"loading\" &&\n transition.location.pathname === pathDescribe.pathname;\n\n return (\n \n \n \n navigate(`/describe?contract=${contractAddress}`)\n }\n />\n \n \n );\n}\n\nconst MainContainer = styled.div<{ compactMode: boolean }>`\n display: flex;\n justify-content: center;\n align-items: flex-start;\n padding-top: ${({ compactMode }) => (compactMode ? 7 * GU : 23 * GU)}px;\n width: 100%;\n height: 100%;\n`;\n\n// https://remix.run/docs/en/v1/api/conventions#catchboundary\nexport function CatchBoundary() {\n const caught = useCatch();\n\n let message;\n\n switch (caught.status) {\n case 400:\n message = caught.data;\n case 500:\n message = caught.data;\n break;\n default:\n throw new Error(caught.data || caught.statusText);\n }\n\n return (\n
    \n
    \n

    \n {caught.status}: {caught.statusText}\n

    \n {message}\n
    \n
    \n );\n}\n", "import {\n Button,\n Field,\n GU,\n Info,\n LoadingRing,\n TextInput,\n} from \"@blossom-labs/rosette-ui\";\nimport { utils } from \"ethers\";\nimport type { ChangeEvent, FormEventHandler } from \"react\";\nimport { useState } from \"react\";\nimport styled from \"styled-components\";\n\ntype ContractFormProps = {\n loading: boolean;\n onSubmit(contractAddress: string): void;\n};\n\nexport const ContractForm = ({ loading, onSubmit }: ContractFormProps) => {\n const [contractAddress, setContractAddress] = useState(\"\");\n const [errorMsg, setErrorMsg] = useState(\"\");\n\n const handleSubmit: FormEventHandler = (e) => {\n e.preventDefault();\n\n if (!contractAddress) {\n setErrorMsg(\"Type in a contract address.\");\n return;\n }\n if (!utils.isAddress(contractAddress)) {\n setErrorMsg(\"Invalid contract address.\");\n return;\n }\n\n onSubmit(contractAddress);\n };\n\n return (\n \n
    \n \n \n ) => {\n setErrorMsg(\"\");\n setContractAddress(e.target.value);\n }}\n size=\"medium\"\n wide\n />\n \n {errorMsg && {errorMsg}}\n \n \n {loading ? (\n <>\n \n Loading\u2026\n \n ) : (\n <>Next\n )}\n \n \n
    \n );\n};\n\nconst Container = styled.div`\n width: 100%;\n max-width: 440px;\n margin: 0 ${3 * GU}px;\n display: flex;\n flex-direction: column;\n`;\n\nconst NextButton = styled(Button)`\n height: ${8 * GU}px;\n`;\n\nconst Load = styled.div`\n color: ${({ theme }) => theme.contentSecondary};\n`;\n", "import { RADIUS, useViewport, GU, useTheme } from \"@blossom-labs/rosette-ui\";\nimport type { Theme } from \"@uniswap/widgets\";\nimport { SwapWidget } from \"@uniswap/widgets\";\nimport styled from \"styled-components\";\nimport { useSigner } from \"wagmi\";\nimport { AppScreen } from \"~/components/AppLayout/AppScreen\";\nimport { SmoothDisplayContainer } from \"~/components/SmoothDisplayContainer\";\n\nexport default function EntriesRoute() {\n const { below } = useViewport();\n const theme = useTheme();\n const [{ data }] = useSigner();\n\n const uniswapTheme: Theme = {\n primary: `${theme.content}`,\n secondary: `${theme.contentSecondary}`,\n interactive: `${theme.border}`,\n container: `${theme.surface.alpha(0.5)}`,\n module: `${theme.floatingContent}`,\n accent: `${theme.accent}`,\n outline: `${theme.borderDark}`,\n dialog: `${theme.surface}`,\n fontFamily: \"rosette-ui\",\n borderRadius: RADIUS,\n active: `${theme.selected}`,\n error: `${theme.negative}`,\n success: `${theme.positive}`,\n warning: `${theme.warning}`,\n };\n\n return (\n \n \n \n \n \n \n \n );\n}\n\nconst Container = styled.div<{ compactMode: boolean }>`\n display: flex;\n justify-content: center;\n align-items: flex-start;\n padding-top: ${({ compactMode }) => (compactMode ? 7 * GU : 23 * GU)}px;\n width: 100%;\n height: 100%;\n`;\n", "export default {'version':'25ff878a','entry':{'module':'/build/entry.client-EBAADLTY.js','imports':['/build/_shared/chunk-CNJFQE26.js','/build/_shared/chunk-HT5F6U2R.js','/build/_shared/chunk-UGHCEMUG.js','/build/_shared/chunk-DUZJJ23T.js']},'routes':{'root':{'id':'root','parentId':undefined,'path':'','index':undefined,'caseSensitive':undefined,'module':'/build/root-PXKWUHFC.js','imports':['/build/_shared/chunk-3WHR7JJT.js','/build/_shared/chunk-ASQYBJWE.js','/build/_shared/chunk-SMMPVETL.js','/build/_shared/chunk-RUS3BQAR.js','/build/_shared/chunk-XPK36EZQ.js','/build/_shared/chunk-BWVQ5Z6V.js','/build/_shared/chunk-XDPGBQYR.js'],'hasAction':false,'hasLoader':true,'hasCatchBoundary':true,'hasErrorBoundary':true},'routes/contract-descriptions-search':{'id':'routes/contract-descriptions-search','parentId':'root','path':'contract-descriptions-search','index':undefined,'caseSensitive':undefined,'module':'/build/routes/contract-descriptions-search-KYVMHUO6.js','imports':undefined,'hasAction':false,'hasLoader':true,'hasCatchBoundary':false,'hasErrorBoundary':false},'routes/describe':{'id':'routes/describe','parentId':'root','path':'describe','index':undefined,'caseSensitive':undefined,'module':'/build/routes/describe-T7I4UB6E.js','imports':['/build/_shared/chunk-TUV333F6.js','/build/_shared/chunk-LY7RL4A7.js','/build/_shared/chunk-576LPLV6.js'],'hasAction':false,'hasLoader':true,'hasCatchBoundary':false,'hasErrorBoundary':false},'routes/entries':{'id':'routes/entries','parentId':'root','path':'entries','index':undefined,'caseSensitive':undefined,'module':'/build/routes/entries-ZUNCNHYP.js','imports':undefined,'hasAction':false,'hasLoader':false,'hasCatchBoundary':false,'hasErrorBoundary':false},'routes/entries/$entry':{'id':'routes/entries/$entry','parentId':'routes/entries','path':':entry','index':undefined,'caseSensitive':undefined,'module':'/build/routes/entries/$entry-DBVWCV3K.js','imports':['/build/_shared/chunk-PUFJTDWE.js','/build/_shared/chunk-576LPLV6.js','/build/_shared/chunk-XDPGBQYR.js'],'hasAction':false,'hasLoader':true,'hasCatchBoundary':false,'hasErrorBoundary':false},'routes/entries/index':{'id':'routes/entries/index','parentId':'routes/entries','path':undefined,'index':true,'caseSensitive':undefined,'module':'/build/routes/entries/index-7FJUP3E7.js','imports':['/build/_shared/chunk-TUV333F6.js','/build/_shared/chunk-LY7RL4A7.js','/build/_shared/chunk-BWVQ5Z6V.js','/build/_shared/chunk-PUFJTDWE.js','/build/_shared/chunk-576LPLV6.js','/build/_shared/chunk-XDPGBQYR.js'],'hasAction':false,'hasLoader':true,'hasCatchBoundary':false,'hasErrorBoundary':false},'routes/fn-descriptions-upload':{'id':'routes/fn-descriptions-upload','parentId':'root','path':'fn-descriptions-upload','index':undefined,'caseSensitive':undefined,'module':'/build/routes/fn-descriptions-upload-MJ3RJ3SP.js','imports':undefined,'hasAction':true,'hasLoader':false,'hasCatchBoundary':false,'hasErrorBoundary':false},'routes/home':{'id':'routes/home','parentId':'root','path':'home','index':undefined,'caseSensitive':undefined,'module':'/build/routes/home-GB24XS5V.js','imports':['/build/_shared/chunk-576LPLV6.js'],'hasAction':false,'hasLoader':false,'hasCatchBoundary':true,'hasErrorBoundary':false},'routes/index':{'id':'routes/index','parentId':'root','path':undefined,'index':true,'caseSensitive':undefined,'module':'/build/routes/index-6PKL6YGY.js','imports':undefined,'hasAction':false,'hasLoader':false,'hasCatchBoundary':false,'hasErrorBoundary':false},'routes/swap':{'id':'routes/swap','parentId':'root','path':'swap','index':undefined,'caseSensitive':undefined,'module':'/build/routes/swap-5UI7MDV6.js','imports':['/build/_shared/chunk-4Z5DCWAV.js','/build/_shared/chunk-LY7RL4A7.js','/build/_shared/chunk-576LPLV6.js'],'hasAction':false,'hasLoader':false,'hasCatchBoundary':false,'hasErrorBoundary':false}},'url':'/build/manifest-25FF878A.js'};"], + "mappings": "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", + "names": [] +} diff --git a/app/App.tsx b/app/App.tsx index 5dab0dc..2467250 100644 --- a/app/App.tsx +++ b/app/App.tsx @@ -1,19 +1,42 @@ -import { useTheme } from "@1hive/1hive-ui"; -import { ReactNode } from "react"; +import { useTheme } from "@blossom-labs/rosette-ui"; import { ThemeProvider } from "styled-components"; -import { AppLayout } from "./components/AppLayout"; -import { AppReady } from "./providers/AppReady"; -import Wagmi from "./providers/Wagmi"; +import { useState } from "react"; -export const App = ({ children }: { children: ReactNode }) => { +import { AppReady } from "~/providers/AppReady"; +import Wagmi from "~/providers/Wagmi"; +import { AppLayout } from "~/components/AppLayout"; +import { Outlet } from "@remix-run/react"; +import { RosetteStone } from "./providers/RosetteStone"; + +export type AppContext = { + displayTopBar(display: boolean): void; + displayBottomBar(display: boolean): void; +}; + +export const App = () => { const theme = useTheme(); + const [displayTopBar, setDisplayTopBar] = useState(true); + const [displayBottomBar, setDisplayBottomBar] = useState(true); + return ( - - {children} - + + + + + + + ); diff --git a/app/abi/RosetteStone.json b/app/abi/RosetteStone.json new file mode 100644 index 0000000..358ff84 --- /dev/null +++ b/app/abi/RosetteStone.json @@ -0,0 +1,194 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "scope", + "type": "bytes32" + }, + { + "internalType": "bytes4", + "name": "sig", + "type": "bytes4" + }, + { + "internalType": "bytes", + "name": "cid", + "type": "bytes" + } + ], + "name": "InvalidEntry", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "scope", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes4", + "name": "sig", + "type": "bytes4" + } + ], + "name": "EntryRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "scope", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes4", + "name": "sig", + "type": "bytes4" + }, + { + "indexed": false, + "internalType": "address", + "name": "submitter", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "cid", + "type": "bytes" + } + ], + "name": "EntryUpserted", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_scope", + "type": "bytes32" + }, + { + "internalType": "bytes4", + "name": "_sig", + "type": "bytes4" + } + ], + "name": "getEntry", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "enum RosetteStone.EntryStatus", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32[]", + "name": "_scopes", + "type": "bytes32[]" + }, + { + "internalType": "bytes4[]", + "name": "_sigs", + "type": "bytes4[]" + } + ], + "name": "removeEntries", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_scope", + "type": "bytes32" + }, + { + "internalType": "bytes4", + "name": "_sig", + "type": "bytes4" + } + ], + "name": "removeEntry", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32[]", + "name": "_scopes", + "type": "bytes32[]" + }, + { + "internalType": "bytes4[]", + "name": "_sigs", + "type": "bytes4[]" + }, + { + "internalType": "bytes[]", + "name": "_cids", + "type": "bytes[]" + } + ], + "name": "upsertEntries", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_scope", + "type": "bytes32" + }, + { + "internalType": "bytes4", + "name": "_sig", + "type": "bytes4" + }, + { + "internalType": "bytes", + "name": "_cid", + "type": "bytes" + } + ], + "name": "upsertEntry", + "outputs": [], + "stateMutability": "payable", + "type": "function" + } +] diff --git a/app/assets/background.png b/app/assets/background.png new file mode 100644 index 0000000..11c7ea8 Binary files /dev/null and b/app/assets/background.png differ diff --git a/app/assets/blossom-labs.svg b/app/assets/blossom-labs.svg deleted file mode 100644 index 2129bfc..0000000 --- a/app/assets/blossom-labs.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/app/assets/blossom-logo.svg b/app/assets/blossom-logo.svg new file mode 100644 index 0000000..25665ab --- /dev/null +++ b/app/assets/blossom-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/ethereum.svg b/app/assets/ethereum.svg index b522435..ae94a43 100644 --- a/app/assets/ethereum.svg +++ b/app/assets/ethereum.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/assets/gnosis-chain.svg b/app/assets/gnosis-chain.svg index cd5711a..b0ac125 100644 --- a/app/assets/gnosis-chain.svg +++ b/app/assets/gnosis-chain.svg @@ -1,8 +1 @@ - - - - - - - - + \ No newline at end of file diff --git a/app/assets/mobile.png b/app/assets/mobile.png new file mode 100644 index 0000000..8a36167 Binary files /dev/null and b/app/assets/mobile.png differ diff --git a/app/assets/rosette-icon.png b/app/assets/rosette-icon.png new file mode 100644 index 0000000..8507256 Binary files /dev/null and b/app/assets/rosette-icon.png differ diff --git a/app/assets/rosette-logo.svg b/app/assets/rosette-logo.svg new file mode 100644 index 0000000..1ad1aad --- /dev/null +++ b/app/assets/rosette-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/sidebar-entries.svg b/app/assets/sidebar-entries.svg new file mode 100644 index 0000000..dd94baf --- /dev/null +++ b/app/assets/sidebar-entries.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/sidebar-guidelines.svg b/app/assets/sidebar-guidelines.svg new file mode 100644 index 0000000..7e0730c --- /dev/null +++ b/app/assets/sidebar-guidelines.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/sidebar-home.svg b/app/assets/sidebar-home.svg new file mode 100644 index 0000000..0cd4fc8 --- /dev/null +++ b/app/assets/sidebar-home.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/sidebar-swap.svg b/app/assets/sidebar-swap.svg new file mode 100644 index 0000000..6b1c811 --- /dev/null +++ b/app/assets/sidebar-swap.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/tablet.png b/app/assets/tablet.png new file mode 100644 index 0000000..81aefb7 Binary files /dev/null and b/app/assets/tablet.png differ diff --git a/app/components/Account/AccountModule.tsx b/app/components/Account/AccountModule.tsx deleted file mode 100644 index 7806d00..0000000 --- a/app/components/Account/AccountModule.tsx +++ /dev/null @@ -1,256 +0,0 @@ -import { Button, GU, IconConnect } from "@1hive/1hive-ui"; -import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { useLoaderData } from "remix"; -import styled from "styled-components"; -import { - Chain, - useAccount, - useConnect, - useNetwork, - UserRejectedRequestError, - ChainNotConfiguredError, -} from "wagmi"; -import { AccountButton } from "./AccountButton"; -import { HeaderPopover } from "./HeaderPopover"; -import { - ScreenPromptedAction, - ScreenConnected, - ScreenConnecting, - ScreenError, - ScreenWallets, -} from "./screens"; -import { PromptedAction, Screen, ScreenType } from "./types"; - -const SCREENS: Screen[] = [ - { - id: ScreenType.Wallets, - }, - { - id: ScreenType.Connecting, - }, - { - id: ScreenType.Action, - }, - { - id: ScreenType.Connected, - }, - { - id: ScreenType.Error, - }, -]; - -const getNetworkError = ( - networkError: Error | undefined, - networkData: { chain?: { unsupported: boolean | undefined }; chains: Chain[] } -): Error | undefined => { - if (networkData.chain?.unsupported) { - return new ChainNotConfiguredError(); - } - - if (networkError) { - return networkError; - } - - return; -}; - -export const AccountModule = ({ compact }: { compact?: boolean }) => { - const buttonRef = useRef(); - const previousScreenIndex = useRef(-1); - const [ - { data: accountData, error: accountError, loading: accountLoading }, - disconnect, - ] = useAccount(); - const [ - { data: connectData, error: connectError, loading: connectLoading }, - connect, - ] = useConnect(); - const [ - { data: networkData, error: networkError, loading: networkLoading }, - switchNetwork, - ] = useNetwork(); - const [opened, setOpened] = useState(false); - const [promptedActionSucceded, setPromptedActionSucceded] = useState(false); - const [promptedAction, setPromptedAction] = useState(); - const [activatingDelayed, setActivatingDelayed] = useState( - false - ); - const loading = accountLoading || connectLoading || networkLoading; - const error = - getNetworkError(networkError, networkData) || accountError || connectError; - - const accountAddress = accountData?.address; - const currentChain = networkData?.chain; - - const { - direction, - screenIndex, - }: { direction: -1 | 1; screenIndex: ScreenType } = useMemo(() => { - const screenId = (() => { - if (promptedAction) { - return ScreenType.Action; - } - if (activatingDelayed || promptedActionSucceded) { - return ScreenType.Connecting; - } - // Ignore user rejection errors - if (error && !(error instanceof UserRejectedRequestError)) { - return ScreenType.Error; - } - if (accountAddress) { - return ScreenType.Connected; - } - - return ScreenType.Wallets; - })(); - - const screenIndex = SCREENS.findIndex((screen) => screen.id === screenId); - const direction = previousScreenIndex.current > screenIndex ? -1 : 1; - - previousScreenIndex.current = screenIndex; - - return { direction, screenIndex }; - }, [ - activatingDelayed, - accountAddress, - error, - promptedAction, - promptedActionSucceded, - ]); - - const screen = SCREENS[screenIndex]; - const screenId = screen.id; - - // Always show the “connecting…” screen, even if there are no delay - useEffect(() => { - if (loading) { - setActivatingDelayed(loading); - return; - } - - if (error) { - setActivatingDelayed(null); - } - - const timer = setTimeout(() => { - setActivatingDelayed(null); - }, 500); - - return () => { - clearTimeout(timer); - }; - }, [error, loading]); - - // Set flag to display "connecting…" screen when prompting actions succeed - useEffect(() => { - let timer: NodeJS.Timeout; - if (promptedActionSucceded) { - timer = setTimeout(() => { - setPromptedActionSucceded(false); - }, 500); - } - return () => { - if (timer) { - clearTimeout(timer); - } - }; - }, [promptedActionSucceded]); - - const toggle = useCallback(() => setOpened((opened) => !opened), []); - - const handleSwitchNetwork = useCallback( - (switchAction: PromptedAction, chain: Chain) => { - setPromptedAction(switchAction); - - if (switchNetwork) { - switchNetwork(chain.id) - .then(({ data, error }) => { - if (data) { - setPromptedActionSucceded(true); - } else { - console.error(error); - } - setPromptedAction(undefined); - }) - .catch((err) => console.error(err)); - } - }, - [switchNetwork] - ); - - const handlePopoverClose = useCallback(() => { - if ( - screenId === ScreenType.Connecting || - screenId === ScreenType.Error || - screenId === ScreenType.Action - ) { - // reject closing the popover - return false; - } - setOpened(false); - }, [screenId]); - - useEffect(() => { - if (screenId === ScreenType.Action) { - setOpened(true); - } - }, [screenId]); - - return ( - - {screen.id === ScreenType.Connected ? ( - - ) : ( - - - ); -}; - -const Title = styled.h4` - color: ${(props) => props.theme.contentSecondary}; - margin-bottom: ${2 * GU}px, ${textStyle("label2")}; - ${textStyle("label2")}; -`; - -const WalletIcon = styled.img<{ size: string | number }>` - ${({ size }) => size && `width: ${size}px; height: ${size}px;`}; - margin-right: ${0.5 * GU}px; - transform: translateY(-2px); -`; - -const CopyButton = styled(ButtonBase)` - display: flex; - align-items: center; - justify-self: flex-end; - padding: ${0.5 * GU}px; - &:active { - background: ${(props) => props.theme.surfacePressed}; - } -`; - -const NetworkInfo = styled.div` - display: flex; - align-items: center; - color: ${(props) => props.theme.positive}; - ${textStyle("label2")}; -`; diff --git a/app/components/Account/types.ts b/app/components/Account/types.ts deleted file mode 100644 index 2207fb5..0000000 --- a/app/components/Account/types.ts +++ /dev/null @@ -1,18 +0,0 @@ -export enum ScreenType { - Connecting, - Connected, - Error, - Wallets, - Action, -} - -export type Screen = { - id: ScreenType - title?: string -} - -export type PromptedAction = { - title: string - subtitle?: string - image?: string -} diff --git a/app/components/Account/wallet-icons.ts b/app/components/Account/wallet-icons.ts deleted file mode 100644 index 0def6a7..0000000 --- a/app/components/Account/wallet-icons.ts +++ /dev/null @@ -1,13 +0,0 @@ -import metamask from "./assets/metamask.svg" -import walletconnect from "./assets/walletconnect.svg" - -export const getWalletIconPath = (id: string): string | undefined => { - switch (id) { - case "injected": - return metamask - case "walletconnect": - return walletconnect - default: - return - } -} diff --git a/app/components/Account/AccountButton.tsx b/app/components/AccountModule/AccountButton.tsx similarity index 57% rename from app/components/Account/AccountButton.tsx rename to app/components/AccountModule/AccountButton.tsx index 167b0ea..1a48708 100644 --- a/app/components/Account/AccountButton.tsx +++ b/app/components/AccountModule/AccountButton.tsx @@ -3,20 +3,21 @@ import { EthIdenticon, GU, IconDown, - RADIUS, + BIG_RADIUS, shortenAddress, textStyle, useTheme, useViewport, -} from "@1hive/1hive-ui"; -import React from "react"; +} from "@blossom-labs/rosette-ui"; +import { Fragment } from "react"; +import type { ReactNode } from "react"; import styled from "styled-components"; -import { useAccount, useNetwork } from "wagmi"; +import { useAccount } from "wagmi"; type AccountButtonWrapperProps = { - content: React.ReactNode; + content: ReactNode; hasPopover: boolean; - icon?: string | React.ReactNode; + icon?: string | ReactNode; onClick?: () => void; }; @@ -35,11 +36,11 @@ const AccountButtonWrapper = ({ <> {icon} {above("medium") && ( - +
    {content} @@ -47,7 +48,7 @@ const AccountButtonWrapper = ({ {hasPopover && ( )} - + )} @@ -60,44 +61,42 @@ type AccountButtonProps = { }; export const AccountButton = ({ onClick }: AccountButtonProps) => { - const theme = useTheme(); - const [{ data: accountData, loading }] = useAccount({ fetchEns: true }); - const [{ data: networkData }] = useNetwork(); + const { above } = useViewport(); + const [{ data: accountData }] = useAccount({ fetchEns: true }); const { address, ens } = accountData || {}; return ( - - - -
    - } - content={ - <> - {!loading && ( - - - {ens?.name || shortenAddress(address ?? "")} - - - )} -
    - Connected to {networkData.chain?.name} + + + +
    - - } - /> + } + content={ + <> + {!!address && ( + + + {ens?.name || shortenAddress(address ?? "")} + + + )} + + } + /> +
    ); }; +const Container = styled.div<{ large: boolean }>` + ${({ large, theme }) => large && `border: 1px solid ${theme.content};`} + border-radius: 8px; +`; + const AccountButtonBase = styled(ButtonBase)` height: 100%; padding: ${0.2 * GU}px; @@ -111,7 +110,7 @@ const InnerContainer = styled.div` display: flex; align-items: center; text-align: left; - padding: 0 ${1 * GU}px; + padding: ${1 * GU}px ${1.75 * GU}px ${1 * GU}px ${1 * GU}px; `; const ConnectedCircle = styled.div` @@ -120,20 +119,20 @@ const ConnectedCircle = styled.div` right: -3px; width: 10px; height: 10px; - background: ${(props) => props.theme.positive}; - border: 2px solid ${(props) => props.theme.surface}; + background: ${({ theme }) => theme.positive}; + border: 2px solid #141313; border-radius: 50%; `; const LabelWrapper = styled.div` margin-bottom: -5px; - + color: ${({ theme }) => theme.content}; ${textStyle("body2")}; `; const LabelInnerWrapper = styled.div` overflow: hidden; - max-width: ${16 * GU}px; + max-width: 128px; text-overflow: ellipsis; white-space: nowrap; `; diff --git a/app/components/Account/HeaderPopover.tsx b/app/components/AccountModule/HeaderPopover.tsx similarity index 86% rename from app/components/Account/HeaderPopover.tsx rename to app/components/AccountModule/HeaderPopover.tsx index d9ddbf4..1ae3b9c 100644 --- a/app/components/Account/HeaderPopover.tsx +++ b/app/components/AccountModule/HeaderPopover.tsx @@ -1,6 +1,7 @@ -import { GU, Popover, springs } from "@1hive/1hive-ui"; -import { ReactNode, useEffect, useRef, useState } from "react"; -import { a, Spring, useTransition } from "react-spring"; +import { GU, Popover, springs, useViewport } from "@blossom-labs/rosette-ui"; +import { useEffect, useRef, useState } from "react"; +import type { ReactNode } from "react"; +import { a, Spring, useTransition } from "@react-spring/web"; import styled from "styled-components"; type PopoverProps = { @@ -23,6 +24,7 @@ export const HeaderPopover = ({ visible, width, }: PopoverProps) => { + const { above } = useViewport(); const [height, setHeight] = useState(30 * GU); // Prevents to lose the focus on the popover when a screen leaves while an @@ -51,6 +53,7 @@ export const HeaderPopover = ({ return ( ` + margin-top: ${({ large }) => (large ? "17px" : "8px")}; width: ${(props) => `${props.width}px`}; `; diff --git a/app/components/Account/LoadingRing.tsx b/app/components/AccountModule/LoadingRing.tsx similarity index 99% rename from app/components/Account/LoadingRing.tsx rename to app/components/AccountModule/LoadingRing.tsx index 1cdd13f..2599f66 100644 --- a/app/components/Account/LoadingRing.tsx +++ b/app/components/AccountModule/LoadingRing.tsx @@ -1,4 +1,5 @@ import styled, { keyframes } from "styled-components"; + import loadingRing from "./assets/loading-ring.svg"; const spin = keyframes` diff --git a/app/components/Account/assets/loading-ring.svg b/app/components/AccountModule/assets/loading-ring.svg similarity index 67% rename from app/components/Account/assets/loading-ring.svg rename to app/components/AccountModule/assets/loading-ring.svg index da908f0..10b039a 100644 --- a/app/components/Account/assets/loading-ring.svg +++ b/app/components/AccountModule/assets/loading-ring.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/components/Account/assets/metamask.svg b/app/components/AccountModule/assets/metamask.svg similarity index 100% rename from app/components/Account/assets/metamask.svg rename to app/components/AccountModule/assets/metamask.svg diff --git a/app/components/Account/assets/walletconnect.svg b/app/components/AccountModule/assets/walletconnect.svg similarity index 100% rename from app/components/Account/assets/walletconnect.svg rename to app/components/AccountModule/assets/walletconnect.svg diff --git a/app/components/AccountModule/helpers.ts b/app/components/AccountModule/helpers.ts new file mode 100644 index 0000000..530c111 --- /dev/null +++ b/app/components/AccountModule/helpers.ts @@ -0,0 +1,36 @@ +import { ChainNotConfiguredError, UserRejectedRequestError } from "wagmi"; +import type { Chain } from "wagmi"; + +import metamask from "./assets/metamask.svg"; +import walletconnect from "./assets/walletconnect.svg"; + +const chainNotConfiguredError = new ChainNotConfiguredError(); + +export const getWalletIconPath = ( + id: string | undefined +): string | undefined => { + switch (id) { + case "injected": + return metamask; + case "walletConnect": + return walletconnect; + default: + return; + } +}; + +export const getNetworkError = ( + networkError: Error | undefined, + networkData: { chain?: { unsupported: boolean | undefined }; chains: Chain[] } +): Error | undefined => { + if (networkError) { + return networkError; + } + + if (networkData.chain?.unsupported) { + return chainNotConfiguredError; + } +}; + +export const isUserRejectedRequestError = (error: Error) => + error instanceof UserRejectedRequestError; diff --git a/app/components/AccountModule/index.tsx b/app/components/AccountModule/index.tsx new file mode 100644 index 0000000..e4fef50 --- /dev/null +++ b/app/components/AccountModule/index.tsx @@ -0,0 +1,163 @@ +import { Button, GU, IconConnect } from "@blossom-labs/rosette-ui"; +import { useEffect, useRef, useState } from "react"; +import styled from "styled-components"; +import { useAccount, useConnect, useNetwork } from "wagmi"; +import type { Chain, Connector } from "wagmi"; +import { getNetworkError } from "./helpers"; +import { + ScreenType, + actions, + useAccounModuleStore, +} from "./use-account-module-store"; +import type { PromptedAction } from "./use-account-module-store"; +import { AccountButton } from "./AccountButton"; +import { HeaderPopover } from "./HeaderPopover"; +import { + ScreenConnected, + ScreenConnecting, + ScreenError, + ScreenPromptedAction, + ScreenWallets, +} from "./screens"; + +const SCREENS = [ + ScreenWallets, + ScreenConnecting, + ScreenError, + ScreenPromptedAction, + ScreenConnected, +]; + +const { Connecting, Error, Action, Connected } = ScreenType; + +export const AccountModule = ({ compact }: { compact?: boolean }) => { + const buttonRef = useRef(); + const timer = useRef(); + const [{ data: accountData, error: accountError }, disconnect] = useAccount(); + const [{ error: connectError }, connect] = useConnect(); + const [{ data: networkData, error: networkError }, switchNetwork] = + useNetwork(); + const [promptedActionSucceeded, setPromptedActionSucceeded] = useState(false); + const error = + getNetworkError(networkError, networkData) || accountError || connectError; + + const { currentScreen, opened, screenDirection } = useAccounModuleStore(); + + const displayAccountButton = + currentScreen === Connected && !error && accountData?.address; + + const Screen = SCREENS[currentScreen]; + + const handlePopoverClose = () => { + if ( + currentScreen === Connecting || + currentScreen === Error || + currentScreen === Action + ) { + // Reject closing the popover + return false; + } + actions.opened(false); + }; + + const handleSwitchNetwork = (switchAction: PromptedAction, chain: Chain) => { + if (switchNetwork) { + actions.promptedAction(switchAction); + actions.goToScreen(Action); + switchNetwork(chain.id) + .then(({ data, error }) => { + if (data) { + setPromptedActionSucceeded(true); + } else { + console.error(error); + actions.goToInitialScreen(); + actions.promptedAction(null); + } + }) + .catch((err) => console.error(err)); + } + }; + + const handleConnect = (connector: Connector) => { + actions.selectedConnector(connector); + actions.goToScreen(Connecting); + /** + * Set a timer to always display connecting screen for + * a period of time + */ + timer.current = setTimeout(() => { + connect(connector).then(({ data, error }) => { + if (data?.chain?.unsupported || error) { + actions.goToScreen(Error); + } else { + actions.goToScreen(Connected); + } + }); + }, 500); + }; + + const handleBack = () => { + actions.goToInitialScreen(); + disconnect(); + }; + + useEffect(() => { + return () => { + if (timer.current) { + clearTimeout(timer.current); + } + }; + }, []); + + /** + * Wait until network switching process is completed before going to + * the connected screen. + */ + useEffect(() => { + if (promptedActionSucceeded && !networkData.chain?.unsupported) { + actions.goToScreen(Connected); + actions.promptedAction(null); + setPromptedActionSucceeded(false); + } + }, [promptedActionSucceeded, networkData.chain]); + + return ( + + {displayAccountButton ? ( + + ) : ( + } + label="Connect account" + onClick={actions.toggleOpened} + display={compact ? "icon" : "all"} + /> + )} + + + + + ); +}; + +const Container = styled.div` + display: flex; + align-items: center; + justify-content: space-around; + outline: 0; +`; + +const ConnectButton = styled(Button)` + border: 1px solid ${({ theme }) => theme.content}; +`; diff --git a/app/components/AccountModule/screens/ScreenConnected.tsx b/app/components/AccountModule/screens/ScreenConnected.tsx new file mode 100644 index 0000000..153103b --- /dev/null +++ b/app/components/AccountModule/screens/ScreenConnected.tsx @@ -0,0 +1,135 @@ +import { + Button, + ButtonBase, + GU, + IconCheck, + IconCopy, + IdentityBadge, + RADIUS, + textStyle, + useTheme, +} from "@blossom-labs/rosette-ui"; +import { useCallback, useEffect } from "react"; +import styled from "styled-components"; +import { useAccount, useNetwork } from "wagmi"; + +import { useCopyToClipboard } from "~/hooks/useCopyToClipboard"; +import { getWalletIconPath } from "../helpers"; +import { ScreenType, actions } from "../use-account-module-store"; + +type ScreenConnectedProps = { + onBack(): void; +}; + +export const ScreenConnected = ({ onBack }: ScreenConnectedProps) => { + const theme = useTheme(); + const copy = useCopyToClipboard(); + const [{ data: networkData }] = useNetwork(); + const [{ data: accountData }] = useAccount(); + + const chain = networkData.chain; + const wallet = accountData?.connector; + const accountAddress = accountData?.address; + + useEffect(() => { + if (networkData.chain?.unsupported || !accountAddress) { + actions.goToScreen(ScreenType.Error); + } + }, [networkData.chain, accountAddress]); + + const handleCopyAddress = useCallback( + () => copy(accountAddress), + [accountAddress, copy] + ); + + return ( +
    + Active Wallet +
    +
    + + + {wallet?.id === "unknown" ? "Wallet" : wallet?.name} + +
    +
    + + + + +
    +
    +
    + + + + {`Connected to ${chain ? chain.name : "Unknown"} Network`} + + +
    + +
    + ); +}; + +const Title = styled.h4` + color: ${(props) => props.theme.border}; + margin-bottom: ${2 * GU}px; + ${textStyle("body4")}; +`; + +const WalletIcon = styled.img<{ size: string | number }>` + ${({ size }) => size && `width: ${size}px; height: ${size}px;`}; + margin-right: ${0.5 * GU}px; + transform: translateY(-2px); +`; + +const CopyButton = styled(ButtonBase)` + display: flex; + align-items: center; + &:active { + color: ${(props) => props.theme.focus}; + } +`; + +const NetworkInfo = styled.div` + display: flex; + align-items: center; + color: ${(props) => props.theme.positive}; + ${textStyle("body4")}; +`; + +const WalletName = styled.span` + color: ${(props) => props.theme.border}; + ${textStyle("body2")}; +`; diff --git a/app/components/Account/screens/ScreenConnecting.tsx b/app/components/AccountModule/screens/ScreenConnecting.tsx similarity index 72% rename from app/components/Account/screens/ScreenConnecting.tsx rename to app/components/AccountModule/screens/ScreenConnecting.tsx index 32aec2d..20fbf10 100644 --- a/app/components/Account/screens/ScreenConnecting.tsx +++ b/app/components/AccountModule/screens/ScreenConnecting.tsx @@ -1,15 +1,18 @@ -import { GU, textStyle, useTheme } from "@1hive/1hive-ui"; +import { GU, textStyle, useTheme } from "@blossom-labs/rosette-ui"; import styled from "styled-components"; -import { Connector } from "wagmi"; -import { getWalletIconPath } from "../wallet-icons"; -import { LoadingRing } from "../LoadingRing"; -type ScreenConnectingProps = { - wallet: Connector; -}; +import { getWalletIconPath } from "../helpers"; +import { LoadingRing } from "../LoadingRing"; +import { useAccounModuleStore } from "../use-account-module-store"; -export const ScreenConnecting = ({ wallet }: ScreenConnectingProps) => { +export const ScreenConnecting = () => { const theme = useTheme(); + const { selectedConnector: wallet } = useAccounModuleStore(); + + if (!wallet) { + return null; + } + const walletIcon = getWalletIconPath(wallet.id); return ( @@ -52,11 +55,12 @@ const SpinnerImage = styled.div<{ src: string }>` left: 0; right: 0; bottom: 0; - background: 50% 50% / auto 5gu no-repeat url("${(props) => props.src}"); + background: 50% 50% / auto ${5 * GU}px no-repeat url(${(props) => props.src}); `; const Title = styled.h1` padding-top: ${2 * GU}px; font-weight: 600; ${textStyle("body1")}; + color: ${({ theme }) => theme.content}; `; diff --git a/app/components/Account/screens/ScreenError.tsx b/app/components/AccountModule/screens/ScreenError.tsx similarity index 68% rename from app/components/Account/screens/ScreenError.tsx rename to app/components/AccountModule/screens/ScreenError.tsx index 536d5da..30e73be 100644 --- a/app/components/Account/screens/ScreenError.tsx +++ b/app/components/AccountModule/screens/ScreenError.tsx @@ -3,42 +3,49 @@ import { GU, IconError, Link, - noop, textStyle, useTheme, -} from "@1hive/1hive-ui"; +} from "@blossom-labs/rosette-ui"; import { useMemo } from "react"; -import { useLoaderData } from "remix"; import styled from "styled-components"; -import { Chain, ChainNotConfiguredError, useNetwork } from "wagmi"; -import { getNetworkLogo, NETWORKS } from "~/utils"; -import { PromptedAction } from "../types"; +import { + ChainNotConfiguredError, + useAccount, + useConnect, + useNetwork, +} from "wagmi"; +import type { Chain } from "wagmi"; -type ScreenErrorProps = { - error?: Error; - onBack(): void; - onSwitchNetwork?(switchAction: PromptedAction, chain: Chain): void; -}; +import { getNetworkLogo } from "~/utils/client/icons.client"; +import { getNetworkError } from "../helpers"; +import type { PromptedAction } from "../use-account-module-store"; const buildSwitchAction = (chain: Chain): PromptedAction => ({ title: `Connecting to ${chain.name} network`, - subtitle: `Creating and/or switching to the ${chain.name} network (${chain.id}) in your wallet. You may be temporarily redirected to a new screen.`, + subtitle: `Creating and/or switching to the ${chain.name} network (id: ${chain.id}) in your wallet. You may be temporarily redirected to a new screen.`, image: getNetworkLogo(chain.id), }); -export const ScreenError = ({ - error, - onBack, - onSwitchNetwork = noop, -}: ScreenErrorProps) => { - const data = useLoaderData(); - console.log("here"); - console.log(data); +type ScreenErrorProps = { + onSwitchNetwork(switchAction: PromptedAction, chain: Chain): void; + onBack(): void; +}; + +export const ScreenError = ({ onSwitchNetwork, onBack }: ScreenErrorProps) => { const theme = useTheme(); - const [{ data: networkData }, switchNetwork] = useNetwork(); + const [{ data: networkData, error: networkError }, switchNetwork] = + useNetwork(); + const [{ error: connectError }] = useConnect(); + const [{ error: accountError }] = useAccount(); + + const error = + getNetworkError(networkError, networkData) || connectError || accountError; const [title, secondary] = useMemo(() => { - if (error instanceof ChainNotConfiguredError) { + if ( + error instanceof ChainNotConfiguredError || + networkData.chain?.unsupported + ) { return [ "Wrong network", @@ -70,7 +77,7 @@ export const ScreenError = ({ return ( -
    +
    {title}

    @@ -117,6 +124,7 @@ const ErrorContainer = styled.div` const Title = styled.h1` font-weight: 600; ${textStyle("body1")}; + color: ${({ theme }) => theme.content}; `; const ButtonContainer = styled.div` diff --git a/app/components/Account/screens/ScreenPromptedAction.tsx b/app/components/AccountModule/screens/ScreenPromptedAction.tsx similarity index 81% rename from app/components/Account/screens/ScreenPromptedAction.tsx rename to app/components/AccountModule/screens/ScreenPromptedAction.tsx index 25a1fa2..fbcc895 100644 --- a/app/components/Account/screens/ScreenPromptedAction.tsx +++ b/app/components/AccountModule/screens/ScreenPromptedAction.tsx @@ -1,14 +1,16 @@ -import { GU, Link, noop, textStyle } from "@1hive/1hive-ui/"; +import { GU, textStyle } from "@blossom-labs/rosette-ui/"; import styled from "styled-components"; + import { LoadingRing } from "../LoadingRing"; -import { PromptedAction } from "../types"; +import { useAccounModuleStore } from "../use-account-module-store"; -type ScreenActionProps = { - promptedAction: PromptedAction; - onCancel?(): void; -}; +export const ScreenPromptedAction = () => { + const { promptedAction } = useAccounModuleStore(); + + if (!promptedAction) { + return null; + } -export const ScreenPromptedAction = ({ promptedAction }: ScreenActionProps) => { const { title, subtitle, image } = promptedAction; return ( @@ -62,6 +64,7 @@ const Title = styled.h1` padding-top: ${2 * GU}px; font-weight: 600; ${textStyle("body1")}; + color: ${({ theme }) => theme.content}; `; const Subtitle = styled.p` diff --git a/app/components/Account/screens/ScreenWallets.tsx b/app/components/AccountModule/screens/ScreenWallets.tsx similarity index 67% rename from app/components/Account/screens/ScreenWallets.tsx rename to app/components/AccountModule/screens/ScreenWallets.tsx index f343715..db723bb 100644 --- a/app/components/Account/screens/ScreenWallets.tsx +++ b/app/components/AccountModule/screens/ScreenWallets.tsx @@ -1,28 +1,39 @@ -import { ButtonBase, GU, Link, RADIUS, textStyle } from "@1hive/1hive-ui"; +import { + ButtonBase, + GU, + Link, + RADIUS, + textStyle, +} from "@blossom-labs/rosette-ui"; import styled from "styled-components"; -import { Connector } from "wagmi"; -import { getWalletIconPath } from "../wallet-icons"; +import { useConnect } from "wagmi"; +import type { Connector } from "wagmi"; -type ScreenWalletsProps = { - wallets: Connector[]; - onClick: (wallet: Connector) => void; -}; +import { getWalletIconPath } from "../helpers"; + +export const ScreenWallets = ({ + onConnect, +}: { + onConnect(connector: Connector): void; +}) => { + const [{ data: connectData }] = useConnect(); -export const ScreenWallets = ({ wallets, onClick }: ScreenWalletsProps) => { return (

    Ethereum Wallets - {wallets.map((wallet) => ( - onClick(wallet)} - /> - ))} + {connectData.connectors.map((connector) => { + return ( + onConnect(connector)} + /> + ); + })} @@ -65,9 +76,9 @@ function WalletButton({ const MainHeader = styled.h4` padding-top: ${2 * GU}px; padding-left: ${2 * GU}px; - color: ${(props) => props.theme.contentSecondary}; + color: ${({ theme }) => theme.border}; margin-bottom: ${2 * GU}px; - ${textStyle("label2")}; + ${textStyle("body2")}; `; const Container = styled.div` @@ -101,9 +112,11 @@ const WalletButtonBase = styled(ButtonBase)` justify-content: center; width: 100%; height: ${12 * GU}px; - background: ${(props) => props.surface}; + border: 1px solid ${(props) => props.theme.border}; + &:hover { + background-color: ${({ theme }) => theme.surfaceUnder.alpha(0.1)}; + } box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.15); - border-radius: ${RADIUS}px; &:active { top: 1px; box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1); @@ -113,9 +126,11 @@ const WalletButtonBase = styled(ButtonBase)` const WalletName = styled.div` margin-top: ${1 * GU}px; ${textStyle("body1")}; + color: ${({ theme }) => theme.content}; `; const Subtitle = styled.div` margin-top: ${-0.5 * GU}px; ${textStyle("body4")}; + color: ${({ theme }) => theme.contentSecondary}; `; diff --git a/app/components/Account/screens/index.ts b/app/components/AccountModule/screens/index.ts similarity index 100% rename from app/components/Account/screens/index.ts rename to app/components/AccountModule/screens/index.ts diff --git a/app/components/AccountModule/use-account-module-store.ts b/app/components/AccountModule/use-account-module-store.ts new file mode 100644 index 0000000..0b1f189 --- /dev/null +++ b/app/components/AccountModule/use-account-module-store.ts @@ -0,0 +1,70 @@ +import { createStore } from "@udecode/zustood"; +import type { Connector } from "wagmi"; + +export enum ScreenType { + Wallets, + Connecting, + Error, + Action, + Connected, +} + +export type PromptedAction = { + title: string; + subtitle?: string; + image?: string; +}; + +type AccountModuleState = { + opened: boolean; + promptedAction: PromptedAction | null; + selectedConnector: Connector | null; + currentScreen: ScreenType; + screenDirection: -1 | 1; +}; + +const initialState: AccountModuleState = { + opened: false, + promptedAction: null, + selectedConnector: null, + currentScreen: ScreenType.Wallets, + screenDirection: -1, +}; + +const accountModuleStore = createStore("account-module")(initialState, { + devtools: { enabled: process.env.NODE_ENV === "development" }, +}) + .extendActions((set, get) => ({ + toggleOpened: () => set.opened(!get.opened()), + reset: () => { + const { + opened, + promptedAction, + selectedConnector, + currentScreen, + screenDirection, + } = initialState; + + set.opened(opened); + set.promptedAction(promptedAction); + set.selectedConnector(selectedConnector); + set.currentScreen(currentScreen); + set.screenDirection(screenDirection); + }, + goToScreen: (screen: ScreenType) => { + const previousScreen = get.currentScreen(); + const screenDirection = previousScreen > screen ? -1 : 1; + + set.currentScreen(screen); + set.screenDirection(screenDirection); + }, + })) + .extendActions((set) => ({ + goToInitialScreen: () => { + set.reset(); + set.opened(true); + }, + })); + +export const useAccounModuleStore = accountModuleStore.useStore; +export const actions = accountModuleStore.set; diff --git a/app/components/AppLayout/AppScreen.tsx b/app/components/AppLayout/AppScreen.tsx index ae2aa7e..dcb5104 100644 --- a/app/components/AppLayout/AppScreen.tsx +++ b/app/components/AppLayout/AppScreen.tsx @@ -1,17 +1,38 @@ -import { ReactNode } from "react"; -import { a } from "react-spring"; +import type { ReactNode } from "react"; +import { useEffect } from "react"; +import { a } from "@react-spring/web"; +import { useOutletContext } from "@remix-run/react"; import styled from "styled-components"; -import { useAppReady } from "../../providers/AppReady"; +import { useAppReady } from "~/providers/AppReady"; +import type { AppContext } from "~/App"; -export const AppScreen = ({ children }: { children: ReactNode }) => { +type AppScreenProps = { + children: ReactNode; + hideBottomBar?: boolean; + hideTopBar?: boolean; +}; + +export const AppScreen = ({ + children, + hideBottomBar = false, + hideTopBar = false, +}: AppScreenProps) => { const { appReadyTransition } = useAppReady(); + const { displayBottomBar, displayTopBar } = useOutletContext(); + + useEffect(() => { + displayBottomBar(!hideBottomBar); + + displayTopBar(!hideTopBar); + }, [hideBottomBar, hideTopBar, displayBottomBar, displayTopBar]); + return appReadyTransition( ({ progress, screenTransform }, ready) => ready && ( - {children} + {children} ) ); @@ -24,9 +45,3 @@ const AnimatedContainer = styled(a.div)` width: 100%; margin: 0 auto; `; - -const InnerContainer = styled.div` - display: flex; - flex-direction: column; - flex-grow: 1; -`; diff --git a/app/components/AppLayout/BottomBar/BlossomLabsLogo.tsx b/app/components/AppLayout/BottomBar/BlossomLabsLogo.tsx new file mode 100644 index 0000000..822fb28 --- /dev/null +++ b/app/components/AppLayout/BottomBar/BlossomLabsLogo.tsx @@ -0,0 +1,23 @@ +import styled from "styled-components"; + +import blossomLabsLogo from "~/assets/blossom-logo.svg"; + +export const BlossomLabsLogo = () => ( + + + + + +); + +const OutterWrapper = styled.span` + display: inline-block; + vertical-align: middle; +`; diff --git a/app/components/AppLayout/BottomBar/index.tsx b/app/components/AppLayout/BottomBar/index.tsx index 7fdcbb7..026a6d4 100644 --- a/app/components/AppLayout/BottomBar/index.tsx +++ b/app/components/AppLayout/BottomBar/index.tsx @@ -1,14 +1,14 @@ -import { GU, useTheme } from "@1hive/1hive-ui"; -import { a } from "react-spring"; +import { GU, useTheme, useViewport } from "@blossom-labs/rosette-ui"; +import { a } from "@react-spring/web"; import styled from "styled-components"; import { useAppReady } from "~/providers/AppReady"; -import { BlossomLabsLogo } from "../../BlossomLabsLogo"; - -const OPACITY = 0.65; +import { BlossomLabsLogo } from "./BlossomLabsLogo"; export const BottomBar = () => { + const { below } = useViewport(); const theme = useTheme(); const { appReadyTransition } = useAppReady(); + const compactMode = below("large"); return ( @@ -17,17 +17,11 @@ export const BottomBar = () => { ready && ( -
    - Made by with{" "} - - ♥ - +
    + powered by{" "} +
    ) @@ -38,13 +32,14 @@ export const BottomBar = () => { const Container = styled.div` position: relative; - height: ${7 * GU}px; + height: ${9 * GU}px; `; -const AnimatedContainer = styled(a.div)` - position: absolute; - inset: 0; +const AnimatedContainer = styled(a.div)<{ $compactMode: boolean }>` display: flex; - justify-content: flex-end; - padding-right: ${7 * GU}px; + z-index: 1; + padding: 0 ${7 * GU}px; + + justify-content: ${({ $compactMode }) => + $compactMode ? "center" : "flex-start"}; `; diff --git a/app/components/AppLayout/TopBar/NavSection/CompactMenu/MenuItem.tsx b/app/components/AppLayout/TopBar/NavSection/CompactMenu/MenuItem.tsx new file mode 100644 index 0000000..ca7b6b6 --- /dev/null +++ b/app/components/AppLayout/TopBar/NavSection/CompactMenu/MenuItem.tsx @@ -0,0 +1,72 @@ +import { GU, useTheme } from "@blossom-labs/rosette-ui"; +import { Link } from "@remix-run/react"; +import styled from "styled-components"; +import type { NavigationItem } from ".."; + +type MenuItemProps = { + item: NavigationItem; + active?: boolean; + onClick(): void; +}; + +export const MenuItem = ({ + item: { icon, label, to }, + active = false, + onClick, +}: MenuItemProps) => { + const theme = useTheme(); + + return ( +
    + {active && } + + + {icon && ( + + )} + {label} + + +
    + ); +}; + +const ActiveBar = styled.div` + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 4px; + background: #7c5fe0; +`; + +const InnerNavLink = styled.div` + display: flex; + align-items: center; + gap: ${1 * GU}px; + padding: ${1.5 * GU}px; + + & img { + width: 20px; + height: 20px; + } + + & > span { + color: ${({ theme }) => theme.content}; + } +`; diff --git a/app/components/AppLayout/TopBar/NavSection/CompactMenu/Sidebar.tsx b/app/components/AppLayout/TopBar/NavSection/CompactMenu/Sidebar.tsx new file mode 100644 index 0000000..775effb --- /dev/null +++ b/app/components/AppLayout/TopBar/NavSection/CompactMenu/Sidebar.tsx @@ -0,0 +1,78 @@ +import { RootPortal } from "@blossom-labs/rosette-ui"; +import type { ReactNode } from "react"; +import { a, useTransition } from "@react-spring/web"; +import styled from "styled-components"; + +type SidebarProps = { + children: ReactNode; + show: boolean; + width: number; + onToggle(): void; +}; + +export const Sidebar = ({ children, show, width, onToggle }: SidebarProps) => { + const sidebarTransition = useTransition(show, { + from: { + marginLeft: `-${width}px`, + opacity: 0, + }, + enter: { + marginLeft: "0", + opacity: 1, + }, + leave: { + marginLeft: `-${width}px`, + opacity: 0, + }, + unique: true, + config: { mass: 5, tension: 1500, friction: 200 }, + delay: 100, + }); + + return sidebarTransition((styles, show) => { + return ( + show && ( + + + {children} + + + + ) + ); + }); +}; + +const AnimatedSidebar = styled(a.div)<{ width: number; $zIndex: number }>` + height: 100vh; + position: absolute; + border-right: 1px solid ${({ theme }) => theme.border}; + background-color: ${({ theme }) => theme.background}; + ${({ width, $zIndex }) => ` + z-index: ${$zIndex}; + width: ${width}px; + top: 0; + bottom: 0; + left: 0; + `}; +`; + +const OpaqueBackground = styled(a.div)<{ $offset: number; $show: boolean }>` + position: absolute; + top: 0; + right: 0; + left: ${({ $offset }) => $offset}px; + z-index: 1; + height: 100%; + background: ${({ theme }) => theme.overlay.alpha(0.7)}; + pointer-events: ${({ $show }) => ($show ? "auto" : "none")}; +`; diff --git a/app/components/AppLayout/TopBar/NavSection/CompactMenu/index.tsx b/app/components/AppLayout/TopBar/NavSection/CompactMenu/index.tsx new file mode 100644 index 0000000..69755b0 --- /dev/null +++ b/app/components/AppLayout/TopBar/NavSection/CompactMenu/index.tsx @@ -0,0 +1,81 @@ +import { + ButtonBase, + GU, + IconMenu, + useViewport, +} from "@blossom-labs/rosette-ui"; +import { useState } from "react"; +import { useLocation } from "@remix-run/react"; +import styled from "styled-components"; +import type { NavigationItem } from ".."; +import { MenuItem } from "./MenuItem"; +import { Sidebar } from "./Sidebar"; + +const MenuButton = ({ onClick }: { onClick(): void }) => ( + + + + + +); + +const MIN_SIDEBAR_WIDTH = 240; + +export const CompactMenu = ({ items }: { items: NavigationItem[] }) => { + const { pathname } = useLocation(); + const { width } = useViewport(); + const [displaySidebar, setDisplaySidebar] = useState(false); + + const sidebarWidth = Math.min(MIN_SIDEBAR_WIDTH, width * 0.6); + + const toggleSidebar = () => setDisplaySidebar((prev) => !prev); + + return ( +
    + + + + {items.map((i) => ( +
  • + +
  • + ))} +
    +
    +
    + ); +}; + +const ButtonContainer = styled.div` + width: ${8 * GU}px; + height: 100%; + border-right: 1px solid ${({ theme }) => theme.border}; + display: flex; + align-self: stretch; + justify-content: center; + align-items: center; +`; + +const StyledMenuIcon = styled(IconMenu)` + width: ${4 * GU}px; + height: ${4 * GU}px; + color: ${({ theme }) => theme.border}; +`; + +const NavContainer = styled.ul` + list-style: none; +`; diff --git a/app/components/AppLayout/TopBar/NavSection/index.tsx b/app/components/AppLayout/TopBar/NavSection/index.tsx new file mode 100644 index 0000000..c261103 --- /dev/null +++ b/app/components/AppLayout/TopBar/NavSection/index.tsx @@ -0,0 +1,81 @@ +import { GU, textStyle } from "@blossom-labs/rosette-ui"; +import { NavLink, useLocation } from "@remix-run/react"; +import styled from "styled-components"; +import { CompactMenu } from "./CompactMenu"; + +import rosetteLogo from "~/assets/rosette-logo.svg"; +import homeIcon from "~/assets/sidebar-home.svg"; +import entriesIcon from "~/assets/sidebar-entries.svg"; +// import guidelinesIcon from "~/assets/sidebar-guidelines.svg"; +// import swapIcon from "~/assets/sidebar-swap.svg"; + +export type NavigationItem = { + icon: string; + label: string; + to: string; +}; + +const navigationItem: NavigationItem[] = [ + { icon: homeIcon, label: "Home", to: "/home" }, + { icon: entriesIcon, label: "All entries", to: "/entries" }, + // { icon: guidelinesIcon, label: "Guidelines", to: "/guidelines" }, + // { icon: swapIcon, label: "Swap", to: "/swap" }, +]; + +export const NavSection = ({ compact }: { compact: boolean }) => { + const { pathname } = useLocation(); + + return compact ? ( + + ) : ( + + {navigationItem.map(({ label, to }) => { + const renderBottom = to !== "/home" && pathname === to; + + return ( + + + {label === "Home" ? ( +
    + +
    + ) : ( + label + )} +
    +
    + ); + })} +
    + ); +}; + +const LiStyled = styled.li<{ renderBottom: boolean }>` + ${({ renderBottom }) => + renderBottom && "padding-bottom: 2px; border-bottom: 1px solid;"} +`; + +const NavLinksList = styled.ul` + display: flex; + align-items: center; + gap: ${6 * GU}px; + list-style: none; + ${textStyle("body2")}; + color: ${(props) => props.theme.content}; + + li:first-child { + ${textStyle("title2")}; + } + + > li { + transition: all 200ms ease; + padding-bottom: 2px; + &:not(:first-child):hover { + border-bottom: 1px solid; + } + } + + li > * { + text-decoration: none; + } +`; diff --git a/app/components/AppLayout/TopBar/index.tsx b/app/components/AppLayout/TopBar/index.tsx index 1d80bbc..e3a1f51 100644 --- a/app/components/AppLayout/TopBar/index.tsx +++ b/app/components/AppLayout/TopBar/index.tsx @@ -1,12 +1,16 @@ -import { GU, textStyle } from "@1hive/1hive-ui"; -import { NavLink } from "@remix-run/react"; -import { a } from "react-spring"; +import { GU, useViewport } from "@blossom-labs/rosette-ui"; +import { a } from "@react-spring/web"; import styled from "styled-components"; -import { AccountModule } from "~/components/Account/AccountModule"; + import { useAppReady } from "~/providers/AppReady"; +import { AccountModule } from "~/components/AccountModule"; +import { NavSection } from "./NavSection"; export const TopBar = () => { const { appReadyTransition } = useAppReady(); + const { below } = useViewport(); + const compactMode = below("large"); + const mobileMode = below("medium"); return ( @@ -14,20 +18,14 @@ export const TopBar = () => { ({ progress, topBarTransform }, ready) => ready && ( - -
  • - Rosette -
  • -
  • - Entries -
  • -
  • - Guidelines -
  • -
    - + +
    ) )} @@ -41,32 +39,21 @@ const NavContainer = styled.nav` height: ${8 * GU}px; `; -const AnimatedContainer = styled(a.div)` +const AnimatedContainer = styled(a.div)<{ $compactMode: boolean }>` position: absolute; inset: 0; - padding: ${1.7 * GU}px ${5 * GU}px; + z-index: 1; + border-bottom: ${({ $compactMode, theme }) => + $compactMode ? `1px solid ${theme.border}` : ""}; + ${({ $compactMode }) => + $compactMode + ? ` + padding-right: ${1 * GU}px; + ` + : ` + padding-right: ${6 * GU}px; + padding-left: ${6 * GU}px; + `}; display: flex; justify-content: space-between; `; - -const NavLinksList = styled.ul` - display: flex; - align-items: center; - gap: ${6 * GU}px; - list-style: none; - - li:first-child { - ${textStyle("title2")}; - } - - > li { - transition: all 200ms ease-out; - &:hover { - color: ${(props) => props.theme.surfaceHighlight}; - } - } - - li > * { - text-decoration: none; - } -`; diff --git a/app/components/AppLayout/index.tsx b/app/components/AppLayout/index.tsx index 2e97c5d..f31b9ad 100644 --- a/app/components/AppLayout/index.tsx +++ b/app/components/AppLayout/index.tsx @@ -1,28 +1,58 @@ -import { ReactNode } from "react"; +import { useViewport } from "@blossom-labs/rosette-ui"; +import type { ReactNode } from "react"; import styled from "styled-components"; +import background from "~/assets/background.png"; +import tablet from "~/assets/tablet.png"; +import mobile from "~/assets/mobile.png"; import { BottomBar } from "./BottomBar"; import { TopBar } from "./TopBar"; -export const AppLayout = ({ children }: { children: ReactNode }) => { +type AppLayoutProps = { + children: ReactNode; + displayTopBar?: boolean; + displayBottomBar?: boolean; +}; + +export const AppLayout = ({ + children, + displayTopBar = true, + displayBottomBar = true, +}: AppLayoutProps) => { + const { below, within } = useViewport(); + + const compactMode = below("medium"); + const tabletMode = within("medium", "large"); + return ( - -
    - -
    + + {displayTopBar && ( +
    + +
    + )} {children} -
    - -
    + {displayBottomBar && ( +
    + +
    + )}
    ); }; -const Container = styled.div` +const Container = styled.div<{ + compactMode: boolean; + tabletMode: boolean; +}>` position: relative; + overflow: auto; height: 100vh; margin: 0 auto; display: flex; flex-direction: column; + background-size: cover; + background-image: url(${({ compactMode, tabletMode }) => + compactMode ? mobile : tabletMode ? tablet : background}); `; const ChildrenWrapper = styled.div` diff --git a/app/components/BlossomLabsLogo.tsx b/app/components/BlossomLabsLogo.tsx deleted file mode 100644 index 13e5703..0000000 --- a/app/components/BlossomLabsLogo.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { GU } from "@1hive/1hive-ui/"; -import styled from "styled-components"; -import blossomLabsIcon from "~/assets/blossom-labs.svg"; - -type BlossomLabsLogoProps = { - iconSize?: string; - opacity?: number | string; - showIconOnly?: boolean; -}; - -export const BlossomLabsLogo = ({ - iconSize = "20px", - opacity, - showIconOnly = false, -}: BlossomLabsLogoProps) => ( - - -
    - {" "} - {!showIconOnly && "Blossom Labs"} -
    -
    -
    -); - -const OutterWrapper = styled.span<{ opacity?: number | string }>` - display: inline-block; - color: #f8b9b6; - opacity: ${(p) => p.opacity}; - font-weight: bold; - vertical-align: middle; -`; diff --git a/app/components/ContractDescriptorScreen/Carousel/PrevNext.tsx b/app/components/ContractDescriptorScreen/Carousel/PrevNext.tsx new file mode 100644 index 0000000..fe707b0 --- /dev/null +++ b/app/components/ContractDescriptorScreen/Carousel/PrevNext.tsx @@ -0,0 +1,50 @@ +import { + GU, + ButtonIcon, + IconDown, + IconUp, + IconRight, + IconLeft, +} from "@blossom-labs/rosette-ui"; +import styled from "styled-components"; + +type PrevNextProps = { + isHorizontal?: boolean; + type: "next" | "previous"; + onClick: () => void; +}; +export const PrevNext = ({ + isHorizontal = true, + type, + onClick, +}: PrevNextProps) => { + const NextIcon = isHorizontal ? IconRight : IconDown; + const PrevIcon = isHorizontal ? IconLeft : IconUp; + const next = type === "next"; + const Icon = next ? NextIcon : PrevIcon; + + return ( + + + + + + ); +}; + +const Container = styled.div<{ isHorizontal: boolean; next: boolean }>` + position: absolute; + z-index: 1; + ${({ isHorizontal, next }) => + isHorizontal + ? ` + top: calc(50% - ${5 * GU}px); + ${next ? "right" : "left"}: ${0.5 * GU}px; + ` + : ` + top: calc(${next ? `100% - ${5 * GU}px` : "0"}); + left: 50%; + `}; + color: ${({ theme }) => theme.surfaceContentSecondary}; + height: ${6 * GU}px; +`; diff --git a/app/components/ContractDescriptorScreen/Carousel/index.tsx b/app/components/ContractDescriptorScreen/Carousel/index.tsx new file mode 100644 index 0000000..4d08c1c --- /dev/null +++ b/app/components/ContractDescriptorScreen/Carousel/index.tsx @@ -0,0 +1,249 @@ +import { GU, noop, useViewport } from "@blossom-labs/rosette-ui"; +import type { ReactNode } from "react"; +import { useCallback, useEffect, useState, useRef } from "react"; +import { a, useSpring } from "@react-spring/web"; +import styled from "styled-components"; +import { PrevNext } from "./PrevNext"; + +type CarouselProps = { + items: ReactNode[]; + selected: number; + compactMode?: boolean; + direction?: "horizontal" | "vertical"; + itemSpacing?: number; + customSideSpace?: number; + showPrevNext?: boolean; + onItemSelected?(selected: number): void; + onTransitionEnd?(): void; +}; + +const DEFAULT_SIZE = { width: 0, height: 0 }; + +export const Carousel = ({ + items, + selected = 0, + compactMode = false, + direction = "vertical", + itemSpacing = 3 * GU, + customSideSpace, + showPrevNext = false, + onItemSelected = noop, + onTransitionEnd = noop, +}: CarouselProps) => { + const [containerSize, setContainerSize] = useState({ ...DEFAULT_SIZE }); + const container = useRef(null); + const { width: vw } = useViewport(); + const isHorizontal = direction === "horizontal"; + const itemSize = containerSize[isHorizontal ? "width" : "height"]; + // The space on one side of the visible items + const sideSpace = + customSideSpace && customSideSpace >= 0 ? customSideSpace : 0; + + // const allowDrag = compactMode || (items && items.length > visibleItems); + + useEffect(() => { + onItemSelected(selected); + }, [onItemSelected, selected]); + + const updateContainerSize = useCallback((element) => { + setContainerSize( + element + ? { width: element.clientWidth, height: element.clientHeight } + : { ...DEFAULT_SIZE } + ); + }, []); + + useEffect(() => { + if (container.current) { + updateContainerSize(container.current); + } + }, [vw, updateContainerSize]); + + const handleContainerRef = useCallback( + (element) => { + container.current = element; + updateContainerSize(element); + }, + [updateContainerSize] + ); + + // Get the container x position from an item index + const xFromItem = useCallback( + (index) => { + return sideSpace - (itemSize + itemSpacing) * index; + }, + [sideSpace, itemSize, itemSpacing] + ); + + // The current x position, before the drag + const selectedX = xFromItem(selected); + + // The x position of the last item, before the drag + // const lastX = xFromItem(items.length - 1); + + // Handles the actual x position, with the drag + const [{ x, drag }, setX] = useSpring(() => ({ + x: selectedX, + drag: Number(false), + immediate: true, + onRest: onTransitionEnd, + })); + + // TODO: Implement dragging feature for mobile screens + // Update the transition during drag + // const bindDrag = useDrag(({ down, delta }) => { + // const updatedX = Math.max(lastX, Math.min(sideSpace, selectedX + delta[0])); + + // if (!allowDrag) { + // return; + // } + + // if (down) { + // setX({ + // x: updatedX, + // immediate: true, + // }); + // } else { + // let target = selected; + // if (Math.abs(delta[0]) > itemWidth / 2) { + // const sP = delta[0] > 0 ? -sideSpace : sideSpace; + // const deltaSelected = Math.abs( + // Math.round((delta[0] + sP) / (itemWidth + itemSpacing)) + // ); + // // Moving to the left + // if (delta[0] > 0) { + // target = Math.max(0, selected - deltaSelected); + // // Move to the right + // } else { + // target = Math.min( + // items.length - visibleItems, + // selected + deltaSelected + // ); + // } + // } + + // setX({ + // x: xFromItem(target), + // immediate: false, + // }); + // setSelected(target); + // } + // }); + + // Update the transition when the base x position updates + useEffect(() => { + setX({ + x: selectedX, + immediate: false, + }); + }, [selectedX, setX]); + + return ( + + {showPrevNext && ( + <> + {selected > 0 && ( + onItemSelected(Math.max(0, selected - 1))} + isHorizontal={isHorizontal} + /> + )} + {selected < items.length - 1 && ( + + onItemSelected(Math.min(items.length - 1, selected + 1)) + } + isHorizontal={isHorizontal} + /> + )} + + )} + + + `translate3d(${ + isHorizontal ? `${x || 0}px, 0, 0` : `0, ${x || 0}px, 0` + })` + ), + }} + > + {items.map((item, i) => ( + { + return drag || (i >= selected && i < selected + 1) ? 1 : 0.15; + }), + }} + $isFirst={i === 0} + $isHorizontal={isHorizontal} + $itemHeight={containerSize.height} + $itemWidth={containerSize.width} + $itemSpacing={itemSpacing} + > + {item} + + ))} + + + ); +}; + +const Container = styled.div<{ + isHorizontal: boolean; +}>` + position: relative; + overflow: hidden; + touch-action: none; + height: 100%; + width: 100%; +`; + +const AnimatedContainer = styled(a.div)<{ $isHorizontal: boolean }>` + position: absolute; + display: flex; + width: 100%; + box-sizing: border-box; + + align-items: center; + touch-action: none; + ${(props) => + props.$isHorizontal + ? ` + flex-direction: row; + justify-content: flex-start; + height: 100%; + ` + : ` + flex-direction: column; + justify-content: center; + `}; +`; + +const AnimatedItemContainer = styled(a.div)<{ + $isHorizontal: boolean; + $isFirst: boolean; + $itemWidth: number; + $itemHeight: number; + $itemSpacing: number; +}>` + width: ${(props) => props.$itemWidth}px; + height: ${(props) => props.$itemHeight}px; + transition: opacity 150ms ease-in-out; + flex-grow: 0; + flex-shrink: 0; + ${({ $isHorizontal }) => ($isHorizontal ? "margin-left" : "margin-top")}: ${({ + $isFirst, + $itemSpacing, +}) => (!$isFirst ? `${$itemSpacing}px` : 0)}}; +`; diff --git a/app/components/ContractDescriptorScreen/FnDescriptorsCarousel.tsx b/app/components/ContractDescriptorScreen/FnDescriptorsCarousel.tsx new file mode 100644 index 0000000..eb3a6b7 --- /dev/null +++ b/app/components/ContractDescriptorScreen/FnDescriptorsCarousel.tsx @@ -0,0 +1,131 @@ +import { createRef, useEffect, useRef, useState } from "react"; +import type { RefObject } from "react"; +import { Carousel } from "./Carousel"; +import { FunctionDescriptor } from "./FunctionDescriptor"; +import { + actions, + useContractDescriptorStore, +} from "./use-contract-descriptor-store"; +import { getSelectionRange } from "~/utils/client/selection.client"; +import { TestModal } from "./TestModal"; + +type FnDescriptorsCarouselProps = { + compactMode: boolean; +}; + +export const FnDescriptorsCarousel = ({ + compactMode, +}: FnDescriptorsCarouselProps) => { + const { + filteredFnDescriptorEntries, + fnSelected, + lastCaretPos, + userFnDescriptions, + readyToFocus, + } = useContractDescriptorStore(); + const descriptorRefs = useRef[]>([]); + const prevDescriptorIndexRef = useRef(-1); + const currentDescriptorIndexRef = useRef(0); + const [carouselMoveAnimationEnded, setCarouselMoveEnded] = useState(false); + const [showTestingModal, setShowTestingModal] = useState(false); + + /** + * Generate descriptor refs + */ + useEffect(() => { + descriptorRefs.current = filteredFnDescriptorEntries.map( + (_, i) => descriptorRefs.current[i] ?? createRef() + ); + + if (!descriptorRefs.current.length) { + return; + } + // Wait a little bit for the ref to be attached before focusing first element + setTimeout(() => descriptorRefs.current[0].current?.focus(), 200); + }, [filteredFnDescriptorEntries]); + + /** + * Focus on current element once the transition animation ended. + */ + useEffect(() => { + if ( + carouselMoveAnimationEnded && + descriptorRefs.current[fnSelected]?.current + ) { + descriptorRefs.current[fnSelected]?.current?.focus(); + setCarouselMoveEnded(false); + } + }, [carouselMoveAnimationEnded, fnSelected]); + + /** + * Lose focus on last element when a new one was selected to not mess up + * the transition animation if user types in. + */ + useEffect(() => { + if (!descriptorRefs.current.length) { + return; + } + + if (prevDescriptorIndexRef.current === -1) { + prevDescriptorIndexRef.current = fnSelected; + } else { + prevDescriptorIndexRef.current = currentDescriptorIndexRef.current; + + descriptorRefs.current[prevDescriptorIndexRef.current]?.current?.blur(); + } + + currentDescriptorIndexRef.current = fnSelected; + }, [fnSelected]); + + /** + * Focus on current element when function was selected and set + * selection on first parameter. + */ + useEffect(() => { + if ( + !readyToFocus || + !descriptorRefs.current.length || + !descriptorRefs.current[fnSelected]?.current + ) { + return; + } + + const selectedDescriptor = descriptorRefs.current[fnSelected].current!; + + selectedDescriptor.focus(); + + const [start, end] = getSelectionRange( + selectedDescriptor.value, + lastCaretPos + ); + + selectedDescriptor.setSelectionRange(start, end); + }, [lastCaretPos, readyToFocus, fnSelected]); + + return ( + <> + ( + setShowTestingModal(true)} + /> + ))} + direction={compactMode ? "horizontal" : "vertical"} + itemSpacing={450} + onTransitionEnd={() => setCarouselMoveEnded(true)} + /> +
    e.stopPropagation()}> + setShowTestingModal(false)} + /> +
    + + ); +}; diff --git a/app/components/ContractDescriptorScreen/FunctionDescriptor/DescriptionField.tsx b/app/components/ContractDescriptorScreen/FunctionDescriptor/DescriptionField.tsx new file mode 100644 index 0000000..3e0de49 --- /dev/null +++ b/app/components/ContractDescriptorScreen/FunctionDescriptor/DescriptionField.tsx @@ -0,0 +1,112 @@ +import { GU, RADIUS, textStyle } from "@blossom-labs/rosette-ui"; +import { forwardRef, useEffect, useState } from "react"; +import type { FocusEventHandler, KeyboardEventHandler } from "react"; +import styled from "styled-components"; +import { useDebounce } from "~/hooks/useDebounce"; + +type DescriptionFieldProps = { + description?: string; + disabled?: boolean; + height?: string; + placeholder?: string; + textSize?: string; + onChange(value: string): void; + onBlur?: FocusEventHandler; + onKeyDown: KeyboardEventHandler; +}; + +export const DescriptionField = forwardRef< + HTMLTextAreaElement, + DescriptionFieldProps +>( + ( + { + description, + disabled = false, + height = `${10 * GU}px`, + textSize = "title4", + placeholder = "Add description…", + onChange, + ...props + }, + ref + ) => { + const [value, setValue] = useState(description); + const debouncedValue = useDebounce(value, 400); + + useEffect(() => { + if (debouncedValue !== undefined) { + onChange(debouncedValue); + } + }, [debouncedValue, onChange]); + + /** + * Keep inner description value in sync as it can be updated from other places + * of the component tree (e.g. adding a function from the picker) + */ + useEffect(() => { + setValue(description); + }, [description]); + + return ( + setValue(e.target.value)} + {...props} + /> + ); + } +); + +DescriptionField.displayName = "DescriptionField"; + +const DescriptionTextArea = styled.textarea<{ + height: string; + textSize: string; +}>` + height: ${(props) => props.height}; + padding: ${1 * GU}px ${1.5 * GU}px; + background: ${(props) => props.theme.surface.alpha(0.5)}; + color: ${(props) => props.theme.contentSecondary}; + appearance: none; + border-radius: ${RADIUS}px; + width: 100%; + outline: none; + resize: none; + ${(props) => textStyle(props.textSize)}; + + &:focus { + outline: none; + border-color: ${(props) => props.theme.focus}; + } + &:read-only { + color: ${(props) => props.theme.hint}; + border-color: ${(props) => props.theme.borderDark}; + } + &::placeholder { + color: ${(props) => props.theme.border}; + opacity: 0.5; + } + &:invalid { + box-shadow: none; + } + + // TODO: Check with Paulo how the following styles render to check if we need to update the colors + &::-webkit-scrollbar { + width: 12px; + background-color: ${(props) => props.theme.surfaceSelected}; + } + + &::-webkit-scrollbar-thumb { + border-radius: 10px; + box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.1); + -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.1); + background-color: ${(props) => props.theme.surfaceIcon}; + } +`; diff --git a/app/components/ContractDescriptorScreen/FunctionDescriptor/index.tsx b/app/components/ContractDescriptorScreen/FunctionDescriptor/index.tsx new file mode 100644 index 0000000..adfc20b --- /dev/null +++ b/app/components/ContractDescriptorScreen/FunctionDescriptor/index.tsx @@ -0,0 +1,125 @@ +import { + Button, + GU, + textStyle, + addressesEqual, +} from "@blossom-labs/rosette-ui"; +import { forwardRef, memo, useCallback } from "react"; +import type { FocusEventHandler, KeyboardEventHandler } from "react"; +import styled from "styled-components"; +import { StatusLabel } from "~/components/StatusLabel"; +import { FnDescriptionStatus } from "~/types"; +import { actions } from "../use-contract-descriptor-store"; +import type { FnDescriptorEntry } from "../use-contract-descriptor-store"; +import { DescriptionField } from "./DescriptionField"; +import { canTab, getSelectionRange } from "~/utils/client/selection.client"; +import { useAccount } from "wagmi"; + +type FunctionDescriptorProps = { + fnDescriptorEntry: FnDescriptorEntry; + description?: string; + onEntryChange(sigHash: string, description: string): void; + onTestFunction(): void; +}; + +export const FunctionDescriptor = memo( + forwardRef( + ( + { + fnDescriptorEntry, + description, + onEntryChange, + onTestFunction, + ...props + }, + ref + ) => { + const [{ data: accountData }] = useAccount(); + const entry = fnDescriptorEntry.entry; + const { notice, status, submitter } = entry || {}; + const descriptorStatus = status || FnDescriptionStatus.Available; + const disableDescriptionField = + !!submitter && !addressesEqual(submitter, accountData?.address ?? ""); + const fnDescription = description ?? notice; + + const handleKeyDown: KeyboardEventHandler = (e) => { + const textArea = e.target as HTMLTextAreaElement; + const text = textArea.value; + const offset = textArea.selectionStart; + + if (e.code === "Tab" && text && canTab(text, offset)) { + e.preventDefault(); + const [start, end] = getSelectionRange(text, offset); + + textArea.setSelectionRange(start, end); + } + }; + + const handleBlur: FocusEventHandler = (e) => { + actions.lastCaretPos(e.target.selectionStart); + }; + + /** + * Need to wrap handler on callback to make helper function use logic + * work. + */ + const handleOnChange = useCallback( + (value) => { + onEntryChange(fnDescriptorEntry.sigHash, value); + }, + [onEntryChange, fnDescriptorEntry.sigHash] + ); + + return ( + + + Description + + + + + {fnDescriptorEntry.fullName.split(" returns (")[0]} + +
    + ) : ( + "Test" + ) + } + wide + onClick={handleTest} + mode="strong" + disabled={disableTestButton} + /> + + + {(evaluatorErrorMsg || evaluatedDescription) && ( +
    + + {evaluatorErrorMsg || evaluatedDescription} + +
    + )} + + ); +}; diff --git a/app/components/ContractDescriptorScreen/assets/hand-icon.svg b/app/components/ContractDescriptorScreen/assets/hand-icon.svg new file mode 100644 index 0000000..2b330d8 --- /dev/null +++ b/app/components/ContractDescriptorScreen/assets/hand-icon.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/app/components/ContractDescriptorScreen/assets/scroll-icon.svg b/app/components/ContractDescriptorScreen/assets/scroll-icon.svg new file mode 100644 index 0000000..6567ba1 --- /dev/null +++ b/app/components/ContractDescriptorScreen/assets/scroll-icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/components/ContractDescriptorScreen/index.tsx b/app/components/ContractDescriptorScreen/index.tsx new file mode 100644 index 0000000..7575dee --- /dev/null +++ b/app/components/ContractDescriptorScreen/index.tsx @@ -0,0 +1,351 @@ +import { + Button, + GU, + LoadingRing, + RootPortal, + useViewport, +} from "@blossom-labs/rosette-ui"; +import { useFetcher } from "@remix-run/react"; +import type { Fetcher } from "@remix-run/react"; +import { utils } from "ethers"; +import { useCallback, useEffect, useState } from "react"; +import type { WheelEventHandler } from "react"; +import styled from "styled-components"; +import { useAccount } from "wagmi"; + +import scrollIcon from "./assets/scroll-icon.svg"; +import handIcon from "./assets/hand-icon.svg"; +import { Pagination } from "./Pagination"; +import { + actions, + selectors, + useContractDescriptorStore, +} from "./use-contract-descriptor-store"; +import type { + FnDescriptorEntry, + UserFnDescription, +} from "./use-contract-descriptor-store"; +import type { ContractData, FnEntry } from "~/types"; +import useRosetteActions from "./useRosetteActions"; +import { HelperFunctionsPicker } from "./HelperFunctionsPicker"; +import { FnDescriptorsCarousel } from "./FnDescriptorsCarousel"; +import type { ArweaveData } from "~/routes/fn-descriptions-upload"; +import { FunctionDescriptorFilters } from "./FunctionDescriptorFilters"; +import debounce from "lodash.debounce"; + +const FN_DESCRIPTOR_DEFAULT_HEIGHT = "527px"; + +const debouncedOnWheel = debounce((e) => { + e.stopPropagation(); + e.preventDefault(); + if (e.deltaY < 0) { + actions.goToPrevFn(); + } else { + actions.goToNextFn(); + } + + return false; +}, 100); + +type ContractDescriptorScreenProps = { + contractAddress: string; + contractData: ContractData; + currentFnEntries: FnEntry[]; +}; + +const buildBundlrUploadData = ( + fnDescriptorEntries: FnDescriptorEntry[], + userFnDescriptions: Record +): ArweaveData["functions"] => { + return Object.keys(userFnDescriptions).map((sigHash) => { + const fullName = fnDescriptorEntries.find( + (e) => e.sigHash === sigHash + )?.fullName; + + if (!fullName) { + throw new Error( + `Couldn't upload to Bundlr: function ${sigHash} not found.` + ); + } + + return { + description: userFnDescriptions[sigHash].description, + fullName, + sigHash, + }; + }); +}; + +const uploadFetcherReturnedData = (fetcher: Fetcher): boolean => + fetcher.state === "loading" && + fetcher.type === "actionReload" && + fetcher.data; + +export const ContractDescriptorScreen = ({ + contractAddress, + contractData, + currentFnEntries, +}: ContractDescriptorScreenProps) => { + const [callingContract, setCallingContract] = useState(false); + const { below } = useViewport(); + const [{ data: accountData }] = useAccount(); + const { fnSelected, filteredFnDescriptorEntries, userFnDescriptions } = + useContractDescriptorStore(); + const actionFetcher = useFetcher(); + const { upsertEntries } = useRosetteActions(); + const bytecodeHash = utils.id(contractData.bytecode); + const fnDescriptionsCounter = selectors.fnDescriptionsCounter(); + const compactMode = below("large"); + const submittingEntries = + actionFetcher.state === "submitting" || + actionFetcher.state === "loading" || + callingContract; + const submitDisabled = + !accountData?.address || fnDescriptionsCounter === 0 || submittingEntries; + + const SubmitButton = () => ( + + + + Submitting entries… +
    + ) : ( + `Submit (${fnDescriptionsCounter})` + ) + } + type="submit" + mode="strong" + wide + disabled={submitDisabled} + /> + + ); + + const handleSubmit = useCallback( + (event) => { + event.preventDefault(); + + const fnsData = buildBundlrUploadData( + filteredFnDescriptorEntries, + userFnDescriptions + ); + + actionFetcher.submit( + { bytecodeHash, functions: JSON.stringify(fnsData) }, + { + method: "post", + action: "/fn-descriptions-upload", + } + ); + }, + [ + actionFetcher, + bytecodeHash, + filteredFnDescriptorEntries, + userFnDescriptions, + ] + ); + + useEffect(() => { + const submitEntries = async () => { + try { + const sigs = Object.keys(userFnDescriptions).map((sigHash) => sigHash); + const scopes = new Array(sigs.length).fill(bytecodeHash); + const cids: string[] = Object.values(actionFetcher.data); + + setCallingContract(true); + await upsertEntries(scopes, sigs, cids); + window.alert("Entries submitted!"); // TODO: Use tx feedback implementation + + // Should we redirect to the entries page? + } catch (err) { + console.error(`Error submitting entries: ${err}`); + } finally { + setCallingContract(false); + } + }; + + /** + * It keeps this effect from running more than once after action + * fetcher returns data. + */ + if (uploadFetcherReturnedData(actionFetcher)) { + submitEntries(); + } + }, [ + actionFetcher, + bytecodeHash, + contractAddress, + upsertEntries, + userFnDescriptions, + ]); + + useEffect(() => { + if (contractData && currentFnEntries) { + actions.setUpContractDescriptorStore(contractData, currentFnEntries); + } + }, [contractData, currentFnEntries]); + + return ( +
    + + + + + {filteredFnDescriptorEntries.length > 1 && ( + + + + + )} + + {filteredFnDescriptorEntries.length ? ( + + ) : ( + No functions found. + )} + + + + + {compactMode ? ( + + ) : ( + + + + )} + +
    + ); +}; + +const Layout = styled.div<{ compactMode: boolean }>` + display: grid; + height: 100%; + width: 100%; + padding: ${4 * GU}px; + grid-gap: ${1 * GU}px; + + ${({ compactMode }) => ` + ${FiltersContainer} { + grid-area: filters; + } + + ${PaginationContainer} { + grid-area: pagination; + ${ + compactMode + ? ` + flex-direction: column-reverse; + ` + : ` + justify-self: start; + ` + } + } + + ${CarouselContainer} { + grid-area: carousel; + } + + ${FunctionsPickerContainer} { + grid-area: picker; + } + + ${SubmitContainer} { + grid-area: submit; + place-self: end; + } + + ${ + compactMode + ? `grid: + [row1-start] "filters" 1fr [row1-end] + [row2-start] "picker" 0.5fr [row2-end] + [row3-start] "carousel" 5fr [row3-end] + [row4-start] "pagination" 1fr [row4-end] + [row5-start] "submit" 1fr [row5-end] + / minmax(200px,${FN_DESCRIPTOR_DEFAULT_HEIGHT}); + + justify-content: center; + ` + : `grid: + [row1-start] "filters filters filters" 1.5fr [row1-end] + [row2-start] "pagination carousel picker" 8fr [row2-end] + / 1fr minmax(200px,${FN_DESCRIPTOR_DEFAULT_HEIGHT}) 1fr; + ` + } + `}; +`; + +const FiltersContainer = styled.div` + justify-self: center; + color: ${({ theme }) => theme.content}; +`; + +const PaginationContainer = styled.div` + display: flex; + align-items: center; + gap: ${1 * GU}px; +`; + +const PaginationIcon = styled.img<{ size: number }>` + ${({ size }) => `width: ${size}px; height: ${size}px;`}; + color: ${({ theme }) => theme.border}; +`; + +const CarouselContainer = styled.div` + min-width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; +`; + +const FunctionsPickerContainer = styled.div` + justify-self: end; +`; + +const SubmitContainer = styled.div<{ compactMode: boolean }>` + ${({ compactMode }) => + compactMode + ? ` + width: 100%; + ` + : ` + width: 230px; + position: fixed; + bottom: ${3 * GU}px; + right: ${5 * GU}px; + `}; +`; + +const InnerSubmitButton = styled(Button)` + box-sizing: border-box; + padding: ${3 * GU}px; + ${({ wide }) => wide && "width: 100%;"}; +`; + +const EmptyContainer = styled.div` + color: ${({ theme }) => theme.surfaceContent}; +`; diff --git a/app/components/ContractDescriptorScreen/use-contract-descriptor-store.ts b/app/components/ContractDescriptorScreen/use-contract-descriptor-store.ts new file mode 100644 index 0000000..af60514 --- /dev/null +++ b/app/components/ContractDescriptorScreen/use-contract-descriptor-store.ts @@ -0,0 +1,363 @@ +import { createStore } from "@udecode/zustood"; +import type { SetImmerState, SetRecord, StoreApiGet } from "@udecode/zustood"; +import { utils } from "ethers"; +import type { FunctionFragment } from "ethers/lib/utils"; +import { FnDescriptionStatus } from "~/types"; +import type { ValueOrArray } from "~/types"; +import type { ContractData, FnEntry } from "~/types"; +import { getFnSelector } from "~/utils"; +import { getDefaultParamValues } from "~/utils/client/param-value.client"; +import type { FieldParamValue } from "~/utils/client/param-value.client"; +import { accessMultidimensionalArray } from "~/utils/client/utils.client"; + +export type FnDescriptorEntry = { + fullName: string; + sigHash: string; + entry?: FnEntry; +}; + +export type UserFnDescription = { + sigHash: string; + description: string; +}; + +export type TestingParam = ValueOrArray; + +type ContractDescriptorState = { + contractAddress: string; + contractNetworkId: number; + fnSelected: number; + fnDescriptorEntries: FnDescriptorEntry[]; + filteredFnDescriptorEntries: FnDescriptorEntry[]; + userFnDescriptions: Record; // Use an object to index descriptions to facilitate state updates at the expense of having some function data duplication (sigHash, minimalName, etc) + readyToFocus: boolean; + lastCaretPos: number; + filters: Record; + fnsTestingParams: Record; +}; + +export const parseNestingPos = (id: string): number[] => + id.split(".").map((i) => Number(i)); + +const initialState: ContractDescriptorState = { + contractAddress: "", + contractNetworkId: -1, + fnSelected: 0, + fnDescriptorEntries: [], + filteredFnDescriptorEntries: [], + userFnDescriptions: {}, + readyToFocus: false, + lastCaretPos: 0, + filters: { + added: false, + available: true, + }, + fnsTestingParams: {}, +}; + +const contractDescriptorStore = createStore("contract-descriptor")( + initialState, + { + devtools: { enabled: process.env.NODE_ENV === "development" }, + } +) + .extendSelectors((_, get) => ({ + fnDescriptionsCounter: () => Object.keys(get.userFnDescriptions()).length, + currentFnDescriptorEntry: (): FnDescriptorEntry => + get.filteredFnDescriptorEntries()[get.fnSelected()], + currentDescription: (): string => { + const descriptorEntry = + get.filteredFnDescriptorEntries()[get.fnSelected()]; + const userDescription = get.userFnDescriptions()[descriptorEntry.sigHash]; + + return ( + userDescription?.description ?? descriptorEntry.entry?.notice ?? "" + ); + }, + })) + .extendActions((set, get) => ({ + setUpContractDescriptorStore: ( + contractData: ContractData, + entries: FnEntry[] + ) => { + const { abi, address, network } = contractData; + const abiInterface = new utils.Interface(abi); + const fnFragments = abiInterface.fragments.filter( + (f) => f.type === "function" + ) as FunctionFragment[]; + // Only consider functions that change state + const nonConstantFnFragments = fnFragments.filter((f) => !f.constant); + const fns = nonConstantFnFragments.map((f) => { + const sigHash = getFnSelector(f); + return { + fullName: f.format("full"), + sigHash, + entry: entries?.find((e) => e.sigHash === sigHash), + }; + }); + + set.state((draft) => { + draft.contractAddress = address; + draft.contractNetworkId = network.id; + draft.fnDescriptorEntries = fns; + draft.filteredFnDescriptorEntries = fns.filter((fn) => !fn.entry); + // @ts-ignore + draft.fnsTestingParams = nonConstantFnFragments.reduce( + ( + fnsTestingParams: ContractDescriptorState["fnsTestingParams"], + f + ) => { + fnsTestingParams[getFnSelector(f)] = f.inputs.map((inp, i) => + getDefaultParamValues(inp) + ); + return fnsTestingParams; + }, + {} + ); + }); + }, + goToNextFn: () => { + const prevFnSelected = get.fnSelected(); + const fns = get.filteredFnDescriptorEntries(); + + set.fnSelected(Math.min(fns.length - 1, prevFnSelected + 1)); + }, + goToPrevFn: () => { + const prevFnSelected = get.fnSelected(); + set.fnSelected(Math.max(0, prevFnSelected - 1)); + }, + toggleFilter: (filterName: keyof ContractDescriptorState["filters"]) => { + set.state((draft) => { + draft.filters[filterName] = !draft.filters[filterName]; + draft.fnSelected = 0; + draft.filteredFnDescriptorEntries = draft.fnDescriptorEntries.filter( + (fnDescriptor) => { + const entry = fnDescriptor.entry; + const isAvailable = !entry; + + if (isAvailable) { + return draft.filters[FnDescriptionStatus.Available]; + } + + return draft.filters[entry!.status]; + } + ); + }); + }, + upsertFnDescription: (sigHash: string, description: string) => { + const userFnDescriptions = get.userFnDescriptions(); + + /** + * Don't insert a description that equals an already existing description entry. + */ + if ( + get + .fnDescriptorEntries() + .find( + (fn) => fn.sigHash === sigHash && fn.entry?.notice === description + ) + ) { + /** + * The user may edit descriptions already inserted by them and then revert the changes back to the original + * value. When this happens, we need to remove the description. + */ + if (userFnDescriptions[sigHash]) { + set.state((draft) => { + delete draft.userFnDescriptions[sigHash]; + }); + } + return; + } + + if (userFnDescriptions[sigHash]?.description === description) { + return; + } + + // Delete empty descriptions + if (!description && userFnDescriptions[sigHash]) { + set.state((draft) => { + delete draft.userFnDescriptions[sigHash]; + }); + + return; + } + + set.state((draft) => { + if (draft.userFnDescriptions[sigHash]) { + draft.userFnDescriptions[sigHash].description = description; + } else { + draft.userFnDescriptions[sigHash] = { + sigHash, + description, + }; + } + }); + }, + updateFnTestingParams: ( + sigHash: string, + fnTestingParams: TestingParam[] + ) => { + set.state((draft) => { + // Ignore excessively deep recursive type + // @ts-ignore + draft.fnsTestingParams[sigHash] = fnTestingParams; + }); + }, + updateFnTestingParam: ( + sigHash: string, + paramValues: FieldParamValue, + nestingPosition: string + ) => { + set.state((draft) => { + const indexes = parseNestingPos(nestingPosition); + let fnTestingParams: ValueOrArray | undefined; + let testingParamOrParams: ValueOrArray; + let elementIndex: keyof TestingParam; + + // For non-array param cases do the following + if (indexes.length === 1) { + fnTestingParams = draft.fnsTestingParams[sigHash]; + elementIndex = indexes[0] as keyof TestingParam; + + testingParamOrParams = fnTestingParams[elementIndex]; + } else { + const deletedIndexes = indexes.splice(-1, 1); + fnTestingParams = accessMultidimensionalArray( + draft.fnsTestingParams[sigHash], + indexes + ); + elementIndex = deletedIndexes[0] as keyof TestingParam; + + if (!fnTestingParams || !Array.isArray(fnTestingParams)) { + return; + } + + testingParamOrParams = fnTestingParams[0][elementIndex]; + } + + if (Array.isArray(testingParamOrParams)) { + return; + } + + const { value, decimals } = testingParamOrParams || {}; + + // Don't update when having the same value + if (value == paramValues.value && decimals == paramValues.decimals) { + return; + } + + fnTestingParams[elementIndex] = paramValues; + }); + }, + insertFnTestingArrayParam: ( + sigHash: string, + nestingPos: string, + paramType: utils.ParamType + ) => { + set.state((draft) => { + const indexes = parseNestingPos(nestingPos); + const testingParams = accessMultidimensionalArray( + // Ignore excessively deep recursive type + // @ts-ignore + draft.fnsTestingParams[sigHash], + indexes + ); + + if (!testingParams || !Array.isArray(testingParams)) { + return; + } + + testingParams.push(getDefaultParamValues(paramType)); + }); + }, + removeFnTestingArrayParam: (sigHash: string, nestingPos: string) => { + set.state((draft) => { + const indexes = parseNestingPos(nestingPos); + const deletedIndexes = indexes.splice(-1, 1); + + let upperOneElementArray: any; + let upperOneElementArrayIndex: number = -1; + let testingParams: any = draft.fnsTestingParams[sigHash]; + + /** + * By finding and removing the upper one element array + * we avoid having empty nested arrays that we can't + * remove otherwise. + */ + for (let i = 0; i < indexes.length; i++) { + const index = indexes[i]; + + if (testingParams[index].length === 1) { + if (i > 0 && !upperOneElementArray) { + upperOneElementArray = testingParams; + upperOneElementArrayIndex = index; + } + } else { + upperOneElementArray = null; + upperOneElementArrayIndex = -1; + } + + testingParams = testingParams[index]; + } + + if (upperOneElementArray && upperOneElementArrayIndex > -1) { + upperOneElementArray.splice(upperOneElementArrayIndex, 1); + return; + } + + const elementIndex = deletedIndexes[0]; + + if (!testingParams || !Array.isArray(testingParams)) { + return; + } + + testingParams.splice(elementIndex, 1); + }); + }, + })) + .extendActions((set, get) => ({ + addHelperFunction: (fnSignature: string) => { + const currentEntry = get.currentFnDescriptorEntry(); + const currentFnDescription = get.currentDescription(); + + const fieldCaretPos = get.lastCaretPos(); + const newDescription = `${currentFnDescription.slice( + 0, + fieldCaretPos + )}\`${fnSignature}\`${currentFnDescription.slice(fieldCaretPos)}`; + + set.upsertFnDescription(currentEntry.sigHash, newDescription); + + // Wait a little bit for the description to update. + setTimeout(() => set.readyToFocus(true), 100); + setTimeout(() => set.readyToFocus(false), 100); + }, + })); + +export const useContractDescriptorStore = contractDescriptorStore.useStore; +export const selectors = contractDescriptorStore.use; +export const actions = contractDescriptorStore.set; + +export const useTestModalData = () => { + const { + contractAddress, + contractNetworkId, + fnSelected, + userFnDescriptions, + fnsTestingParams, + } = useContractDescriptorStore(); + const { entry, fullName, sigHash } = + selectors.filteredFnDescriptorEntries()[fnSelected] ?? {}; + const userDescription = userFnDescriptions[sigHash]; + const testingParams = fnsTestingParams[sigHash] ?? {}; + const description = userDescription?.description ?? entry?.notice ?? ""; + + return { + contractAddress, + contractNetworkId, + description, + fnAbi: fullName, + testingParams, + sigHash, + }; +}; diff --git a/app/components/ContractDescriptorScreen/useRosetteActions.ts b/app/components/ContractDescriptorScreen/useRosetteActions.ts new file mode 100644 index 0000000..94b5e06 --- /dev/null +++ b/app/components/ContractDescriptorScreen/useRosetteActions.ts @@ -0,0 +1,30 @@ +import { useCallback } from "react"; +import { utils } from "ethers"; +import { useContract, useSigner } from "wagmi"; +import rosetteStoneAbi from "~/abi/RosetteStone.json"; + +const GAS_LIMIT = 6000000; + +export default function useRosetteActions() { + const [{ data }] = useSigner(); + const rosetteContract = useContract({ + addressOrName: window.ENV.ROSETTE_STONE_ADDRESS, + contractInterface: rosetteStoneAbi, + signerOrProvider: data, + }); + + const upsertEntries = useCallback( + async (scopes: string[], sigs: string[], cids: string[]) => { + const tx = await rosetteContract.upsertEntries( + scopes, + sigs, + cids.map((cid) => utils.hexlify(utils.toUtf8Bytes(cid))), + { gasLimit: GAS_LIMIT } + ); + await tx.wait(); + }, + [rosetteContract] + ); + + return { upsertEntries }; +} diff --git a/app/components/ContractForm/NetworkItem.tsx b/app/components/ContractForm/NetworkItem.tsx index df456a9..b329767 100644 --- a/app/components/ContractForm/NetworkItem.tsx +++ b/app/components/ContractForm/NetworkItem.tsx @@ -1,3 +1,4 @@ +import { GU } from "@blossom-labs/rosette-ui"; import styled from "styled-components"; type NetworkItemProps = { @@ -10,20 +11,18 @@ export const NetworkItem = ({ icon, label, isTestnet = false, -}: NetworkItemProps) => { - return ( -
    - - {label} -
    - ); -}; +}: NetworkItemProps) => ( +
    + + {label} +
    +); const NetworkImg = styled.img<{ isTestnet: boolean }>` border-radius: 50%; width: 19px; height: 19px; vertical-align: -4px; - margin-right: 1gu;9 + margin-right: ${1 * GU}px; ${(props) => (props.isTestnet ? "filter: grayscale(80%);" : "")}; `; diff --git a/app/components/ContractForm/index.tsx b/app/components/ContractForm/index.tsx index f7c781c..e6be886 100644 --- a/app/components/ContractForm/index.tsx +++ b/app/components/ContractForm/index.tsx @@ -1,115 +1,70 @@ -// import { Box, Button, DropDown, Field, Info, TextInput } from "@1hive/1hive-ui"; -// import { utils } from "ether"; -// import { -// ChangeEvent, -// FormEventHandler, -// useEffect, -// useMemo, -// useState, -// } from "react"; -// import { NETWORK_IDS, NETWORKS, isTestnet } from "../../../utils"; -// import { NetworkItem } from "./NetworkItem"; -// const DEFAULT_NETWORK_ID_INDEX = -1; +import { Field, GU, LoadingRing, TextInput } from "@blossom-labs/rosette-ui"; +import { utils } from "ethers"; +import type { ChangeEventHandler } from "react"; +import { useState } from "react"; +import styled from "styled-components"; -export const ContractForm = () =>
    ContractForm
    ; +type ContractFormProps = { + loading: boolean; + onSubmit(contractAddress: string): void; +}; -// type ContractFormProps = { -// onSubmit(contractAddress: string, networkId: number): void; -// }; +export const ContractForm = ({ loading, onSubmit }: ContractFormProps) => { + const [contractAddress, setContractAddress] = useState(""); + const [errorMsg, setErrorMsg] = useState(""); + const errorExists = !!errorMsg.length; -// const ContractForm = ({ onSubmit }: ContractFormProps) => { -// const [contractAddress, setContractAddress] = useState(""); -// const [networkIdIndex, setNetworkIdIndex] = useState( -// DEFAULT_NETWORK_ID_INDEX -// ); -// const [errorMsg, setErrorMsg] = useState(""); -// const networkItems = useMemo( -// () => -// NETWORK_IDS.map((id) => ( -// -// )), -// [] -// ); -// const disableSubmit = -// networkIdIndex === DEFAULT_NETWORK_ID_INDEX || !contractAddress.length; + const handleChange: ChangeEventHandler = ({ + target: { value }, + }) => { + setContractAddress(value); + setErrorMsg(""); -// useEffect(() => { -// setErrorMsg(""); -// }, [contractAddress, networkIdIndex]); + if (!value.length) { + return; + } -// const handleSubmit: FormEventHandler = (e) => { -// e.preventDefault(); + if (!utils.isAddress(value)) { + setErrorMsg("Invalid contract address"); + return; + } -// if ( -// !contractAddress || -// !utils.isAddress(contractAddress) || -// networkIdIndex === DEFAULT_NETWORK_ID_INDEX -// ) { -// setErrorMsg("Invalid contract address."); -// return; -// } + onSubmit(value); + }; -// onSubmit(contractAddress, NETWORK_IDS[networkIdIndex]); -// }; + return ( + +
    + + : null} + adornmentPosition="end" + adornmentSettings={{ + width: 70, + padding: 15, + }} + value={contractAddress} + placeholder="0x…" + onChange={handleChange} + error={errorExists} + disabled={loading} + size="medium" + wide + /> + +
    +
    + ); +}; -// return ( -// -//
    -//
    -//
    -// -// ) => { -// setContractAddress(e.target.value); -// }} -// wide -// /> -// -// -// -// -// {errorMsg && {errorMsg}} -//
    -//
    -//
    -// ); -// }; - -// export default ContractForm; +const Container = styled.div` + width: 100%; + max-width: 440px; + margin: 0 ${3 * GU}px; + display: flex; + flex-direction: column; +`; diff --git a/app/components/ContractSelectorScreen/ContractItem.tsx b/app/components/ContractSelectorScreen/ContractItem.tsx new file mode 100644 index 0000000..c110138 --- /dev/null +++ b/app/components/ContractSelectorScreen/ContractItem.tsx @@ -0,0 +1,199 @@ +import { + GU, + LoadingRing, + Switch, + Tag, + textStyle, +} from "@blossom-labs/rosette-ui"; +import type { MouseEventHandler } from "react"; +import { useEffect, useState } from "react"; +import { a, useTransition } from "@react-spring/web"; +import styled from "styled-components"; +import type { ContractData, AggregatedContract } from "~/types"; +import { getNetworkLogo } from "~/utils/client/icons.client"; + +type ContractItemProps = { + contract: AggregatedContract; + loaderText?: string; + onClick(contractData: ContractData): void; +}; + +export const ContractItem = ({ + contract: { proxy, implementation }, + loaderText = "Fetching data…", + onClick, +}: ContractItemProps) => { + const [itemClicked, setItemClicked] = useState(false); + const [displayProxy, setDisplayProxy] = useState(!implementation); + const contractData = displayProxy ? proxy : implementation; + const { address, name, network } = contractData!; + const implementationFound = !!implementation; + const displaySwitch = !!proxy && !!implementation; + + const clickTransition = useTransition(itemClicked, { + from: { + loaderOpacity: 0, + }, + enter: { + loaderOpacity: 1, + }, + }); + + useEffect(() => { + if (!itemClicked || !contractData) { + return; + } + + /** + * Wait a little bit before handling click to display loader + * for a period of time. + */ + let timeoutId = setTimeout(() => onClick(contractData), 500); + + return () => { + clearTimeout(timeoutId); + }; + }, [contractData, itemClicked, onClick]); + + const handleViewClick: MouseEventHandler = (e) => { + e.stopPropagation(); + + setDisplayProxy((prev) => !prev); + }; + + return ( + { + setItemClicked(true); + }} + isClicked={itemClicked} + > + {clickTransition(({ loaderOpacity }, clicked) => ( + <> + + + + } + /> + {displaySwitch && ( + <> + + Show proxy:{" "} +
    + +
    +
    + + )} +
    + +
    {name}
    +
    {address}
    +
    + {!implementationFound && ( + *Implementation source code not found. + )} +
    + {clicked && ( + + {loaderText} + + )} + + ))} +
    + ); +}; + +const Wrapper = styled.div<{ isClicked: boolean }>` + position: relative; + padding: ${2 * GU}px; + background-color: ${({ theme }) => theme.surface.alpha(0.5)}; + border-radius: 10px; + border: 1px solid ${({ theme }) => theme.borderDark}; + transition: background-color ease-out 200ms; + min-width: 45vmin; + max-width: 85vmin; + cursor: pointer; + + ${({ isClicked, theme }) => + !isClicked + ? `&:hover { + background-color: ${theme.surfaceUnder.alpha(0.1)}; + }` + : ""}; +`; + +const ItemHeader = styled.div` + display: flex; + justify-content: space-between; + margin-bottom: ${1.5 * GU}px; +`; + +const ItemContent = styled.div` + & > div:nth-child(1) { + ${textStyle("body1")}; + color: ${({ theme }) => theme.content}; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + & > div:nth-child(2) { + ${textStyle("body2")}; + color: ${({ theme }) => theme.contentSecondary}; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } +`; + +const InfoError = styled.span` + color: ${({ theme }) => theme.negative}; + margin-top: ${1 * GU}px; + ${textStyle("body4")}; +`; + +const ProxySwitchWrapper = styled.div` + display: flex; + gap: ${0.5 * GU}px; + color: ${({ theme }) => theme.contentSecondary}; + ${textStyle("body3")}; + + & > div { + display: flex; + align-items: center; + } +`; + +const Loader = styled(a.div)` + width: 100%; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + display: flex; + justify-content: center; + align-items: center; + flex-wrap: nowrap; + gap: ${1 * GU}px; + ${textStyle("body2")}; + color: ${({ theme }) => theme.contentSecondary}; +`; + +const NetworkImg = styled.img<{ isTestnet: boolean }>` + border-radius: 50%; + width: 20px; + height: 20px; + margin-left: -${1.2 * GU}px; + margin-right: ${0.5 * GU}px; + ${(props) => (props.isTestnet ? "filter: grayscale(80%);" : "")}; +`; diff --git a/app/components/ContractSelectorScreen/index.tsx b/app/components/ContractSelectorScreen/index.tsx new file mode 100644 index 0000000..e57a71d --- /dev/null +++ b/app/components/ContractSelectorScreen/index.tsx @@ -0,0 +1,78 @@ +import { Button, GU, textStyle } from "@blossom-labs/rosette-ui"; +import styled from "styled-components"; +import { a, useTransition } from "@react-spring/web"; +import type { ContractData, AggregatedContract } from "~/types"; +import { ContractItem } from "./ContractItem"; +import { useNavigate } from "@remix-run/react"; + +type ContractSelectorScreenProps = { + contracts: AggregatedContract[]; + loaderText?: string; + onContractDataSelected(contractData: ContractData): void; +}; + +export const ContractSelectorScreen = ({ + contracts, + loaderText, + onContractDataSelected, +}: ContractSelectorScreenProps) => { + const navigate = useNavigate(); + const transition = useTransition(contracts, { + trail: 700 / contracts.length, + from: { opacity: 0, scale: 0 }, + enter: { opacity: 1, scale: 1 }, + }); + + return ( + + {contracts.length ? ( + <> +
    The following contracts have been found:
    + {transition((styles, item) => ( + + + + ))} + + ) : ( + +
    No contract found.
    +