Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Feat/web app #4

Merged
merged 111 commits into from
Mar 7, 2024
Merged

Feat/web app #4

merged 111 commits into from
Mar 7, 2024

Conversation

sayandcode
Copy link
Owner

@sayandcode sayandcode commented Mar 7, 2024

What?

We added a web app frontend for the staking smart contract

Why?

It helps users easily interact with, and understand how staking works in Finthetix.

How?

It uses

  • Remix-React for the server
  • Redux for global state
  • Shadcn-UI and Tailwind for styles
  • Ethers and TypeChain for contract interaction and type-safe ABI's
  • Typescript and Zod for static typesafety
  • ESLint for linting

Note: We use Redux instead of cookies (like Remix suggests), as we can't load the user data unless on the client (needs wallet signer). So there's no point to putting user data in cookies, as we can't do anything with it on server. This also allows us to cache pages on CDN.

Screenshots

image
image
image
image
image

Smart Contract Changes

Events

We changed the structure of Events in smart contracts, to more closely match our needs in the frontend dashboard. This means unified events for Staked amount changes, and Reward Balance updates.

Deployer script

We added a deployer script, which is necessary to bootstrap the environment before running the web app locally for development. We added a convenience make command to deploy as well:

make deploy-dev

This script was also utilized in the smart contract testing, in order to validate that the script works. As a result we have had to bootstrap the .env file in CI testing (github actions) as well

These can be revealed at any time using `npx remix reveal`
A fontsource package was used so that we self host the font
The root route was added, and the theme was customized with css
- Ethers.js library was added and the chain + wallet linking logic was
  added. The failure paths were handled with toast messages. Success paths
  are yet to be handled.

- A couple of utils were added: The `TrialResult` type, the `tryItAsync`
  function, as well as an error code library for UI errors.

- \*.dev.\* files were added to gitignore
- A POST request is submitted to /login. This sets the active address in
  a cookie, helping us
  - show the address in the navbar on first render
  - redirect to dashboard for already signed in users navigating to root
    route (/)

- Set up env variables for cookie secrets
The env variables are parsed with zod and made available for use on
import
Apart from this, the error returned from '/login' API was modified to
conform to the format expected by the error boundary. Namely the error
data was returned as a single string instead of an object. We can log
additional details separately in our logger. This is only for client
facing purposes
Storing the client address on cookie prevents caching the page. So we
moved it to localStorage
- This context is made available globally. Since users are likely to
  login from and access user info from every page.

- The fetching of chain info was moved to the root loader. This needs to
  be cached in order to prevent refetching data that doesn't change so
  often (chain info)
- Earlier, the user address was fetched from localStorage automatically.
  This is a good first step, but now we go one step further and try to
  automatically fetch active address from metamask, and update it in-app.

- Memoization was added to AuthContext value

-  noUncheckedIndexedAccess was enabled to make sure we typecheck on
  indexing arrays. The associated tweaks were also made.
…ading

- Add chain switching request to login flow: This allows us to explicitly
  fail if user rejects chain switch.

- Fix perpetual loading states on login: If users were to fail the login
  process from metamask, we need to kill the loading state.

- Prevent auto-login on explicit logout: If users explicitly log out,
  signalled by lack of stored user in localStorage, then we don't need to
  attempt auto login, regardless of whether the metamask is already linked
The style of loading spinner was also fixed
- Cards were added with dummy data

- The color of accent css variable was made darker, so that buttons are
  darker on hover
This was also used in the tests to deploy the contracts
- Dapp info is loaded via env variables, parsed and checked to ensure
  integrity, and provided in the root loader to be available throughout
  the app.

- The ChainInfo for dev environment was moved to a constants folder
- A useEffect was added, in which we use the dappInfo (provided by root
  loader) to interact with the smart contract and fetch the user's data.
  So far only the user staked amount was fetched.

- The root loader's path was exported from that `root.tsx` file, to
  eliminate arbitrary string literals when being used in `useRouteLoaderData`

- A dedicated `FinthetixStakingContractHandler` class was added to wrap
  the contract methods and to contain ABI etc. This is used to fetch the
  user data
Unexpected should be handled gracefully and not shown to the client.
Hence, non-route-errors were replaced with a generic error message
The associated scripts were also added
… class

FinthetixStakingContract now uses the factory generated by typechain
to infer the abi. This method works even though the factory is not committed
because we always run the generator on `postinstall`. Any type errors
are automatically caught in the build step.
…ction

- Earlier the metamask request was highly coupled to the state functions.
  Now it has been abstracted to `requestMetamaskAddress`

- The `UserInCookie` type was exported for reuse globally
- Now the loader still runs even when user is logged in. This prolongs
  the loading state to allow for redirect after user is logged in.

- The comment in dashboard page useEffect was modified to be more clear.
  The useEffect is only intended to run when the loading is finished,
  since only then can we fetch the data for the loaded user.
The dependencies of AuthContext like Metamask and localStorage interaction
functions were moved into separate colocated files.
Now this can be used to fetch the loader data from root route, in a
typesafe manner
…point

This data is reused in many places, so makes sense to move into its own
endpoint. This has the added benefit of reducing data over the wire
- All data fetching on /dashboard page can happen only after the user is
  logged in. So instead of implementing guard checks for this within each
  component, and fetching at each component, we fetch at the closest
  common ancestor, which is the /dashboard page `route.tsx` component,
  and pass it down

- A separate flag for loading state was eliminated. We simply nullify the
  data when new data is loading. So far we don't have a use case where
  the existing data should be sustained, while the new data is being
  fetched. We are okay with blowing away the old data as soon as we know
  it's stale.

- We also removed the condition where data is fetched as undefined. This
  is not an expected case. We are always expecting some kind of data from
  the backend, even if it is symbolically empty (like an empty array)
- Since this is user agnostic, we benefit from fetching it as early as
  possible. Hence the RTK Query endpoint was removed, and the fetching
  was moved to the server side

- In order to fetch on the server side, where no signer exists, we needed
  to extend our `FinthetixStakingContractHandler` abstraction.

  - For this a `Base` class was created, which contains the logic for
    handling all the primitive contract handlers.

  - We created a new readonly abstraction
    `ReadonlyFinthetixStakingContractHandler` which extends this base
    class. This is intended to be used on the server side, or wherever
    user signer is not available(or necessary).

  - The `FinthetixStakingContractHandler` was also made to be based off
    of the `Base` class

- Since we fetch the `dappInfo` and `chainInfo` on multiple pages now,
  we moved that into its own function as well, so that it can be reused
  between loaders. This is necessary since loaders are run in parallel,
  and data cannot be shared between loaders.
… eslint errors

- The renaming of `get` to `fetch` makes more narrative sense, as we never
  extract the data directly from the `get`-prefixed function. Rather the
  `get` function acts like a trigger. So we made this cosmetic adjustment

- Eslint errors in some config files were fixed
…tixStakingContractHandler

References to `super` instance variables were returning undefined. So for
simplicity, all `super` referenes were converted to `this`
…ching

- The data is now fetched in parallel wherever possible

- Also, the current block timestamp logic was slightly changed to be less
  verbose
- When user changes their active account on metamask, we automatically
  try to login and fetch info for the new account

- All the auto-login logic was moved to a single `AutoLogin` component.
  This helps colocate the auto-login from localStorage as well as the
  auto-login when accounts are switched.

- A dedicated global state variable was added to the `User` slice for
  signalling whether the data is from localStorage or not. This makes it
  more explicit and avoids overlapping resposibilities of the `isLoading`
  flag. Selectors were also added for the same

- The metamask handler class was extended to expose the `window.ethereum`
  object. This makes sure we go through the required guard checks and
  error handling before using window.ethereum.

- We use dedicated `isUserLoggedIn` selector in index page. This is a
  redux optimization to minimize rerenders.
…cache

The query hook triggers are now prefixed with `trigger` to make it more
explicit. Also the `preferCache` setting was explicitly enabled for these
triggers on dashboard page.
This abstracts away the complexity, reducing cognitive load on developer
- The navbar was made to be sticky, with a blur and shadow that appears
  on scroll. We use `IntersectionObserver` API inside a custom hook for
  this.

- The cards were made to have strong borders and in some places rise on
  hover, similar to the codecademy website
This adds to the user experience
Forward looking change in anticipation of more sections being added to
homepage
- Some info as well as useful links were added. Right now the links
  are hardcoded dummy links

- Some styling for the RippleHeroBg was modified so that
  - The container does not overflow viewport
  - The animation is continuous(infinite) on larger viewports
- Credits section includes developer info

- The Github Icon was moved to a separate component, and an X Icon was
  added

- Github and Developer contact URLs were added and sourced from the
  constants.ts folder
- Earlier when we made the intersection observerscroll trigger into a
  custom hook, we forgot to pass the dependencies array. Now this was done

- Also, the navbar was made thinner for aesthetic purposes
- Earlier this was hardcoded. The metadata fetch was also moved to the
  root route, as this is used on multiple routes.

- The metadata was extended to contain info on staking reward rate.
  Accordingly, all references to `FinthetixMetadata` were updated.
…nts`

This makes the distinction between the generic type and the utility
function more clear
- When users stake/unstake/withdraw-rewards, we show them the link to the
  block explorer as well, so that they can independently verify their
  transaction details.

- The styling of the toast component was modified to wrap words and thus
  show the whole contents always
We check if the contract is cooling down, before trying to interact with
it.  This saves us gas by preventing reverts, and also improves the UX.

This is currently done only in a guard check format. This needs to be
displayed prominently on the user's dashboard as well, and also needs to
be handled better by the frontend
- We need to prevent the user from interacting when the Staking contract
  is cooling down.

- Warnings were added for this purpose. This contains a time counter
  which updates every second based on how much time is left for cooldown.
  The cooldown time is currently hardcoded, and needs to be fetched from
  backend. A new variant was added to the alert component, denoting warning.

- Interaction buttons were disabled when cooling down. For this a new
  `disabled` prop was added to the relevant components.

- In order to avoid rerenders, the subcomponents of the dashboard page
  were memoized using React.memo. So now these don't rerender until the
  props change, which is either user data change, or the enabling of
  interaction post cooldown
The cooldown time is fetched as part of the `Finthetix Status`, from the
contract via RTK Query endpoint. It is refreshed whenever user tries some
interaction. Good thing to note is that if the cooldown check fails when
user tries to interact, that interaction will also trigger a refetch of
`FinthetixStatus`, causing the _Cooling Down_ banner to be shown correctly.
…actHandler`

- This logic is more related to the staking contract than to the RTK query
  endpoint. So we moved it accordingly.

- The contract handler methods were modified so that we can use the
  cooldown time separate from the overall status. This optimizes data
  over the wire for that specific method. For eg. when staking, we only
  need to know whether contract is cooling down. We don't need other
  status data.
The text was allowed to break freely, in an earlier change. This is being
reverted now. The reasons are:
  - We only wanted to break longer words like the ETH address so that
    they are not obfuscated. But shorter words were also being broken up
    as a result of the previous change

  - The ETH address was only being broken up for unrealistic viewport
    sizes. So we don't have to handle this condition in our styling
- ChainInfo is now loaded in via env variables, for production server as
  well.  It is parsed and validated at the time of server start, and read
  by the server when requested by user.

- ChainInfo and DappInfo were moved into their own files, with the types,
  schema and fetchers colocated. This helps reduce cognitive overload in
  a single file.

- Minor style change was made to homepage as well, wherein the `HowItWorks`
  section was given a top margin, so that it is not visible along with
  the hero section, when page first loads (especially prominent on mobile
  sizes).
- Earlier the `Navbar` never actually unmounted; it only stopped rendering
  the child components. As a result the sticky state was persisting
  across apparent unmounts, causing the `Navbar` to show sticky style
  even when it was not stuck. To fix this, we actually unmount the entire
  `Navbar` component, which also resets the sticky state kept in JS

- Also, the _Interaction observer_ was disconnected properly in the
  useEffect unmount. This was forgotten earlier, leading to multiple
  interaction observers.

- The type in chainInfo was fixed to more closely match the zod schema.
  Now the type conveys the info that there will always be at least one
  url.
This is required for testing the deployer script, which is used in tests
@sayandcode sayandcode merged commit 3e34a58 into main Mar 7, 2024
0 of 2 checks passed
@sayandcode sayandcode deleted the feat/web-app-with-redux branch March 29, 2024 10:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant