├── .eslintignore
├── .eslintrc
├── .gitignore
├── .lintstagedrc
├── .prettierignore
├── .prettierrc
├── .vscode
└── settings.json
├── README.md
├── index.html
├── netlify.toml
├── package.json
├── public
├── favicon.png
└── social-card.png
├── src
├── App.tsx
├── assets
│ ├── fonts
│ │ ├── 3616Grammastile-Regular.woff2
│ │ ├── DecimaMonoPro-Bold.woff2
│ │ ├── DecimaMonoPro-BoldItalic.woff2
│ │ ├── DecimaMonoPro-Italic.woff2
│ │ ├── DecimaMonoPro.woff2
│ │ ├── DecimaMonoProLt-Italic.woff2
│ │ └── DecimaMonoProLt.woff2
│ └── images
│ │ ├── allow-any-claimer-selected.png
│ │ ├── allow-any-claimer.png
│ │ ├── allow-any-collection-selected.png
│ │ ├── allow-any-collection.png
│ │ ├── allow-any-infuser-selected.png
│ │ ├── allow-any-infuser.png
│ │ ├── allow-specific-addresses-selected.png
│ │ ├── allow-specific-addresses.png
│ │ ├── allow-specific-collections-selected.png
│ │ ├── allow-specific-collections.png
│ │ ├── card-back.png
│ │ ├── claim-header-flourish.svg
│ │ ├── claim-tokens-border-pattern-horizontal.png
│ │ ├── claim-tokens-border-pattern-vertical.png
│ │ ├── claim-tokens.png
│ │ ├── create-realm-border-pattern-horizontal.png
│ │ ├── create-realm-border-pattern-vertical.png
│ │ ├── create-realm.png
│ │ ├── crosshair.svg
│ │ ├── dust-bg.png
│ │ ├── explore-realms-hr.svg
│ │ ├── fallback.png
│ │ ├── faq-tile.svg
│ │ ├── flag.svg
│ │ ├── headings
│ │ ├── choose-your-path.svg
│ │ ├── claim-tokens.svg
│ │ ├── create-your-realm.svg
│ │ ├── explore-realms.svg
│ │ ├── infuse-token.svg
│ │ ├── select-collection.svg
│ │ ├── select-collections.svg
│ │ ├── select-nft.svg
│ │ ├── select-realm.svg
│ │ ├── set-up-claiming.svg
│ │ └── set-up-infusion.svg
│ │ ├── icons
│ │ ├── arrow-left.svg
│ │ ├── arrow-right.svg
│ │ ├── discord.svg
│ │ ├── github.svg
│ │ ├── plus.svg
│ │ ├── step-1.svg
│ │ ├── step-2.svg
│ │ ├── step-3.svg
│ │ ├── step-4.svg
│ │ └── twitter.svg
│ │ ├── infuse-nft-border-pattern-horizontal.png
│ │ ├── infuse-nft-border-pattern-vertical.png
│ │ ├── infuse-nft.png
│ │ ├── infusion-header-flourish.svg
│ │ ├── lab-bg.png
│ │ ├── logo.svg
│ │ ├── metamask.png
│ │ ├── plus.svg
│ │ ├── star.svg
│ │ ├── submit-button-bg-lg.png
│ │ ├── submit-button-bg.png
│ │ └── swirl-bg.png
├── components
│ ├── AddressInput.tsx
│ ├── AppErrorMessage.tsx
│ ├── BackButton.tsx
│ ├── BannerPageHeading.tsx
│ ├── Button.tsx
│ ├── ButtonGroup.tsx
│ ├── Card.tsx
│ ├── CenteredContent.tsx
│ ├── ClaimTokensContainer.tsx
│ ├── CollectionCard.tsx
│ ├── CollectionList.tsx
│ ├── ConnectWallet.tsx
│ ├── ConnectWalletInline.tsx
│ ├── CreateRealmContainer.tsx
│ ├── DecimalNumber.tsx
│ ├── EmptyState.tsx
│ ├── FormErrors.tsx
│ ├── FormFieldErrorMessage.tsx
│ ├── FormHeading.tsx
│ ├── FormSteps.tsx
│ ├── GlobalStyle.tsx
│ ├── Header.tsx
│ ├── InfuseNftContainer.tsx
│ ├── InfusionTicker.tsx
│ ├── Input.tsx
│ ├── InputGroup.tsx
│ ├── Label.tsx
│ ├── Modal.tsx
│ ├── MultiAddressInput.tsx
│ ├── Nav.tsx
│ ├── Nft.tsx
│ ├── NftCard.tsx
│ ├── NftGalleryCard.tsx
│ ├── NumberInput.tsx
│ ├── RadioButton.tsx
│ ├── RadioButtonCard.tsx
│ ├── RadioGroup.tsx
│ ├── RealmCard.tsx
│ ├── RealmList.tsx
│ ├── ScrollToTop.tsx
│ ├── SocialLinks.tsx
│ ├── SubmitButton.tsx
│ ├── Switch.tsx
│ ├── SwitchGroup.tsx
│ ├── TextInput.tsx
│ └── WalletModal.tsx
├── constants
│ ├── abis
│ │ ├── ERC20.json
│ │ ├── ERC721.json
│ │ └── HyperVIBES.json
│ ├── contracts.ts
│ ├── formSteps.ts
│ ├── rpc.ts
│ └── wallets.ts
├── hooks
│ ├── useAutoConnect.ts
│ ├── useBrowseNftDetails.ts
│ ├── useClaimTokens.ts
│ ├── useCollectionInfusions.ts
│ ├── useContract.ts
│ ├── useCreateRealmWizard.ts
│ ├── useCurrentMinedTokens.ts
│ ├── useEns.ts
│ ├── useErc20Allowance.ts
│ ├── useErc20ApproveAllowance.ts
│ ├── useErc20Contract.ts
│ ├── useErc20Decimals.ts
│ ├── useErc20TokenDetails.ts
│ ├── useErc721Contract.ts
│ ├── useErc721IsApproved.ts
│ ├── useErc721OwnerOf.ts
│ ├── useHyperVibesContract.ts
│ ├── useHyperVibesSubgraph.ts
│ ├── useInfuseNft.ts
│ ├── useListRealmNfts.ts
│ ├── useListRealms.ts
│ ├── useMetadata.ts
│ ├── useMyInfusedNfts.ts
│ ├── useMyInfusibleRealms.ts
│ ├── useMyOwnedNfts.ts
│ ├── useMyRealmAdmins.ts
│ ├── useNftDetails.ts
│ ├── useRealmCollections.ts
│ ├── useRealmDetails.ts
│ └── useRealms.ts
├── hypervibes
│ ├── abi
│ │ └── HyperVIBES.json
│ ├── constants.ts
│ ├── contract.ts
│ ├── dataloaders.ts
│ ├── ipfs.ts
│ ├── nft.ts
│ └── subgraph.ts
├── index.tsx
├── pages
│ ├── BrowseNftsPage.tsx
│ ├── BrowseRealmsPage.tsx
│ ├── ChooseYourPathPage.tsx
│ ├── ClaimTokensEnterClaimAmountPage.tsx
│ ├── ClaimTokensSelectRealmPage.tsx
│ ├── ClaimTokensSelectTokenPage.tsx
│ ├── ClaimTokensSuccessPage.tsx
│ ├── CreateRealmBasicInfoPage.tsx
│ ├── CreateRealmSelectCollectionsPage.tsx
│ ├── CreateRealmSetUpClaimingPage.tsx
│ ├── CreateRealmSetUpInfusionPage.tsx
│ ├── CreateRealmSuccessPage.tsx
│ ├── InfuseNftEnterInfusionAmountPage.tsx
│ ├── InfuseNftInputTokenPage.tsx
│ ├── InfuseNftSelectCollectionPage.tsx
│ ├── InfuseNftSelectRealmPage.tsx
│ ├── InfuseNftSelectTokenPage.tsx
│ ├── InfuseNftSuccessPage.tsx
│ ├── NftDetailPage.tsx
│ ├── NftListPage.tsx
│ ├── NotFoundPage.tsx
│ └── PlaceholderPage.tsx
├── providers
│ ├── NftProvider.tsx
│ ├── QueryProvider.tsx
│ └── WalletProvider.tsx
├── types
│ └── index.ts
├── utils
│ ├── address.ts
│ ├── contract.ts
│ ├── network.ts
│ └── object.ts
└── vite-env.d.ts
├── tsconfig.json
├── vite.config.ts
└── yarn.lock
/.eslintignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /build
3 | /.husky
4 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "extends": [
4 | "eslint:recommended",
5 | "plugin:@typescript-eslint/recommended",
6 | "plugin:import/recommended",
7 | "plugin:import/typescript",
8 | "plugin:react/recommended",
9 | "plugin:react/jsx-runtime",
10 | "plugin:react-hooks/recommended",
11 | "plugin:prettier/recommended"
12 | ],
13 | "settings": {
14 | "import/resolver": {
15 | "typescript": {}
16 | },
17 | "react": {
18 | "version": "detect"
19 | }
20 | },
21 | "rules": {
22 | "react/display-name": 0
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # dependencies
2 | /node_modules
3 |
4 | # production
5 | /build
6 |
7 | # misc
8 | .DS_Store
9 | .env.local
10 | .env.development.local
11 | .env.test.local
12 | .env.production.local
13 | .eslintcache
14 |
15 | npm-debug.log*
16 | yarn-debug.log*
17 | yarn-error.log*
18 |
--------------------------------------------------------------------------------
/.lintstagedrc:
--------------------------------------------------------------------------------
1 | {
2 | "*.ts?(x)": "eslint --cache --fix"
3 | }
4 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /build
3 | /.husky
4 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "arrowParens": "avoid"
4 | }
5 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.codeActionsOnSave": {
3 | "source.fixAll.eslint": true
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # hypervibes-frontend
2 |
3 | Frontend app for the [**HyperVIBES**](https://hypervibes.xyz) project.
4 |
5 | ## Links
6 |
7 | - [Product Brief](https://docs.google.com/document/d/1NvztqdMAyLERTPuX5uHSnq8f5G0YVRaxNsq5UaXhQEw/edit?usp=sharing)
8 | - [MVP Coordination Doc](https://docs.google.com/document/d/1dpMlzGeO4XfD6gBQoaTTXO2NxCCfA0hDYlTinJjCsfQ/edit?usp=sharing)
9 | - [Contracts repo](https://github.com/R-Group-Devs/hypervibes-contracts)
10 |
11 | ## Requirements
12 |
13 | - [Node](https://nodejs.org/en/) >=14.0.0
14 | - [Yarn](https://yarnpkg.com/) ^1.22.0
15 |
16 | ## Setup
17 |
18 | Clone the repo:
19 |
20 | ```sh
21 | $ git clone git@github.com:R-Group-Devs/hypervibes-frontend.git
22 | ```
23 |
24 | ## Development
25 |
26 | Install dependencies:
27 |
28 | ```sh
29 | $ yarn
30 | ```
31 |
32 | You'll need RPC endpoints for all networks you plan to use during local development. You can generate private RPC endpoints with [Infura](https://infura.io/) or [Alchemy](https://www.alchemy.com/), or search for public RPC endpoints to use.
33 |
34 | Add the following environment variables to a `.env.local` file:
35 |
36 | ```
37 | REACT_APP_ETHEREUM_RPC_URL=???
38 | REACT_APP_POLYGON_RPC_URL=???
39 | REACT_APP_FANTOM_RPC_URL=???
40 | REACT_APP_ARBITRUM_RPC_URL=???
41 | REACT_APP_ROPSTEN_RPC_URL=???
42 | REACT_APP_RINKEBY_RPC_URL=???
43 | REACT_APP_GOERLI_RPC_URL=???
44 | REACT_APP_MUMBAI_RPC_URL=???
45 | ```
46 |
47 | Run the app in development mode:
48 |
49 | ```sh
50 | $ yarn start
51 | ```
52 |
53 | Generate a browser-ready build artifact:
54 |
55 | ```sh
56 | $ yarn build
57 | ```
58 |
59 | Preview an app build:
60 |
61 | ```sh
62 | $ yarn serve
63 | ```
64 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | HyperVIBES
23 |
24 |
25 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/netlify.toml:
--------------------------------------------------------------------------------
1 | [[redirects]]
2 | from = "/*"
3 | to = "/index.html"
4 | status = 200
5 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hypervibes-frontend",
3 | "version": "0.1.0",
4 | "description": "Frontend app for the HyperVIBES project.",
5 | "license": "UNLICENSED",
6 | "scripts": {
7 | "build": "tsc && vite build",
8 | "prepare": "git config --unset core.hooksPath; pro-commit",
9 | "pro-commit:task": "lint-staged",
10 | "serve": "vite preview",
11 | "start": "vite"
12 | },
13 | "dependencies": {
14 | "@ethersproject/address": "^5.5.0",
15 | "@ethersproject/bignumber": "^5.5.0",
16 | "@ethersproject/contracts": "^5.4.1",
17 | "@ethersproject/providers": "^5.4.5",
18 | "@hookform/devtools": "^4.0.1",
19 | "dataloader": "^2.0.0",
20 | "ethers": "^5.5.1",
21 | "ethers-multicall": "0.2.1",
22 | "get-ens": "^2.0.1",
23 | "graphql": "^15.7.0",
24 | "graphql-request": "^3.6.1",
25 | "lodash": "4.17.21",
26 | "p-queue": "7.1.0",
27 | "react": "^17.0.2",
28 | "react-copy-to-clipboard": "^5.0.4",
29 | "react-dom": "^17.0.2",
30 | "react-error-boundary": "^3.1.3",
31 | "react-helmet": "^6.1.0",
32 | "react-hook-form": "^7.17.5",
33 | "react-hooks-global-state": "^1.0.2",
34 | "react-is": "^17.0.2",
35 | "react-query": "^3.28.0",
36 | "react-router-dom": "^5.3.0",
37 | "react-spring": "^9.3.0",
38 | "react-useportal": "^1.0.14",
39 | "styled-components": "^5.3.1",
40 | "use-nft": "^0.10.1",
41 | "use-wallet": "^0.12.3"
42 | },
43 | "devDependencies": {
44 | "@types/react": "^17.0.0",
45 | "@types/react-copy-to-clipboard": "^5.0.2",
46 | "@types/react-dom": "^17.0.0",
47 | "@types/react-helmet": "^6.1.4",
48 | "@types/react-router-dom": "^5.3.1",
49 | "@types/styled-components": "^5.1.15",
50 | "@typescript-eslint/eslint-plugin": "^5.3.0",
51 | "@typescript-eslint/parser": "^5.3.0",
52 | "@vitejs/plugin-react": "^1.0.7",
53 | "eslint": "^8.1.0",
54 | "eslint-config-prettier": "^8.3.0",
55 | "eslint-import-resolver-typescript": "^2.5.0",
56 | "eslint-plugin-import": "^2.25.2",
57 | "eslint-plugin-prettier": "^4.0.0",
58 | "eslint-plugin-react": "^7.26.1",
59 | "eslint-plugin-react-hooks": "^4.3.0",
60 | "lint-staged": "^11.2.6",
61 | "prettier": "^2.4.1",
62 | "pro-commit": "^1.2.2",
63 | "typescript": "^4.4.4",
64 | "vite": "^2.6.13"
65 | },
66 | "engines": {
67 | "node": ">=14.0.0",
68 | "yarn": "^1.22.0"
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/public/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/R-Group-Devs/hypervibes-frontend/03bb26092709b8e2243b17f0e4821a37b9481f4c/public/favicon.png
--------------------------------------------------------------------------------
/public/social-card.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/R-Group-Devs/hypervibes-frontend/03bb26092709b8e2243b17f0e4821a37b9481f4c/public/social-card.png
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | BrowserRouter as Router,
3 | Switch,
4 | Route,
5 | Redirect,
6 | } from 'react-router-dom';
7 | import { ErrorBoundary } from 'react-error-boundary';
8 | import styled from 'styled-components';
9 | import WalletProvider from './providers/WalletProvider';
10 | import QueryProvider from './providers/QueryProvider';
11 | import NftProvider from './providers/NftProvider';
12 | import ScrollToTop from './components/ScrollToTop';
13 | import PlaceholderPage from './pages/PlaceholderPage';
14 | import ChooseYourPathPage from './pages/ChooseYourPathPage';
15 | import CreateRealmBasicInfoPage from './pages/CreateRealmBasicInfoPage';
16 | import CreateRealmSelectCollectionsPage from './pages/CreateRealmSelectCollectionsPage';
17 | import CreateRealmSetUpInfusionPage from './pages/CreateRealmSetUpInfusionPage';
18 | import CreateRealmSetUpClaimingPage from './pages/CreateRealmSetUpClaimingPage';
19 | import CreateRealmSuccessPage from './pages/CreateRealmSuccessPage';
20 | import InfuseNftSelectRealmPage from './pages/InfuseNftSelectRealmPage';
21 | import InfuseNftSelectCollectionPage from './pages/InfuseNftSelectCollectionPage';
22 | import InfuseNftSelectTokenPage from './pages/InfuseNftSelectTokenPage';
23 | import InfuseNftInputTokenPage from './pages/InfuseNftInputTokenPage';
24 | import InfuseNftEnterInfusionAmountPage from './pages/InfuseNftEnterInfusionAmountPage';
25 | import ClaimTokensSelectRealmPage from './pages/ClaimTokensSelectRealmPage';
26 | import ClaimTokensSelectTokenPage from './pages/ClaimTokensSelectTokenPage';
27 | import ClaimTokensEnterClaimAmountPage from './pages/ClaimTokensEnterClaimAmountPage';
28 | import NftListPage from './pages/NftListPage';
29 | import NotFoundPage from './pages/NotFoundPage';
30 | import GlobalStyle from './components/GlobalStyle';
31 | import Header from './components/Header';
32 | import SocialLinks from './components/SocialLinks';
33 | import AppErrorMessage from './components/AppErrorMessage';
34 | import BrowseRealmsPage from './pages/BrowseRealmsPage';
35 | import BrowseNftsPage from './pages/BrowseNftsPage';
36 | import NftDetailPage from './pages/NftDetailPage';
37 |
38 | const AppContainer = styled.div`
39 | display: flex;
40 | margin: 0 auto;
41 | padding: 10em 0;
42 | max-width: 1200px;
43 | height: 100%;
44 | flex-direction: column;
45 | align-items: center;
46 | `;
47 |
48 | export default () => (
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | }>
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 | );
157 |
--------------------------------------------------------------------------------
/src/assets/fonts/3616Grammastile-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/R-Group-Devs/hypervibes-frontend/03bb26092709b8e2243b17f0e4821a37b9481f4c/src/assets/fonts/3616Grammastile-Regular.woff2
--------------------------------------------------------------------------------
/src/assets/fonts/DecimaMonoPro-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/R-Group-Devs/hypervibes-frontend/03bb26092709b8e2243b17f0e4821a37b9481f4c/src/assets/fonts/DecimaMonoPro-Bold.woff2
--------------------------------------------------------------------------------
/src/assets/fonts/DecimaMonoPro-BoldItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/R-Group-Devs/hypervibes-frontend/03bb26092709b8e2243b17f0e4821a37b9481f4c/src/assets/fonts/DecimaMonoPro-BoldItalic.woff2
--------------------------------------------------------------------------------
/src/assets/fonts/DecimaMonoPro-Italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/R-Group-Devs/hypervibes-frontend/03bb26092709b8e2243b17f0e4821a37b9481f4c/src/assets/fonts/DecimaMonoPro-Italic.woff2
--------------------------------------------------------------------------------
/src/assets/fonts/DecimaMonoPro.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/R-Group-Devs/hypervibes-frontend/03bb26092709b8e2243b17f0e4821a37b9481f4c/src/assets/fonts/DecimaMonoPro.woff2
--------------------------------------------------------------------------------
/src/assets/fonts/DecimaMonoProLt-Italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/R-Group-Devs/hypervibes-frontend/03bb26092709b8e2243b17f0e4821a37b9481f4c/src/assets/fonts/DecimaMonoProLt-Italic.woff2
--------------------------------------------------------------------------------
/src/assets/fonts/DecimaMonoProLt.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/R-Group-Devs/hypervibes-frontend/03bb26092709b8e2243b17f0e4821a37b9481f4c/src/assets/fonts/DecimaMonoProLt.woff2
--------------------------------------------------------------------------------
/src/assets/images/allow-any-claimer-selected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/R-Group-Devs/hypervibes-frontend/03bb26092709b8e2243b17f0e4821a37b9481f4c/src/assets/images/allow-any-claimer-selected.png
--------------------------------------------------------------------------------
/src/assets/images/allow-any-claimer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/R-Group-Devs/hypervibes-frontend/03bb26092709b8e2243b17f0e4821a37b9481f4c/src/assets/images/allow-any-claimer.png
--------------------------------------------------------------------------------
/src/assets/images/allow-any-collection-selected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/R-Group-Devs/hypervibes-frontend/03bb26092709b8e2243b17f0e4821a37b9481f4c/src/assets/images/allow-any-collection-selected.png
--------------------------------------------------------------------------------
/src/assets/images/allow-any-collection.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/R-Group-Devs/hypervibes-frontend/03bb26092709b8e2243b17f0e4821a37b9481f4c/src/assets/images/allow-any-collection.png
--------------------------------------------------------------------------------
/src/assets/images/allow-any-infuser-selected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/R-Group-Devs/hypervibes-frontend/03bb26092709b8e2243b17f0e4821a37b9481f4c/src/assets/images/allow-any-infuser-selected.png
--------------------------------------------------------------------------------
/src/assets/images/allow-any-infuser.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/R-Group-Devs/hypervibes-frontend/03bb26092709b8e2243b17f0e4821a37b9481f4c/src/assets/images/allow-any-infuser.png
--------------------------------------------------------------------------------
/src/assets/images/allow-specific-addresses-selected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/R-Group-Devs/hypervibes-frontend/03bb26092709b8e2243b17f0e4821a37b9481f4c/src/assets/images/allow-specific-addresses-selected.png
--------------------------------------------------------------------------------
/src/assets/images/allow-specific-addresses.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/R-Group-Devs/hypervibes-frontend/03bb26092709b8e2243b17f0e4821a37b9481f4c/src/assets/images/allow-specific-addresses.png
--------------------------------------------------------------------------------
/src/assets/images/allow-specific-collections-selected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/R-Group-Devs/hypervibes-frontend/03bb26092709b8e2243b17f0e4821a37b9481f4c/src/assets/images/allow-specific-collections-selected.png
--------------------------------------------------------------------------------
/src/assets/images/allow-specific-collections.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/R-Group-Devs/hypervibes-frontend/03bb26092709b8e2243b17f0e4821a37b9481f4c/src/assets/images/allow-specific-collections.png
--------------------------------------------------------------------------------
/src/assets/images/card-back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/R-Group-Devs/hypervibes-frontend/03bb26092709b8e2243b17f0e4821a37b9481f4c/src/assets/images/card-back.png
--------------------------------------------------------------------------------
/src/assets/images/claim-tokens-border-pattern-horizontal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/R-Group-Devs/hypervibes-frontend/03bb26092709b8e2243b17f0e4821a37b9481f4c/src/assets/images/claim-tokens-border-pattern-horizontal.png
--------------------------------------------------------------------------------
/src/assets/images/claim-tokens-border-pattern-vertical.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/R-Group-Devs/hypervibes-frontend/03bb26092709b8e2243b17f0e4821a37b9481f4c/src/assets/images/claim-tokens-border-pattern-vertical.png
--------------------------------------------------------------------------------
/src/assets/images/claim-tokens.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/R-Group-Devs/hypervibes-frontend/03bb26092709b8e2243b17f0e4821a37b9481f4c/src/assets/images/claim-tokens.png
--------------------------------------------------------------------------------
/src/assets/images/create-realm-border-pattern-horizontal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/R-Group-Devs/hypervibes-frontend/03bb26092709b8e2243b17f0e4821a37b9481f4c/src/assets/images/create-realm-border-pattern-horizontal.png
--------------------------------------------------------------------------------
/src/assets/images/create-realm-border-pattern-vertical.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/R-Group-Devs/hypervibes-frontend/03bb26092709b8e2243b17f0e4821a37b9481f4c/src/assets/images/create-realm-border-pattern-vertical.png
--------------------------------------------------------------------------------
/src/assets/images/create-realm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/R-Group-Devs/hypervibes-frontend/03bb26092709b8e2243b17f0e4821a37b9481f4c/src/assets/images/create-realm.png
--------------------------------------------------------------------------------
/src/assets/images/crosshair.svg:
--------------------------------------------------------------------------------
1 |
20 |
--------------------------------------------------------------------------------
/src/assets/images/dust-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/R-Group-Devs/hypervibes-frontend/03bb26092709b8e2243b17f0e4821a37b9481f4c/src/assets/images/dust-bg.png
--------------------------------------------------------------------------------
/src/assets/images/explore-realms-hr.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/assets/images/fallback.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/R-Group-Devs/hypervibes-frontend/03bb26092709b8e2243b17f0e4821a37b9481f4c/src/assets/images/fallback.png
--------------------------------------------------------------------------------
/src/assets/images/flag.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/headings/select-nft.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/src/assets/images/icons/arrow-left.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/icons/arrow-right.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/icons/discord.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/icons/github.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/icons/plus.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/images/icons/step-1.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/icons/step-2.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/icons/step-3.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/icons/step-4.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/icons/twitter.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/infuse-nft-border-pattern-horizontal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/R-Group-Devs/hypervibes-frontend/03bb26092709b8e2243b17f0e4821a37b9481f4c/src/assets/images/infuse-nft-border-pattern-horizontal.png
--------------------------------------------------------------------------------
/src/assets/images/infuse-nft-border-pattern-vertical.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/R-Group-Devs/hypervibes-frontend/03bb26092709b8e2243b17f0e4821a37b9481f4c/src/assets/images/infuse-nft-border-pattern-vertical.png
--------------------------------------------------------------------------------
/src/assets/images/infuse-nft.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/R-Group-Devs/hypervibes-frontend/03bb26092709b8e2243b17f0e4821a37b9481f4c/src/assets/images/infuse-nft.png
--------------------------------------------------------------------------------
/src/assets/images/infusion-header-flourish.svg:
--------------------------------------------------------------------------------
1 |
54 |
--------------------------------------------------------------------------------
/src/assets/images/lab-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/R-Group-Devs/hypervibes-frontend/03bb26092709b8e2243b17f0e4821a37b9481f4c/src/assets/images/lab-bg.png
--------------------------------------------------------------------------------
/src/assets/images/logo.svg:
--------------------------------------------------------------------------------
1 |
20 |
--------------------------------------------------------------------------------
/src/assets/images/metamask.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/R-Group-Devs/hypervibes-frontend/03bb26092709b8e2243b17f0e4821a37b9481f4c/src/assets/images/metamask.png
--------------------------------------------------------------------------------
/src/assets/images/plus.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/assets/images/star.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/images/submit-button-bg-lg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/R-Group-Devs/hypervibes-frontend/03bb26092709b8e2243b17f0e4821a37b9481f4c/src/assets/images/submit-button-bg-lg.png
--------------------------------------------------------------------------------
/src/assets/images/submit-button-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/R-Group-Devs/hypervibes-frontend/03bb26092709b8e2243b17f0e4821a37b9481f4c/src/assets/images/submit-button-bg.png
--------------------------------------------------------------------------------
/src/assets/images/swirl-bg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/R-Group-Devs/hypervibes-frontend/03bb26092709b8e2243b17f0e4821a37b9481f4c/src/assets/images/swirl-bg.png
--------------------------------------------------------------------------------
/src/components/AddressInput.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { useFormContext } from 'react-hook-form';
3 | import { isAddress } from '@ethersproject/address';
4 | import TextInput from './TextInput';
5 | import useEns from '../hooks/useEns';
6 | import useErc20TokenDetails from '../hooks/useErc20TokenDetails';
7 |
8 | interface Props {
9 | name: string;
10 | label?: string;
11 | description?: string;
12 | required?: boolean;
13 | showTokenSymbol?: boolean;
14 | }
15 |
16 | const Container = styled.div`
17 | position: relative;
18 | display: flex;
19 | flex-direction: column;
20 | width: 100%;
21 | `;
22 |
23 | const ResolvedAddress = styled.div`
24 | position: absolute;
25 | bottom: 0.35em;
26 | font-size: 12px;
27 | `;
28 |
29 | const TokenSymbol = styled.div`
30 | position: absolute;
31 | right: 0;
32 | bottom: 2.4em;
33 | padding: 2em 0.5em 0 2em;
34 | background: #000;
35 | `;
36 |
37 | export default ({
38 | name,
39 | label,
40 | description,
41 | required,
42 | showTokenSymbol,
43 | }: Props) => {
44 | const { watch } = useFormContext();
45 | const value = watch(name);
46 | const { address } = useEns(value);
47 | const { symbol } = useErc20TokenDetails(value);
48 |
49 | return (
50 |
51 | !value || isAddress(value),
58 | }}
59 | />
60 |
61 | {showTokenSymbol && {symbol}}
62 |
63 | {address && (
64 |
65 | Resolved address: {address}
66 |
67 | )}
68 |
69 | );
70 | };
71 |
--------------------------------------------------------------------------------
/src/components/AppErrorMessage.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { Link } from 'react-router-dom';
3 | import CenteredContent from './CenteredContent';
4 |
5 | const H1 = styled.h1`
6 | margin-top: 0;
7 | margin-bottom: 0.25em;
8 | font-size: 28px;
9 | letter-spacing: 5px;
10 | `;
11 |
12 | const H2 = styled.h2`
13 | margin-bottom: 0;
14 | font-size: 16px;
15 | letter-spacing: 8px;
16 | color: rgba(255, 255, 255, 0.6);
17 | `;
18 |
19 | export default () => (
20 |
21 | something went wrong :(
22 |
23 | go back home
24 |
25 |
26 | );
27 |
--------------------------------------------------------------------------------
/src/components/BackButton.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { Link } from 'react-router-dom';
3 | import Button from './Button';
4 | import arrowLeft from '../assets/images/icons/arrow-left.svg';
5 |
6 | interface Props {
7 | path: string;
8 | }
9 |
10 | const BackButton = styled(Button)`
11 | display: flex;
12 | margin-top: 2.2em;
13 | padding-left: 0;
14 | padding-right: 0;
15 | width: 182px;
16 | background: none;
17 |
18 | &:hover:not([disabled]) {
19 | background: none;
20 | }
21 | `;
22 |
23 | const StyledLink = styled(Link)`
24 | display: flex;
25 | align-items: center;
26 | justify-content: center;
27 | width: 100%;
28 | color: #fff;
29 |
30 | &:hover {
31 | text-decoration: none;
32 | }
33 | `;
34 |
35 | const ArrowLeftIcon = styled.img`
36 | margin-top: 0.2em;
37 | margin-right: 0.5em;
38 | `;
39 |
40 | export default ({ path, ...rest }: Props) => (
41 | e.preventDefault()} {...rest}>
42 |
43 |
44 | Back
45 |
46 |
47 | );
48 |
--------------------------------------------------------------------------------
/src/components/BannerPageHeading.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import useMetadata from '../hooks/useMetadata';
3 |
4 | interface Props {
5 | name: string;
6 | tokenUri: string;
7 | }
8 |
9 | const Banner = styled.div<{ src?: string }>`
10 | position: absolute;
11 | top: 0;
12 | left: 0;
13 | width: 100%;
14 | height: 400px;
15 | background: linear-gradient(rgba(0, 0, 0, 0.75), rgba(0, 0, 0, 0.75)),
16 | url(${({ src }) => src}) center center no-repeat;
17 | background-size: cover;
18 | z-index: -1;
19 | `;
20 |
21 | const Title = styled.h1`
22 | font-size: 32px;
23 | text-align: center;
24 | `;
25 |
26 | const Subtitle = styled.div`
27 | font-size: 14px;
28 | text-align: center;
29 | `;
30 |
31 | export default ({ name, tokenUri }: Props) => {
32 | const { metadata, isLoading, isIdle } = useMetadata(tokenUri);
33 |
34 | if (isLoading || isIdle) {
35 | return null;
36 | }
37 |
38 | return (
39 | <>
40 |
41 | {name}
42 | NFTs in this realm
43 | >
44 | );
45 | };
46 |
--------------------------------------------------------------------------------
/src/components/Button.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export default styled.button<{ size?: 'sm' | 'md'; inline?: boolean }>`
4 | padding: ${({ size }) => (size === 'sm' ? '0.25em 1em' : '0.4em 2em')};
5 | display: ${({ inline }) => (inline ? 'inline-block' : 'block')};
6 | background: #000;
7 | font-size: ${({ size }) => (size === 'sm' ? '14px' : '12px')};
8 | line-height: ${({ size }) => (size === 'sm' ? '18px' : '18px')};
9 | font-family: ${({ size }) =>
10 | size === 'sm'
11 | ? "'Decima Mono', 'Courier New', monospace"
12 | : "'3616 Grammastile', sans-serif"};
13 | color: #fff;
14 | border: none;
15 |
16 | &:disabled {
17 | opacity: 0.5;
18 | }
19 |
20 | &:hover:not([disabled]) {
21 | background: #000;
22 | cursor: pointer;
23 | }
24 | `;
25 |
--------------------------------------------------------------------------------
/src/components/ButtonGroup.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export default styled.div`
4 | display: flex;
5 | align-items: center;
6 | justify-content: center;
7 | text-align: center;
8 | margin-top: 3em;
9 |
10 | & button {
11 | display: inline-block;
12 | margin-right: 4em;
13 |
14 | &:last-child {
15 | margin-right: 0;
16 | }
17 | }
18 | `;
19 |
--------------------------------------------------------------------------------
/src/components/Card.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { Link } from 'react-router-dom';
3 | import fallbackImage from '../assets/images/fallback.png';
4 |
5 | interface Props {
6 | name: string;
7 | url: string;
8 | }
9 |
10 | const Container = styled.div`
11 | margin-right: 2em;
12 | margin-bottom: 2em;
13 | width: 136px;
14 | display: inline-block;
15 |
16 | &:hover img {
17 | outline: 4px solid #bcff67;
18 | box-shadow: 0 0 20px 0 #bcff67;
19 | }
20 | `;
21 |
22 | const Image = styled.img`
23 | width: 136px;
24 | height: 136px;
25 | border-radius: 12px;
26 | outline: 4px solid transparent;
27 | box-shadow: none;
28 | transition: all 0.2s;
29 | `;
30 |
31 | const StyledLink = styled(Link)`
32 | color: #fff;
33 |
34 | &:hover {
35 | color: #bcff67;
36 | text-decoration: none;
37 | }
38 | `;
39 |
40 | const Name = styled.div`
41 | margin-top: 12px;
42 | width: 100%;
43 | font-size: 12px;
44 | font-weight: 600;
45 | `;
46 |
47 | export default ({ name, url }: Props) => (
48 |
49 |
50 |
51 | {name}
52 |
53 |
54 | );
55 |
--------------------------------------------------------------------------------
/src/components/CenteredContent.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export default styled.div`
4 | display: flex;
5 | position: relative;
6 | top: -10em;
7 | margin: 0 auto;
8 | max-width: 1200px;
9 | height: 100vh;
10 | flex-direction: column;
11 | align-items: center;
12 | justify-content: center;
13 | pointer-events: none;
14 |
15 | & * {
16 | pointer-events: auto;
17 | }
18 | `;
19 |
--------------------------------------------------------------------------------
/src/components/ClaimTokensContainer.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import borderPatternVertical from '../assets/images/claim-tokens-border-pattern-vertical.png';
3 | import borderPatternHorizontal from '../assets/images/claim-tokens-border-pattern-horizontal.png';
4 | import headerFlourish from '../assets/images/claim-header-flourish.svg';
5 | import crosshair from '../assets/images/crosshair.svg';
6 |
7 | interface Props {
8 | name: string;
9 | children: React.ReactNode;
10 | }
11 |
12 | const BackgroundContainerVertical = styled.div`
13 | position: relative;
14 | padding-left: 3em;
15 | background: url(${borderPatternVertical}) left bottom no-repeat;
16 | background-size: 24px 388px;
17 | `;
18 |
19 | const BackgroundContainerHorizontal = styled.div`
20 | padding-bottom: 3em;
21 | margin-left: -3em;
22 | background: url(${borderPatternHorizontal}) left bottom no-repeat;
23 | background-size: 388px 24px;
24 | `;
25 |
26 | const BorderContainer = styled.div`
27 | position: relative;
28 | padding: 1px;
29 | box-shadow: 0 0 20px 10px rgba(0, 0, 0, 0.8);
30 | background: linear-gradient(#ff6b6b, #000);
31 | `;
32 |
33 | const Container = styled.div`
34 | min-height: 600px;
35 | background: #000;
36 | `;
37 |
38 | const Content = styled.div`
39 | display: flex;
40 | align-items: center;
41 | justify-content: center;
42 | flex-direction: column;
43 | padding: 5em 3.5em 4.5em;
44 | width: 80vw;
45 | max-width: 784px;
46 | `;
47 |
48 | const NameTab = styled.div`
49 | position: absolute;
50 | top: 0;
51 | right: -44px;
52 | padding: 20px 10px;
53 | width: 44px;
54 | display: inline-block;
55 | font-family: '3616 Grammastile', sans-serif;
56 | font-size: 14px;
57 | color: #000;
58 | background: #ff6b6b;
59 | writing-mode: vertical-rl;
60 | `;
61 |
62 | const ThreeLinePattern = styled.div`
63 | display: flex;
64 | flex-direction: column;
65 | padding-top: 2px;
66 | `;
67 |
68 | const LinePattern = styled.div`
69 | margin-top: 6px;
70 | width: 100%;
71 | border-top: 1px solid #ff6b6b;
72 | `;
73 |
74 | const HeaderFlourish = styled.img`
75 | position: absolute;
76 | top: -40px;
77 | left: calc(50% - 45px);
78 | `;
79 |
80 | const Crosshair = styled.img`
81 | position: absolute;
82 | top: 3em;
83 | right: 1.5em;
84 | `;
85 |
86 | export default ({ name, children }: Props) => (
87 |
88 |
89 | {name}
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 | {children}
101 |
102 |
103 |
104 |
105 | );
106 |
--------------------------------------------------------------------------------
/src/components/CollectionCard.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { Link } from 'react-router-dom';
3 | import useMetadata from '../hooks/useMetadata';
4 | import fallbackImage from '../assets/images/fallback.png';
5 |
6 | interface Props {
7 | name: string;
8 | url: string;
9 | tokenUri: string;
10 | }
11 |
12 | const Container = styled.div`
13 | margin-left: 1em;
14 | margin-right: 1em;
15 | margin-bottom: 2em;
16 | width: 136px;
17 | display: inline-block;
18 |
19 | &:hover img {
20 | outline: 4px solid #bcff67;
21 | box-shadow: 0 0 20px 0 #bcff67;
22 | }
23 | `;
24 |
25 | const Image = styled.img`
26 | width: 136px;
27 | height: 136px;
28 | border-radius: 12px;
29 | outline: 4px solid transparent;
30 | box-shadow: none;
31 | transition: all 0.2s;
32 | `;
33 |
34 | const StyledLink = styled(Link)`
35 | color: #fff;
36 |
37 | &:hover {
38 | color: #bcff67;
39 | text-decoration: none;
40 | }
41 | `;
42 |
43 | const Name = styled.div`
44 | margin-top: 12px;
45 | width: 100%;
46 | font-size: 12px;
47 | font-weight: 600;
48 | `;
49 |
50 | export default ({ name, url, tokenUri }: Props) => {
51 | const { metadata, isLoading } = useMetadata(tokenUri);
52 |
53 | return (
54 |
55 |
56 | {!isLoading && (
57 | <>
58 |
59 | {name}
60 | >
61 | )}
62 |
63 |
64 | );
65 | };
66 |
--------------------------------------------------------------------------------
/src/components/CollectionList.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import CollectionCard from './CollectionCard';
3 | import { shortenAddress } from '../utils/address';
4 | import { Collection } from '../types';
5 |
6 | interface Props {
7 | collections: Collection[];
8 | }
9 |
10 | const Container = styled.div`
11 | display: flex;
12 | width: 100%;
13 | flex-wrap: wrap;
14 | `;
15 |
16 | export default ({ collections }: Props) => (
17 |
18 | {collections.map(collection => (
19 |
25 | ))}
26 |
27 | );
28 |
--------------------------------------------------------------------------------
/src/components/ConnectWallet.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import styled from 'styled-components';
3 | import { useWallet } from 'use-wallet';
4 | import usePortal from 'react-useportal';
5 | import Button from './Button';
6 | import WalletModal from './WalletModal';
7 | import useAutoConnect from '../hooks/useAutoConnect';
8 | import { shortenAddress } from '../utils/address';
9 |
10 | const Container = styled.div`
11 | position: absolute;
12 | top: 30px;
13 | right: 50px;
14 | display: flex;
15 | align-items: center;
16 | `;
17 |
18 | const ConnectWalletButton = styled(Button)<{ isConnected: boolean }>`
19 | padding: 1px;
20 | height: 42px;
21 | font-family: ${({ isConnected }) =>
22 | isConnected
23 | ? "'Decima Mono', 'Courier New', monospace"
24 | : "'3616 Grammastile', sans-serif"};
25 | font-size: ${({ isConnected }) => (isConnected ? '14px' : '8px')};
26 | line-height: ${({ isConnected }) => (isConnected ? '14px' : '24px')};
27 | background: linear-gradient(#bcff67, #17ffe3);
28 |
29 | &:hover:not([disabled]) {
30 | background: linear-gradient(#bcff67, #17ffe3);
31 | }
32 | `;
33 |
34 | const ButtonBackground = styled.div`
35 | padding: 1em 2.5em;
36 | height: 100%;
37 | background: #1c1c1c;
38 | transition: background 0.2s;
39 | `;
40 |
41 | const NetworkName = styled.span`
42 | margin-right: 1.5em;
43 | font-size: 14px;
44 | `;
45 |
46 | export default ({ ...rest }) => {
47 | const { openPortal, closePortal, isOpen, Portal } = usePortal();
48 | const wallet = useWallet();
49 | const triedAutoConnect = useAutoConnect();
50 |
51 | useEffect(() => {
52 | if (wallet.status === 'connected') {
53 | closePortal();
54 | }
55 | }, [wallet.status, closePortal]);
56 |
57 | return (
58 |
59 | {wallet.account && }
60 |
61 | {triedAutoConnect && (
62 |
66 |
67 | {wallet.account ? shortenAddress(wallet.account) : 'connect wallet'}
68 |
69 |
70 | )}
71 |
72 |
73 |
74 |
75 |
76 | );
77 | };
78 |
--------------------------------------------------------------------------------
/src/components/ConnectWalletInline.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { useWallet } from 'use-wallet';
3 | import ConnectWallet from '../components/ConnectWallet';
4 | import useAutoConnect from '../hooks/useAutoConnect';
5 |
6 | interface Props {
7 | message?: string;
8 | }
9 |
10 | const Message = styled.p`
11 | margin-top: 2em;
12 | `;
13 |
14 | const StyledConnectWallet = styled(ConnectWallet)`
15 | position: static;
16 | margin-top: 1em;
17 | `;
18 |
19 | export default ({ message }: Props) => {
20 | const wallet = useWallet();
21 | const triedAutoConnect = useAutoConnect();
22 |
23 | return (
24 | <>
25 | {!wallet.account && triedAutoConnect && (
26 | <>
27 | {message && {message}}
28 |
29 | >
30 | )}
31 | >
32 | );
33 | };
34 |
--------------------------------------------------------------------------------
/src/components/CreateRealmContainer.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import FormSteps, { FormStep } from './FormSteps';
3 | import borderPatternVertical from '../assets/images/create-realm-border-pattern-vertical.png';
4 | import borderPatternHorizontal from '../assets/images/create-realm-border-pattern-horizontal.png';
5 | import stepOneIcon from '../assets/images/icons/step-1.svg';
6 | import stepTwoIcon from '../assets/images/icons/step-2.svg';
7 | import stepThreeIcon from '../assets/images/icons/step-3.svg';
8 | import stepFourIcon from '../assets/images/icons/step-4.svg';
9 | import star from '../assets/images/star.svg';
10 |
11 | interface Props {
12 | name: string;
13 | steps: FormStep[];
14 | activeStep: number;
15 | children: React.ReactNode;
16 | }
17 |
18 | const BackgroundContainerVertical = styled.div`
19 | position: relative;
20 | padding-left: 3em;
21 | background: url(${borderPatternVertical}) left top repeat-y;
22 | background-size: 24px 388px;
23 | `;
24 |
25 | const BackgroundContainerHorizontal = styled.div`
26 | padding-bottom: 3em;
27 | margin-left: -3em;
28 | background: url(${borderPatternHorizontal}) left bottom repeat-x;
29 | background-size: 388px 24px;
30 | `;
31 |
32 | const BorderContainer = styled.div`
33 | position: relative;
34 | padding: 1px;
35 | box-shadow: 0 0 20px 10px rgba(0, 0, 0, 0.8);
36 | background: linear-gradient(#17ffe3, #000);
37 | `;
38 |
39 | const Container = styled.div`
40 | background: #000;
41 | `;
42 |
43 | const Content = styled.div`
44 | display: flex;
45 | align-items: center;
46 | justify-content: center;
47 | flex-direction: column;
48 | padding: 5em 4em 4.5em;
49 | width: 80vw;
50 | max-width: 784px;
51 | `;
52 |
53 | const NameTab = styled.div`
54 | position: absolute;
55 | top: 0;
56 | right: -44px;
57 | padding: 20px 10px;
58 | width: 44px;
59 | display: inline-block;
60 | font-family: '3616 Grammastile', sans-serif;
61 | font-size: 14px;
62 | color: #000;
63 | background: #bcff67;
64 | writing-mode: vertical-rl;
65 | `;
66 |
67 | const ThreeLinePattern = styled.div`
68 | display: flex;
69 | flex-direction: column;
70 | padding-top: 2px;
71 | `;
72 |
73 | const LinePattern = styled.div`
74 | margin-top: 6px;
75 | width: 90%;
76 | max-width: 704px;
77 | border-top: 1px solid #17ffe3;
78 | `;
79 |
80 | const StepIcon = styled.img`
81 | position: absolute;
82 | top: 1em;
83 | right: 1em;
84 | `;
85 |
86 | const Star = styled.img`
87 | position: absolute;
88 | bottom: 1.5em;
89 | left: 1.5em;
90 | `;
91 |
92 | export default ({ name, steps, activeStep, children }: Props) => (
93 | <>
94 |
95 |
96 |
97 |
98 | {name}
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | {activeStep === 1 && }
110 | {activeStep === 2 && }
111 | {activeStep === 3 && }
112 | {activeStep === 4 && }
113 | {children}
114 |
115 |
116 |
117 |
118 |
119 | >
120 | );
121 |
--------------------------------------------------------------------------------
/src/components/DecimalNumber.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { formatUnits } from '@ethersproject/units';
3 | import { BigNumber } from 'ethers';
4 | import { useEffect, useState } from 'react';
5 |
6 | interface Props {
7 | value: BigNumber | null | undefined;
8 | decimals: number;
9 | precision?: number;
10 | interpolation?: {
11 | sampledAt: number;
12 | dailyRate: BigNumber;
13 | max: BigNumber;
14 | };
15 | maximumFractionDigits?: number;
16 | minimumFractionDigits?: number;
17 | }
18 |
19 | const Container = styled.span``;
20 |
21 | /**
22 | * Render a BigNumber, optionally lerped in real time based on a sample time and
23 | * daily rate
24 | */
25 | export default ({
26 | value,
27 | decimals,
28 | interpolation,
29 | minimumFractionDigits,
30 | maximumFractionDigits,
31 | ...props
32 | }: Props) => {
33 | const [alpha, setAlpha] = useState();
34 |
35 | useEffect(() => {
36 | const h = setInterval(update, 100);
37 | return () => clearInterval(h);
38 | }, [interpolation]);
39 |
40 | const update = () => {
41 | if (interpolation == null) {
42 | setAlpha(BigNumber.from(0));
43 | return;
44 | }
45 |
46 | // compute time since sample and amount to add (alpha)
47 | const delta = Math.max(0, Date.now() - interpolation.sampledAt * 1000);
48 | const nextAlpha = interpolation.dailyRate
49 | .mul(delta)
50 | .div(1000 * 60 * 60 * 24);
51 |
52 | setAlpha(nextAlpha);
53 | };
54 |
55 | if (value == null) {
56 | return <>->;
57 | }
58 |
59 | // un-inited state
60 | if (alpha == null) {
61 | return null;
62 | }
63 |
64 | // add alpha to value and clamp to max
65 | let num = value.add(alpha);
66 | if (interpolation != null) {
67 | num = num.gt(interpolation.max) ? interpolation.max : num;
68 | }
69 |
70 | const truncated = Number(formatUnits(num, decimals)).toLocaleString('en-US', {
71 | minimumFractionDigits,
72 | maximumFractionDigits,
73 | });
74 |
75 | return {truncated};
76 | };
77 |
--------------------------------------------------------------------------------
/src/components/EmptyState.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export default styled.div`
4 | margin-top: 2em;
5 | width: 100%;
6 | text-align: center;
7 | `;
8 |
--------------------------------------------------------------------------------
/src/components/FormErrors.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | interface Props {
4 | errors: string[];
5 | }
6 |
7 | const Container = styled.ul`
8 | margin-top: 2em;
9 | margin-bottom: 3em;
10 | padding: 1em 3em;
11 | border: 1px solid #ff6b6b;
12 | color: #ff6b6b;
13 | `;
14 |
15 | const FormError = styled.li``;
16 |
17 | export default ({ errors }: Props) => {
18 | if (errors.length === 0) {
19 | return null;
20 | }
21 |
22 | return (
23 |
24 | {errors.map(error => (
25 | {error}
26 | ))}
27 |
28 | );
29 | };
30 |
--------------------------------------------------------------------------------
/src/components/FormFieldErrorMessage.tsx:
--------------------------------------------------------------------------------
1 | import { FieldError } from 'react-hook-form';
2 | import styled from 'styled-components';
3 |
4 | interface Props {
5 | error?: FieldError;
6 | minValue?: number;
7 | maxLength?: number;
8 | }
9 |
10 | const Container = styled.div`
11 | margin-top: 0.75em;
12 | height: 20px;
13 | font-size: 12px;
14 | `;
15 |
16 | const Message = styled.span<{ isVisible: boolean }>`
17 | opacity: ${({ isVisible }) => (isVisible ? 1 : 0)};
18 | transition: opacity 0.2s;
19 | `;
20 |
21 | export default ({ error, minValue, maxLength }: Props) => (
22 |
23 |
24 | {error?.type === 'required' && 'This field is required.'}
25 | {error?.type === 'isNumber' && 'Enter a valid number.'}
26 | {error?.type === 'maxLength' &&
27 | `This field must be shorter ${maxLength} characters.`}
28 | {error?.type === 'minValue' &&
29 | `Enter a value ${minValue === 0 ? 'zero' : minValue} or higher.`}
30 | {error?.type === 'address' && 'Enter a valid Ethereum address.'}
31 | {error?.type === 'custom' && error?.message}
32 |
33 |
34 | );
35 |
--------------------------------------------------------------------------------
/src/components/FormHeading.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export default styled.img`
4 | margin-bottom: 3em;
5 | height: 20px;
6 | `;
7 |
--------------------------------------------------------------------------------
/src/components/FormSteps.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export interface FormStep {
4 | label: string;
5 | path: string;
6 | }
7 |
8 | interface Props {
9 | steps: FormStep[];
10 | activeStep: number;
11 | }
12 |
13 | const Container = styled.div`
14 | position: absolute;
15 | margin-top: -3.5em;
16 | width: 80vw;
17 | max-width: 784px;
18 | `;
19 |
20 | const ProgressBar = styled.div<{ progress: number }>`
21 | margin-bottom: 15px;
22 | height: 6px;
23 | background: linear-gradient(
24 | to right,
25 | #bcff67 ${({ progress }) => progress / 2}%,
26 | transparent ${({ progress }) => progress}%
27 | );
28 | border-radius: 20px;
29 | `;
30 |
31 | const Steps = styled.div`
32 | display: flex;
33 | justify-content: space-between;
34 | `;
35 |
36 | const Step = styled.span<{ $isActive: boolean }>`
37 | font-size: 14px;
38 | text-transform: uppercase;
39 | font-weight: ${({ $isActive }) => ($isActive ? 600 : 400)};
40 | color: ${({ $isActive }) => ($isActive ? '#fff' : '#777')};
41 | `;
42 |
43 | export default ({ steps, activeStep }: Props) => (
44 |
45 |
46 |
47 | {steps.map((step, index) => (
48 |
49 | {step.label}
50 |
51 | ))}
52 |
53 |
54 | );
55 |
--------------------------------------------------------------------------------
/src/components/GlobalStyle.tsx:
--------------------------------------------------------------------------------
1 | import { useLocation } from 'react-router-dom';
2 | import { createGlobalStyle } from 'styled-components';
3 | import DecimaMonoProLt from '../assets/fonts/DecimaMonoProLt.woff2';
4 | import DecimaMonoProLtItalic from '../assets/fonts/DecimaMonoProLt-Italic.woff2';
5 | import DecimaMonoPro from '../assets/fonts/DecimaMonoPro.woff2';
6 | import DecimaMonoProBold from '../assets/fonts/DecimaMonoPro-Bold.woff2';
7 | import DecimaMonoProItalic from '../assets/fonts/DecimaMonoPro-Italic.woff2';
8 | import DecimaMonoProBoldItalic from '../assets/fonts/DecimaMonoPro-BoldItalic.woff2';
9 | import ThreeSixOneSixGrammastileRegular from '../assets/fonts/3616Grammastile-Regular.woff2';
10 | import swirlBg from '../assets/images/swirl-bg.png';
11 | import labBg from '../assets/images/lab-bg.png';
12 | import dustBg from '../assets/images/dust-bg.png';
13 |
14 | const GlobalStyle = createGlobalStyle`
15 | @font-face {
16 | font-family: 'Decima Mono';
17 | src: url(${DecimaMonoProLt}) format('woff2');
18 | font-weight: 300;
19 | font-style: normal;
20 | font-display: block;
21 | }
22 |
23 | @font-face {
24 | font-family: 'Decima Mono';
25 | src: url(${DecimaMonoProLtItalic}) format('woff2');
26 | font-weight: 300;
27 | font-style: italic;
28 | font-display: block;
29 | }
30 |
31 | @font-face {
32 | font-family: 'Decima Mono';
33 | src: url(${DecimaMonoPro}) format('woff2');
34 | font-weight: 400;
35 | font-style: normal;
36 | font-display: block;
37 | }
38 |
39 | @font-face {
40 | font-family: 'Decima Mono';
41 | src: url(${DecimaMonoProBold}) format('woff2');
42 | font-weight: 600;
43 | font-style: normal;
44 | font-display: block;
45 | }
46 |
47 | @font-face {
48 | font-family: 'Decima Mono';
49 | src: url(${DecimaMonoProItalic}) format('woff2');
50 | font-weight: 400;
51 | font-style: italic;
52 | font-display: block;
53 | }
54 |
55 | @font-face {
56 | font-family: 'Decima Mono';
57 | src: url(${DecimaMonoProBoldItalic}) format('woff2');
58 | font-weight: 600;
59 | font-style: italic;
60 | font-display: block;
61 | }
62 |
63 | @font-face {
64 | font-family: '3616 Grammastile';
65 | src: url(${ThreeSixOneSixGrammastileRegular}) format('woff2');
66 | font-weight: 400;
67 | font-style: normal;
68 | font-display: block;
69 | }
70 |
71 | * {
72 | box-sizing: border-box;
73 | }
74 |
75 | ::selection {
76 | background: #17ffe3;
77 | color: #000;
78 | }
79 |
80 | ::-moz-selection {
81 | background: #17ffe3;
82 | color: #000;
83 | }
84 |
85 | html {
86 | background: linear-gradient(#1c1c1c 20%, #183934 100%);
87 | background-size: cover;
88 | background-attachment: fixed;
89 | }
90 |
91 | body, button, input {
92 | font-family: "Decima Mono", "Courier New", monospace;
93 | }
94 |
95 | #root {
96 | min-height: 100vh;
97 | }
98 |
99 | p {
100 | line-height: 24px;
101 | }
102 |
103 | h2 {
104 | margin-bottom: 2em;
105 | }
106 |
107 | form {
108 | width: 100%;
109 | }
110 |
111 | a {
112 | padding-bottom: 0.5em;
113 | color: rgba(255, 255, 255, 0.6);
114 | text-decoration: none;
115 | transition: all 0.2s;
116 |
117 | &:hover {
118 | color: #fff;
119 | text-decoration: underline;
120 | }
121 | }
122 | `;
123 |
124 | export default () => {
125 | const location = useLocation();
126 | const section = location.pathname.split('/')[1];
127 | const isBrowseRealmsPage =
128 | location.pathname.split('/')[2] === 'realms' &&
129 | !location.pathname.split('/')[3];
130 | const isHomePage = section === '';
131 |
132 | const getBackground = () => {
133 | if (section === 'realm' || isHomePage) {
134 | return `
135 | background: url(${swirlBg}) 0 0 no-repeat;
136 | background-size: cover;
137 | `;
138 | }
139 |
140 | if (section === 'infuse') {
141 | return `
142 | background: url(${labBg}) 70% -30% no-repeat;
143 | background-size: 60%;
144 | `;
145 | }
146 |
147 | if (section === 'claim' || isBrowseRealmsPage) {
148 | return `
149 | background: url(${dustBg}) 0 0 no-repeat;
150 | background-size: cover;
151 | `;
152 | }
153 | };
154 |
155 | const background = getBackground();
156 |
157 | const BodyStyle = createGlobalStyle`
158 | body {
159 | margin: 0;
160 | ${background}
161 | background-attachment: fixed;
162 | color: #fff;
163 | }
164 | `;
165 |
166 | return (
167 | <>
168 |
169 |
170 | >
171 | );
172 | };
173 |
--------------------------------------------------------------------------------
/src/components/Header.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import Nav from './Nav';
3 | import ConnectWallet from './ConnectWallet';
4 |
5 | const Container = styled.div``;
6 |
7 | export default () => {
8 | return (
9 |
10 |
11 |
12 |
13 | );
14 | };
15 |
--------------------------------------------------------------------------------
/src/components/InfuseNftContainer.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import borderPatternVertical from '../assets/images/infuse-nft-border-pattern-vertical.png';
3 | import borderPatternHorizontal from '../assets/images/infuse-nft-border-pattern-horizontal.png';
4 | import headerFlourish from '../assets/images/infusion-header-flourish.svg';
5 |
6 | interface Props {
7 | name: string;
8 | children: React.ReactNode;
9 | }
10 |
11 | const BackgroundContainerVertical = styled.div`
12 | position: relative;
13 | padding-left: 3em;
14 | background: url(${borderPatternVertical}) left bottom no-repeat;
15 | background-size: 24px 388px;
16 | `;
17 |
18 | const BackgroundContainerHorizontal = styled.div`
19 | padding-bottom: 3em;
20 | margin-left: -3em;
21 | background: url(${borderPatternHorizontal}) left bottom no-repeat;
22 | background-size: 388px 24px;
23 | `;
24 |
25 | const BorderContainer = styled.div`
26 | position: relative;
27 | padding: 1px;
28 | box-shadow: 0 0 20px 10px rgba(0, 0, 0, 0.8);
29 | background: linear-gradient(#00e0ff, #000);
30 | `;
31 |
32 | const Container = styled.div`
33 | min-height: 600px;
34 | background: #000;
35 | `;
36 |
37 | const Content = styled.div`
38 | display: flex;
39 | align-items: center;
40 | justify-content: center;
41 | flex-direction: column;
42 | padding: 5em 2.5em 4.5em;
43 | width: 80vw;
44 | max-width: 784px;
45 | `;
46 |
47 | const NameTab = styled.div`
48 | position: absolute;
49 | top: 0;
50 | right: -44px;
51 | padding: 20px 10px;
52 | width: 44px;
53 | display: inline-block;
54 | font-family: '3616 Grammastile', sans-serif;
55 | font-size: 14px;
56 | color: #000;
57 | background: #00e0ff;
58 | writing-mode: vertical-rl;
59 | `;
60 |
61 | const ThreeLinePattern = styled.div`
62 | display: flex;
63 | flex-direction: column;
64 | padding-top: 2px;
65 | `;
66 |
67 | const LinePattern = styled.div`
68 | margin-top: 6px;
69 | width: 100%;
70 | border-top: 1px solid #06e8f8;
71 | `;
72 |
73 | const HeaderFlourish = styled.img`
74 | position: absolute;
75 | top: -40px;
76 | left: calc(50% - 45px);
77 | `;
78 |
79 | export default ({ name, children }: Props) => (
80 |
81 |
82 | {name}
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | {children}
93 |
94 |
95 |
96 |
97 | );
98 |
--------------------------------------------------------------------------------
/src/components/InfusionTicker.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { BigNumber } from 'ethers';
3 | import { useQuery } from 'react-query';
4 | import { getLoaders } from '../hypervibes/dataloaders';
5 | import DecimalNumber from './DecimalNumber';
6 |
7 | interface Props {
8 | chainId: number;
9 | collection: string;
10 | tokenId: BigNumber;
11 | realmId: BigNumber;
12 | }
13 |
14 | const Container = styled.div`
15 | display: flex;
16 | align-items: flex-end;
17 | justify-content: space-between;
18 | font-size: 12px;
19 | font-weight: 300;
20 | `;
21 |
22 | const ClaimableAmount = styled(DecimalNumber)`
23 | color: #17ffe3;
24 | `;
25 |
26 | const TokenSymbol = styled.div`
27 | margin-left: 0.5em;
28 | `;
29 |
30 | export default ({ chainId, realmId, collection, tokenId }: Props) => {
31 | const loaders = getLoaders(chainId);
32 |
33 | // fetch data from the chain and the graph simul
34 | const { data, isError } = useQuery([realmId, collection, tokenId], async () =>
35 | Promise.all([
36 | loaders.tokenData.load({
37 | realmId,
38 | collection: collection,
39 | tokenId,
40 | }),
41 | loaders.indexedInfusion.load({ collection: collection, tokenId }),
42 | ])
43 | );
44 |
45 | if (isError) {
46 | return null;
47 | } else if (data === undefined) {
48 | return null;
49 | }
50 |
51 | const [tokenData, infusionData] = data;
52 |
53 | if (tokenData.view === null) {
54 | return null;
55 | }
56 |
57 | const infusion = infusionData.infusions.find(i => i.realm.id.eq(realmId));
58 |
59 | if (!infusion) {
60 | return null;
61 | }
62 |
63 | const { realm } = infusion;
64 |
65 | return (
66 |
67 |
68 | {' '}
78 | /{' '}
79 |
84 |
85 | {realm.token.symbol}
86 |
87 | );
88 | };
89 |
--------------------------------------------------------------------------------
/src/components/Input.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export default styled.input<{ hasError?: boolean }>`
4 | padding: 0.3em 0;
5 | width: 100%;
6 | background: none;
7 | font-size: 20px;
8 | color: #fff;
9 | border: 0;
10 | border-bottom-width: 2px;
11 | border-style: solid;
12 | border-color: ${({ hasError }) => (hasError ? '#a72f2f' : '#fff')};
13 | transition: border-color 0.2s;
14 |
15 | &:focus {
16 | border-color: ${({ hasError }) => (hasError ? '#a72f2f' : '#17ffe3')};
17 | outline: none;
18 | }
19 | `;
20 |
--------------------------------------------------------------------------------
/src/components/InputGroup.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | interface Props {
4 | label: string;
5 | description?: string;
6 | children: React.ReactNode;
7 | }
8 |
9 | const Container = styled.div`
10 | margin-top: 1.7em;
11 | `;
12 |
13 | const Label = styled.div`
14 | font-size: 18px;
15 | font-weight: 600;
16 | text-transform: uppercase;
17 | `;
18 |
19 | const InputGroup = styled.div`
20 | display: flex;
21 | margin-bottom: 2em;
22 | `;
23 |
24 | const Description = styled.p`
25 | margin-bottom: 2em;
26 | font-size: 14px;
27 | font-weight: 300;
28 | `;
29 |
30 | export default ({ label, description, children }: Props) => (
31 |
32 |
33 |
34 | {description && {description}}
35 |
36 | {children}
37 |
38 | );
39 |
--------------------------------------------------------------------------------
/src/components/Label.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | interface Props {
4 | name: string;
5 | children: React.ReactNode;
6 | isRequired?: boolean;
7 | }
8 |
9 | const Label = styled.label<{ isRequired: boolean }>`
10 | margin: 1.5em 0 0.5em;
11 | display: block;
12 | font-size: 18px;
13 | font-weight: 600;
14 | color: #fff;
15 | text-transform: uppercase;
16 |
17 | &:after {
18 | content: '*';
19 | position: relative;
20 | top: -0.2em;
21 | margin-left: 0.3em;
22 | color: #a72f2f;
23 | font-weight: 900;
24 | display: ${({ isRequired }) => (isRequired ? 'inline-block' : 'none')};
25 | }
26 | `;
27 |
28 | export default ({ name, children, isRequired = false, ...rest }: Props) => (
29 |
32 | );
33 |
--------------------------------------------------------------------------------
/src/components/Modal.tsx:
--------------------------------------------------------------------------------
1 | import { useRef } from 'react';
2 | import styled from 'styled-components';
3 | import { useTransition, animated } from 'react-spring';
4 | import Button from './Button';
5 |
6 | export interface Props {
7 | isOpen: boolean;
8 | close: () => void;
9 | children: React.ReactNode;
10 | }
11 |
12 | const Overlay = styled.div`
13 | position: fixed;
14 | top: 0;
15 | left: 0;
16 | width: 100%;
17 | height: 100vh;
18 | background: rgba(0, 0, 0, 0.6);
19 | `;
20 |
21 | const Modal = styled.div`
22 | position: fixed;
23 | left: 50%;
24 | top: 50%;
25 | width: 580px;
26 | padding: 1px;
27 | transform: translate(-50%, -50%);
28 | z-index: 1000;
29 | background: linear-gradient(#bcff67 0%, #17ffe3 30%, transparent 70%);
30 | box-shadow: 0 0 20px 10px rgba(23, 255, 227, 0.3);
31 | `;
32 |
33 | const ModalBackground = styled.div`
34 | background: #000;
35 | `;
36 |
37 | export const ModalHeading = styled.h3`
38 | display: flex;
39 | align-items: center;
40 | justify-content: center;
41 | margin-top: 0;
42 | margin-bottom: 2em;
43 | padding: 1.5em 2em;
44 | font-family: '3616 Grammastile', sans-serif;
45 | font-size: 14px;
46 | width: 100%;
47 | border-bottom: 1px solid #17ffe3;
48 | `;
49 |
50 | export const ModalContent = styled.div`
51 | padding: 0 2em 1em;
52 | `;
53 |
54 | const CloseButton = styled(Button)`
55 | position: absolute;
56 | top: 1em;
57 | right: 1em;
58 | padding: 0.5em;
59 | background: none;
60 | font-family: '3616 Grammastile', sans-serif;
61 | font-size: 14px;
62 | font-weight: 600;
63 |
64 | &:hover:not([disabled]) {
65 | background: none;
66 | }
67 | `;
68 |
69 | export default ({ isOpen, children, close }: Props) => {
70 | const overlay = useRef(null);
71 |
72 | const transitions = useTransition(isOpen, {
73 | from: { opacity: 0 },
74 | enter: { opacity: 1 },
75 | leave: { opacity: 0 },
76 | reverse: isOpen,
77 | });
78 |
79 | return transitions(
80 | (styles, item) =>
81 | item && (
82 |
83 | {
86 | if (e.target === overlay.current) {
87 | close();
88 | }
89 | }}
90 | >
91 |
92 |
93 | {children}
94 |
95 |
96 | X
97 |
98 |
99 |
100 |
101 |
102 | )
103 | );
104 | };
105 |
--------------------------------------------------------------------------------
/src/components/MultiAddressInput.tsx:
--------------------------------------------------------------------------------
1 | import { useFormContext, useFieldArray } from 'react-hook-form';
2 | import styled from 'styled-components';
3 | import Label from './Label';
4 | import AddressInput from './AddressInput';
5 | import plusIcon from '../assets/images/icons/plus.svg';
6 |
7 | interface Props {
8 | name: string;
9 | label: string;
10 | description?: string;
11 | required?: boolean;
12 | maxLength?: number;
13 | addMoreText?: string;
14 | }
15 |
16 | const Container = styled.div`
17 | margin-bottom: 4em;
18 | `;
19 |
20 | const InputContainer = styled.div`
21 | position: relative;
22 | width: 100%;
23 | `;
24 |
25 | const StyledLabel = styled(Label)`
26 | display: inline-block;
27 | `;
28 |
29 | const Description = styled.p`
30 | margin: 0 0 0.5em;
31 | font-size: 14px;
32 | font-weight: 300;
33 | `;
34 |
35 | const Input = styled(AddressInput)`
36 | margin-bottom: 0.5em;
37 | `;
38 |
39 | const AddAnotherButton = styled.button`
40 | display: flex;
41 | margin-top: 1em;
42 | padding: 0;
43 | align-items: center;
44 | background: none;
45 | font-size: 14px;
46 | color: #fff;
47 | border: none;
48 |
49 | &:hover {
50 | cursor: pointer;
51 | }
52 | `;
53 |
54 | const PlusIcon = styled.img`
55 | margin-right: 1em;
56 | `;
57 |
58 | const RemoveButton = styled.button<{ isVisible: boolean }>`
59 | position: absolute;
60 | top: -1.55em;
61 | right: 0;
62 | margin-top: 1.4em;
63 | margin-left: 1em;
64 | padding: 1em 0.5em 0 2em;
65 | align-self: flex-start;
66 | font-size: 14px;
67 | visibility: ${({ isVisible }) => (isVisible ? 'visible' : 'hidden')};
68 | background: #000;
69 | color: #fff;
70 | border: none;
71 |
72 | &:hover {
73 | cursor: pointer;
74 | }
75 | `;
76 |
77 | export default ({
78 | name,
79 | label,
80 | description,
81 | required,
82 | addMoreText = 'Add more',
83 | ...rest
84 | }: Props) => {
85 | const { control } = useFormContext();
86 | const { fields, append, remove } = useFieldArray({ control, name });
87 |
88 | return (
89 |
90 | {label}
91 | {description && {description}}
92 |
93 | {fields.map((field, index) => (
94 |
95 |
100 |
101 | 1}
103 | onClick={() => remove(index)}
104 | >
105 | Remove
106 |
107 |
108 | ))}
109 |
110 | {
112 | e.preventDefault();
113 | append({ value: '' });
114 | }}
115 | >
116 |
117 | {addMoreText}
118 |
119 |
120 | );
121 | };
122 |
--------------------------------------------------------------------------------
/src/components/Nav.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { Link, useLocation } from 'react-router-dom';
3 | import { useWallet } from 'use-wallet';
4 | import logo from '../assets/images/logo.svg';
5 | import { NETWORKS } from '../constants/contracts';
6 |
7 | const Container = styled.div`
8 | position: absolute;
9 | top: 0;
10 | left: 0;
11 | display: flex;
12 | align-items: center;
13 | justify-content: center;
14 | margin-top: 42px;
15 | width: 100%;
16 | `;
17 |
18 | const NavigationLink = styled(Link)`
19 | font-family: '3616 Grammastile', sans-serif;
20 | font-size: 6px;
21 | display: inline-block;
22 | margin-left: 16px;
23 | margin-right: 16px;
24 | color: #fff;
25 | `;
26 |
27 | const Logo = styled.img`
28 | position: absolute;
29 | left: 40px;
30 | `;
31 |
32 | // return a network name if we're already within the explore part of the app
33 | const currentExploreNetwork = (pathname: string): string | undefined => {
34 | const match = pathname.match(/\/(\w+)\//);
35 | if (!match) {
36 | return undefined;
37 | }
38 |
39 | // if it matches a network, we're good
40 | const [, part] = match;
41 | if (NETWORKS[part]) {
42 | return part;
43 | }
44 |
45 | return undefined;
46 | };
47 |
48 | // handle differences from network names from web3 wallet
49 | const exploreNetworkFromWalletNetwork = (
50 | network: string | null
51 | ): string | undefined => {
52 | if (!network) {
53 | return undefined;
54 | } else if (network === 'main') {
55 | return 'mainnet';
56 | }
57 |
58 | return network;
59 | };
60 |
61 | export default () => {
62 | const wallet = useWallet();
63 | const location = useLocation();
64 | const pageName = location.pathname.split('/')[1];
65 |
66 | if (!pageName) {
67 | return null;
68 | }
69 |
70 | const exploreNetwork = currentExploreNetwork(location.pathname);
71 | const exploreDestination =
72 | exploreNetwork ??
73 | exploreNetworkFromWalletNetwork(wallet.networkName) ??
74 | 'mainnet';
75 |
76 | return (
77 |
78 |
79 |
80 |
81 |
82 |
83 | Explore
84 |
85 |
86 | Create Realm
87 | Infuse
88 | Claim
89 |
90 | );
91 | };
92 |
--------------------------------------------------------------------------------
/src/components/Nft.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { useNft } from 'use-nft';
3 |
4 | interface Props {
5 | contractAddress: string;
6 | tokenId: string;
7 | }
8 |
9 | const Container = styled.div`
10 | width: 25%;
11 | `;
12 |
13 | const ImageContainer = styled.div`
14 | display: flex;
15 | height: 400px;
16 | align-items: center;
17 | `;
18 |
19 | const Image = styled.img`
20 | width: 300px;
21 | `;
22 |
23 | export default ({ contractAddress, tokenId }: Props) => {
24 | const { loading, error, nft } = useNft(contractAddress, tokenId);
25 |
26 | if (loading) return <>Loading...>;
27 |
28 | if (error || !nft) return <>Error.>;
29 |
30 | return (
31 |
32 |
33 |
34 |
35 | {nft.name}
36 |
37 | );
38 | };
39 |
--------------------------------------------------------------------------------
/src/components/NftCard.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import fallbackImage from '../assets/images/fallback.png';
3 |
4 | interface Props {
5 | name: string;
6 | image?: string;
7 | }
8 |
9 | const Container = styled.div`
10 | width: 100%;
11 | `;
12 |
13 | const Image = styled.img`
14 | width: 100%;
15 | height: auto;
16 | border-radius: 12px;
17 | `;
18 |
19 | const Name = styled.div`
20 | margin-top: 25px;
21 | width: 100%;
22 | font-size: 20px;
23 | font-weight: 600;
24 | color: #bcff67;
25 | text-align: center;
26 | `;
27 |
28 | export default ({ name, image = fallbackImage }: Props) => (
29 |
30 |
31 | {name}
32 |
33 | );
34 |
--------------------------------------------------------------------------------
/src/components/NftGalleryCard.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { BigNumber } from 'ethers';
3 | import { Link } from 'react-router-dom';
4 | import useMetadata from '../hooks/useMetadata';
5 | import InfusionTicker from '../components/InfusionTicker';
6 | import fallbackImage from '../assets/images/fallback.png';
7 |
8 | interface Props {
9 | chainId?: number;
10 | realmId?: BigNumber;
11 | collection?: string;
12 | tokenId?: BigNumber;
13 | url: string;
14 | tokenUri: string;
15 | size?: 'sm' | 'lg';
16 | showClaimableAmount?: boolean;
17 | }
18 |
19 | const Container = styled.div`
20 | margin-left: 1em;
21 | margin-right: 1em;
22 | margin-bottom: 2em;
23 | display: inline-block;
24 |
25 | &:hover img {
26 | outline: 4px solid #bcff67;
27 | box-shadow: 0 0 20px 0 #bcff67;
28 | }
29 | `;
30 |
31 | const Image = styled.img<{ size: 'sm' | 'lg' }>`
32 | width: ${({ size }) => (size === 'sm' ? '136px' : '270px')};
33 | height: ${({ size }) => (size === 'sm' ? '136px' : '270px')};
34 | border-radius: 12px;
35 | outline: 4px solid transparent;
36 | box-shadow: none;
37 | transition: all 0.2s;
38 | `;
39 |
40 | const Name = styled.div`
41 | margin: 12px 0 8px;
42 | width: 100%;
43 | font-size: 14px;
44 | font-weight: 600;
45 | `;
46 |
47 | const StyledLink = styled(Link)`
48 | color: #fff;
49 |
50 | &:hover ${Name} {
51 | color: #bcff67;
52 | text-decoration: none;
53 | }
54 | `;
55 |
56 | export default ({
57 | chainId,
58 | realmId,
59 | collection,
60 | tokenId,
61 | url,
62 | tokenUri,
63 | size = 'sm',
64 | showClaimableAmount = true,
65 | }: Props) => {
66 | const { metadata, isLoading } = useMetadata(tokenUri);
67 |
68 | return (
69 |
70 |
71 | {!isLoading && (
72 | <>
73 |
74 | {metadata?.name}
75 |
76 | {showClaimableAmount &&
77 | chainId &&
78 | realmId &&
79 | collection &&
80 | tokenId && (
81 |
87 | )}
88 | >
89 | )}
90 |
91 |
92 | );
93 | };
94 |
--------------------------------------------------------------------------------
/src/components/NumberInput.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { useFormContext } from 'react-hook-form';
3 | import Label from './Label';
4 | import Input from './Input';
5 | import FormFieldErrorMessage from './FormFieldErrorMessage';
6 |
7 | interface Props {
8 | name: string;
9 | label: string;
10 | required?: boolean;
11 | min?: number;
12 | onChange?: (value: string) => void;
13 | validate?: (value: string) => boolean | string;
14 | }
15 |
16 | const Container = styled.div`
17 | position: relative;
18 | `;
19 |
20 | const StyledInput = styled(Input)`
21 | max-width: 270px;
22 | padding-top: 1.5em;
23 | padding-left: 0.5em;
24 | padding-right: 0.5em;
25 | border-radius: 10px;
26 | border-width: 2px;
27 | `;
28 |
29 | const StyledLabel = styled(Label)`
30 | position: absolute;
31 | top: -0.5em;
32 | left: 1em;
33 | font-size: 12px;
34 | font-weight: 400;
35 | text-transform: none;
36 | `;
37 |
38 | export default ({
39 | name,
40 | label,
41 | required = false,
42 | min = 0,
43 | onChange,
44 | validate,
45 | ...rest
46 | }: Props) => {
47 | const { register, formState } = useFormContext();
48 |
49 | return (
50 |
51 |
52 | {label}
53 |
54 |
55 | !!value?.match(/^[+-]?(\d*\.)?\d+$/),
65 | minValue: value => value === undefined || parseFloat(value) >= min,
66 | custom: value => !validate || validate(value),
67 | },
68 | })}
69 | {...rest}
70 | />
71 |
72 |
73 |
74 | );
75 | };
76 |
--------------------------------------------------------------------------------
/src/components/RadioButton.tsx:
--------------------------------------------------------------------------------
1 | import { useFormContext } from 'react-hook-form';
2 | import styled from 'styled-components';
3 | import Label from './Label';
4 |
5 | export interface Props {
6 | name: string;
7 | id: string;
8 | label: string;
9 | required?: boolean;
10 | }
11 |
12 | const Container = styled.div``;
13 |
14 | const StyledLabel = styled(Label)`
15 | display: inline-block;
16 | `;
17 |
18 | const RadioButton = styled.input`
19 | display: inline-block;
20 | `;
21 |
22 | export default ({ id, name, label, required = false, ...rest }: Props) => {
23 | const { register } = useFormContext();
24 |
25 | return (
26 |
27 |
34 | {label}
35 |
36 | );
37 | };
38 |
--------------------------------------------------------------------------------
/src/components/RadioButtonCard.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { useFormContext } from 'react-hook-form';
3 |
4 | export interface Props {
5 | name: string;
6 | id: string;
7 | label: string;
8 | children: React.ReactNode;
9 | }
10 |
11 | const Container = styled.div``;
12 |
13 | const Card = styled.div<{ isSelected: boolean }>`
14 | display: flex;
15 | align-items: center;
16 | justify-content: center;
17 | margin-right: 2em;
18 | width: 310px;
19 | height: 138px;
20 | background: #0b0b0b;
21 | border-width: 1px;
22 | border-style: solid;
23 | border-color: ${({ isSelected }) => (isSelected ? '#bcff67' : '#fff')};
24 | outline-width: 1px;
25 | outline-style: solid;
26 | outline-color: ${({ isSelected }) =>
27 | isSelected ? '#bcff67' : 'transparent'};
28 | box-shadow: ${({ isSelected }) =>
29 | isSelected ? '0 0 40px 0 #bcff67' : 'none'};
30 | border-radius: 20px;
31 | transition: all 0.2s;
32 |
33 | &:hover {
34 | border-color: #bcff67;
35 | outline-color: #bcff67;
36 | cursor: pointer;
37 | }
38 | `;
39 |
40 | const Label = styled.div`
41 | margin: 1.5em 1em 0;
42 | `;
43 |
44 | export default ({ name, id, label, children }: Props) => {
45 | const { watch, setValue } = useFormContext();
46 | const value = watch(name);
47 |
48 | return (
49 |
50 | setValue(name, id)}>
51 | {children}
52 |
53 |
54 |
55 | );
56 | };
57 |
--------------------------------------------------------------------------------
/src/components/RadioGroup.tsx:
--------------------------------------------------------------------------------
1 | import { useFormContext } from 'react-hook-form';
2 | import styled from 'styled-components';
3 | import FormFieldErrorMessage from './FormFieldErrorMessage';
4 |
5 | interface Props {
6 | name: string;
7 | label: string;
8 | children: React.ReactNode;
9 | }
10 |
11 | const Container = styled.div`
12 | margin-top: 2em;
13 | margin-bottom: 0.5em;
14 | `;
15 |
16 | const RadioGroup = styled.div`
17 | display: flex;
18 | `;
19 |
20 | export default ({ name, label, children }: Props) => {
21 | const { formState } = useFormContext();
22 |
23 | return (
24 |
25 | {label}
26 |
27 | {children}
28 |
29 |
30 | );
31 | };
32 |
--------------------------------------------------------------------------------
/src/components/RealmCard.tsx:
--------------------------------------------------------------------------------
1 | import { useMemo } from 'react';
2 | import styled from 'styled-components';
3 | import { Link } from 'react-router-dom';
4 | import useMetadata from '../hooks/useMetadata';
5 | import fallbackImage from '../assets/images/fallback.png';
6 |
7 | interface Props {
8 | name: string;
9 | url: string;
10 | tokenUri: string;
11 | size: 'sm' | 'lg';
12 | }
13 |
14 | const Container = styled.div<{ size: 'sm' | 'lg' }>`
15 | width: ${({ size }) => (size === 'sm' ? '134px' : '214px')};
16 | margin-left: 1em;
17 | margin-right: 1em;
18 | margin-bottom: 5.25em;
19 | display: inline-block;
20 |
21 | &:hover > *:first-child {
22 | outline: 4px solid #bcff67;
23 | box-shadow: 0 0 20px 0 #bcff67;
24 | }
25 | `;
26 |
27 | const Image = styled.div<{ src: string; size: 'sm' | 'lg' }>`
28 | width: ${({ size }) => (size === 'sm' ? '134px' : '214px')};
29 | height: ${({ size }) => (size === 'sm' ? '175px' : '302px')};
30 | background: url(${({ src }) => src}) center center no-repeat,
31 | url(${fallbackImage}) center center no-repeat;
32 | background-size: cover;
33 | border-radius: 12px;
34 | border-top-left-radius: 250px;
35 | border-top-right-radius: 250px;
36 | outline: 4px solid transparent;
37 | box-shadow: none;
38 | transition: all 0.2s;
39 | `;
40 |
41 | const StyledLink = styled(Link)`
42 | color: #fff;
43 |
44 | &:hover {
45 | color: #bcff67;
46 | text-decoration: none;
47 | }
48 | `;
49 |
50 | const Name = styled.div<{ size: 'sm' | 'lg' }>`
51 | margin-top: 12px;
52 | font-size: ${({ size }) => (size === 'sm' ? '12px' : '16px')};
53 | font-weight: ${({ size }) => (size === 'sm' ? 600 : 400)};
54 | text-align: left;
55 | text-overflow: ellipsis;
56 | white-space: nowrap;
57 | overflow: hidden;
58 | `;
59 |
60 | export default ({ name, url, tokenUri, size }: Props) => {
61 | const { metadata, isLoading } = useMetadata(tokenUri);
62 | const image = useMemo(() => {
63 | if (metadata?.image) {
64 | return metadata?.image;
65 | }
66 |
67 | if (!isLoading) {
68 | return fallbackImage;
69 | }
70 |
71 | return '';
72 | }, [metadata, isLoading]);
73 |
74 | return (
75 |
76 |
77 |
78 | {name}
79 |
80 |
81 | );
82 | };
83 |
--------------------------------------------------------------------------------
/src/components/RealmList.tsx:
--------------------------------------------------------------------------------
1 | import { ReactNode } from 'react';
2 | import styled from 'styled-components';
3 | import { Link } from 'react-router-dom';
4 | import RealmCard from './RealmCard';
5 | import EmptyState from './EmptyState';
6 | import SubmitButton from '../components/SubmitButton';
7 | import { Realm } from '../types';
8 |
9 | interface Props {
10 | realms: Realm[];
11 | url?: (realmId: string) => string;
12 | size?: 'sm' | 'lg';
13 | empty?: ReactNode;
14 | }
15 |
16 | const Container = styled.div`
17 | display: flex;
18 | width: 100%;
19 | flex-wrap: wrap;
20 | justify-content: center;
21 | `;
22 |
23 | const Message = styled.div`
24 | margin-bottom: 1em;
25 | `;
26 |
27 | export default ({
28 | realms,
29 | url,
30 | size = 'sm',
31 | empty = 'There are no realms.',
32 | }: Props) => (
33 |
34 | {realms && realms.length > 0 ? (
35 | <>
36 | {realms.map(realm => (
37 |
44 | ))}
45 | >
46 | ) : (
47 |
48 | {empty}
49 |
50 |
51 | Create a Realm
52 |
53 |
54 | )}
55 |
56 | );
57 |
--------------------------------------------------------------------------------
/src/components/ScrollToTop.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { useLocation } from 'react-router-dom';
3 |
4 | export default () => {
5 | const { pathname } = useLocation();
6 |
7 | useEffect(() => {
8 | window.scrollTo(0, 0);
9 | }, [pathname]);
10 |
11 | return null;
12 | };
13 |
--------------------------------------------------------------------------------
/src/components/SocialLinks.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import discordIcon from '../assets/images/icons/discord.svg';
3 | import twitterIcon from '../assets/images/icons/twitter.svg';
4 | import githubIcon from '../assets/images/icons/github.svg';
5 |
6 | const Container = styled.div`
7 | position: fixed;
8 | top: 570px;
9 | left: 32px;
10 | display: flex;
11 | transform: rotate(-90deg);
12 | transform-origin: top left;
13 | `;
14 |
15 | const SocialLink = styled.a`
16 | margin-right: 32px;
17 | display: inline-block;
18 | `;
19 |
20 | export default () => (
21 |
22 |
27 |
28 |
29 |
30 |
35 |
36 |
37 |
38 |
43 |
44 |
45 |
46 | );
47 |
--------------------------------------------------------------------------------
/src/components/SubmitButton.tsx:
--------------------------------------------------------------------------------
1 | import { MouseEvent } from 'react';
2 | import styled from 'styled-components';
3 | import arrowRight from '../assets/images/icons/arrow-right.svg';
4 |
5 | interface Props {
6 | disabled?: boolean;
7 | children: React.ReactNode;
8 | arrow?: boolean;
9 | onClick?: (e: MouseEvent) => void;
10 | }
11 |
12 | const SubmitButton = styled.button`
13 | margin-top: 2em;
14 | padding: 1px;
15 | height: 42px;
16 | font-family: '3616 Grammastile', sans-serif;
17 | font-size: 12px;
18 | line-height: 20px;
19 | color: #fff;
20 | background: linear-gradient(#bcff67 0%, #17ffe3 30%, transparent 70%);
21 | border: none;
22 | transition: all 0.2s;
23 |
24 | &:disabled {
25 | opacity: 0.5;
26 | background: none;
27 | cursor: not-allowed;
28 |
29 | > div {
30 | background: none;
31 | }
32 | }
33 |
34 | &:hover:not([disabled]) {
35 | box-shadow: 0 0 14px 0 #bcff67;
36 | cursor: pointer;
37 | }
38 | `;
39 |
40 | const ButtonBackground = styled.div`
41 | display: flex;
42 | align-items: center;
43 | padding: 0 34px;
44 | height: 40px;
45 | background: linear-gradient(#214c42 20%, #000 80%);
46 | transition: all 0.2s;
47 | `;
48 |
49 | const ArrowRightIcon = styled.img`
50 | margin-top: 0.1em;
51 | margin-left: 0.5em;
52 | `;
53 |
54 | export default ({
55 | disabled = false,
56 | children,
57 | arrow = true,
58 | onClick,
59 | ...rest
60 | }: Props) => (
61 | onClick && onClick(e)}
64 | {...rest}
65 | >
66 |
67 | {children}
68 |
69 | {arrow && }
70 |
71 |
72 | );
73 |
--------------------------------------------------------------------------------
/src/components/Switch.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { useFormContext } from 'react-hook-form';
3 |
4 | export interface Props {
5 | name: string;
6 | id: string;
7 | label: string;
8 | }
9 |
10 | const Container = styled.div``;
11 |
12 | const Option = styled.div<{ isSelected: boolean }>`
13 | display: flex;
14 | align-items: center;
15 | justify-content: center;
16 | margin-right: 1.5em;
17 | width: 264px;
18 | height: 64px;
19 | font-size: 20px;
20 | font-weight: ${({ isSelected }) => (isSelected ? 600 : 400)};
21 | background: ${({ isSelected }) =>
22 | isSelected
23 | ? 'linear-gradient(rgba(188, 255, 103, 0.8), rgba(23, 255, 227, 0.8)) 40%'
24 | : 'none'};
25 | text-transform: uppercase;
26 | border: ${({ isSelected }) =>
27 | isSelected ? '2px solid transparent' : '2px solid #fff'};
28 | border-radius: 10px;
29 |
30 | &:hover {
31 | border: ${({ isSelected }) =>
32 | isSelected ? '2px solid transparent' : '2px solid #17ffe3'};
33 | transition: all 0.2s;
34 | cursor: pointer;
35 | }
36 | `;
37 |
38 | export default ({ name, id, label }: Props) => {
39 | const { watch, setValue } = useFormContext();
40 | const value = watch(name);
41 |
42 | return (
43 | setValue(name, id)}>
44 |
45 |
46 | );
47 | };
48 |
--------------------------------------------------------------------------------
/src/components/SwitchGroup.tsx:
--------------------------------------------------------------------------------
1 | import { useFormContext } from 'react-hook-form';
2 | import styled from 'styled-components';
3 | import FormFieldErrorMessage from './FormFieldErrorMessage';
4 |
5 | interface Props {
6 | name: string;
7 | label: string;
8 | children: React.ReactNode;
9 | }
10 |
11 | const Container = styled.div`
12 | margin-top: 1.7em;
13 | margin-bottom: 0.5em;
14 | `;
15 |
16 | const SwitchGroup = styled.div`
17 | display: flex;
18 | `;
19 |
20 | const Label = styled.h3`
21 | margin-bottom: 1.5em;
22 | text-transform: uppercase;
23 | font-size: 18px;
24 | `;
25 |
26 | export default ({ name, label, children }: Props) => {
27 | const { formState } = useFormContext();
28 |
29 | return (
30 |
31 |
32 |
33 | {children}
34 |
35 |
36 | );
37 | };
38 |
--------------------------------------------------------------------------------
/src/components/TextInput.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { useFormContext, Validate } from 'react-hook-form';
3 | import Label from './Label';
4 | import Input from './Input';
5 | import FormFieldErrorMessage from './FormFieldErrorMessage';
6 | import { getDeep } from '../utils/object';
7 |
8 | interface Props {
9 | name: string;
10 | label?: string;
11 | description?: string;
12 | required?: boolean;
13 | maxLength?: number;
14 | validate?: Record>;
15 | }
16 |
17 | const Description = styled.p`
18 | margin: 0 0 0.5em;
19 | font-size: 14px;
20 | font-weight: 300;
21 | `;
22 |
23 | export default ({
24 | name,
25 | label,
26 | description,
27 | required = false,
28 | maxLength,
29 | validate,
30 | ...rest
31 | }: Props) => {
32 | const { register, formState } = useFormContext();
33 |
34 | return (
35 |
36 | {label && (
37 |
40 | )}
41 |
42 | {description && {description}}
43 |
44 |
53 |
54 |
58 |
59 | );
60 | };
61 |
--------------------------------------------------------------------------------
/src/constants/abis/ERC20.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "constant": true,
4 | "inputs": [],
5 | "name": "name",
6 | "outputs": [
7 | {
8 | "name": "",
9 | "type": "string"
10 | }
11 | ],
12 | "payable": false,
13 | "stateMutability": "view",
14 | "type": "function"
15 | },
16 | {
17 | "constant": false,
18 | "inputs": [
19 | {
20 | "name": "_spender",
21 | "type": "address"
22 | },
23 | {
24 | "name": "_value",
25 | "type": "uint256"
26 | }
27 | ],
28 | "name": "approve",
29 | "outputs": [
30 | {
31 | "name": "",
32 | "type": "bool"
33 | }
34 | ],
35 | "payable": false,
36 | "stateMutability": "nonpayable",
37 | "type": "function"
38 | },
39 | {
40 | "constant": true,
41 | "inputs": [],
42 | "name": "totalSupply",
43 | "outputs": [
44 | {
45 | "name": "",
46 | "type": "uint256"
47 | }
48 | ],
49 | "payable": false,
50 | "stateMutability": "view",
51 | "type": "function"
52 | },
53 | {
54 | "constant": false,
55 | "inputs": [
56 | {
57 | "name": "_from",
58 | "type": "address"
59 | },
60 | {
61 | "name": "_to",
62 | "type": "address"
63 | },
64 | {
65 | "name": "_value",
66 | "type": "uint256"
67 | }
68 | ],
69 | "name": "transferFrom",
70 | "outputs": [
71 | {
72 | "name": "",
73 | "type": "bool"
74 | }
75 | ],
76 | "payable": false,
77 | "stateMutability": "nonpayable",
78 | "type": "function"
79 | },
80 | {
81 | "constant": true,
82 | "inputs": [],
83 | "name": "decimals",
84 | "outputs": [
85 | {
86 | "name": "",
87 | "type": "uint8"
88 | }
89 | ],
90 | "payable": false,
91 | "stateMutability": "view",
92 | "type": "function"
93 | },
94 | {
95 | "constant": true,
96 | "inputs": [
97 | {
98 | "name": "_owner",
99 | "type": "address"
100 | }
101 | ],
102 | "name": "balanceOf",
103 | "outputs": [
104 | {
105 | "name": "balance",
106 | "type": "uint256"
107 | }
108 | ],
109 | "payable": false,
110 | "stateMutability": "view",
111 | "type": "function"
112 | },
113 | {
114 | "constant": true,
115 | "inputs": [],
116 | "name": "symbol",
117 | "outputs": [
118 | {
119 | "name": "",
120 | "type": "string"
121 | }
122 | ],
123 | "payable": false,
124 | "stateMutability": "view",
125 | "type": "function"
126 | },
127 | {
128 | "constant": false,
129 | "inputs": [
130 | {
131 | "name": "_to",
132 | "type": "address"
133 | },
134 | {
135 | "name": "_value",
136 | "type": "uint256"
137 | }
138 | ],
139 | "name": "transfer",
140 | "outputs": [
141 | {
142 | "name": "",
143 | "type": "bool"
144 | }
145 | ],
146 | "payable": false,
147 | "stateMutability": "nonpayable",
148 | "type": "function"
149 | },
150 | {
151 | "constant": true,
152 | "inputs": [
153 | {
154 | "name": "_owner",
155 | "type": "address"
156 | },
157 | {
158 | "name": "_spender",
159 | "type": "address"
160 | }
161 | ],
162 | "name": "allowance",
163 | "outputs": [
164 | {
165 | "name": "",
166 | "type": "uint256"
167 | }
168 | ],
169 | "payable": false,
170 | "stateMutability": "view",
171 | "type": "function"
172 | },
173 | {
174 | "payable": true,
175 | "stateMutability": "payable",
176 | "type": "fallback"
177 | },
178 | {
179 | "anonymous": false,
180 | "inputs": [
181 | {
182 | "indexed": true,
183 | "name": "owner",
184 | "type": "address"
185 | },
186 | {
187 | "indexed": true,
188 | "name": "spender",
189 | "type": "address"
190 | },
191 | {
192 | "indexed": false,
193 | "name": "value",
194 | "type": "uint256"
195 | }
196 | ],
197 | "name": "Approval",
198 | "type": "event"
199 | },
200 | {
201 | "anonymous": false,
202 | "inputs": [
203 | {
204 | "indexed": true,
205 | "name": "from",
206 | "type": "address"
207 | },
208 | {
209 | "indexed": true,
210 | "name": "to",
211 | "type": "address"
212 | },
213 | {
214 | "indexed": false,
215 | "name": "value",
216 | "type": "uint256"
217 | }
218 | ],
219 | "name": "Transfer",
220 | "type": "event"
221 | }
222 | ]
223 |
--------------------------------------------------------------------------------
/src/constants/contracts.ts:
--------------------------------------------------------------------------------
1 | export const NETWORKS: Record = {
2 | mainnet: 1,
3 | matic: 137,
4 | ropsten: 3,
5 | rinkeby: 4,
6 | goerli: 5,
7 | mumbai: 80001,
8 | };
9 |
10 | export const HYPERVIBES_CONTRACT_ADDRESSES: Record = {
11 | [NETWORKS.mainnet]: '0x26887a9F95e1794e52aE1B72Bfa404c1562Eed0E',
12 | [NETWORKS.matic]: '0x26887a9F95e1794e52aE1B72Bfa404c1562Eed0E',
13 | [NETWORKS.ropsten]: '0x91367F9C5a07912a4870ceA43b3893366c05b35c',
14 | [NETWORKS.rinkeby]: '0xafb96b99a0A4eF348115C6BbD99A71d3d4F52Ff1',
15 | [NETWORKS.goerli]: '0x26887a9F95e1794e52aE1B72Bfa404c1562Eed0E',
16 | [NETWORKS.mumbai]: '0x57FBF9E899E17E23d46425e33eE191C8FaD27c28',
17 | };
18 |
--------------------------------------------------------------------------------
/src/constants/formSteps.ts:
--------------------------------------------------------------------------------
1 | export const CREATE_REALM_STEPS = [
2 | {
3 | label: 'Basic Info',
4 | path: 'basic-info',
5 | },
6 | {
7 | label: 'Select Collections',
8 | path: 'select-collections',
9 | },
10 | {
11 | label: 'Set up Infusion',
12 | path: 'set-up-infusion',
13 | },
14 | {
15 | label: 'Set up Claiming',
16 | path: 'set-up-claiming',
17 | },
18 | ];
19 |
--------------------------------------------------------------------------------
/src/constants/rpc.ts:
--------------------------------------------------------------------------------
1 | if (!import.meta.env.REACT_APP_ETHEREUM_RPC_URL) {
2 | throw new Error('must set REACT_APP_ETHEREUM_RPC_URL environment variable');
3 | }
4 |
5 | if (!import.meta.env.REACT_APP_RINKEBY_RPC_URL) {
6 | throw new Error('must set REACT_APP_RINKEBY_RPC_URL environment variable');
7 | }
8 |
9 | export const RPC_URLS: Record = {
10 | 1: String(import.meta.env.REACT_APP_ETHEREUM_RPC_URL ?? ''),
11 | 4: String(import.meta.env.REACT_APP_RINKEBY_RPC_URL ?? ''),
12 | };
13 |
--------------------------------------------------------------------------------
/src/constants/wallets.ts:
--------------------------------------------------------------------------------
1 | import metaMaskIconUrl from '../assets/images/metamask.png';
2 |
3 | interface WalletInfo {
4 | name: string;
5 | connector: string;
6 | iconURL: string;
7 | description: string;
8 | }
9 |
10 | export const SUPPORTED_WALLETS: Record = {
11 | injected: {
12 | name: 'MetaMask',
13 | connector: 'injected',
14 | iconURL: metaMaskIconUrl,
15 | description: 'Easy-to-use browser extension.',
16 | },
17 | };
18 |
--------------------------------------------------------------------------------
/src/hooks/useAutoConnect.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import { useWallet } from 'use-wallet';
3 |
4 | export default () => {
5 | const [tried, setTried] = useState(false);
6 | const wallet = useWallet();
7 |
8 | useEffect(() => {
9 | const cachedWallet = localStorage.getItem('__CONNECTED_WALLET');
10 |
11 | if (wallet.status === 'connected' && wallet.connector) {
12 | localStorage.setItem('__CONNECTED_WALLET', wallet.connector);
13 | setTried(true);
14 | }
15 |
16 | if (!tried && wallet.status === 'disconnected' && cachedWallet) {
17 | wallet.connect(cachedWallet).then(() => setTried(true));
18 | }
19 |
20 | if (!cachedWallet) {
21 | setTried(true);
22 | }
23 |
24 | if (tried && wallet.status === 'disconnected') {
25 | localStorage.removeItem('__CONNECTED_WALLET');
26 | }
27 | }, [wallet, tried]);
28 |
29 | return tried;
30 | };
31 |
--------------------------------------------------------------------------------
/src/hooks/useBrowseNftDetails.ts:
--------------------------------------------------------------------------------
1 | import { gql } from 'graphql-request';
2 | import useHyperVibesSubgraph from './useHyperVibesSubgraph';
3 |
4 | interface QueryResult {
5 | nfts: Array<{
6 | id: string;
7 | tokenId: string;
8 | tokenUri: string | null;
9 | owner: { id: string; address: string };
10 | collection: {
11 | id: string;
12 | address: string;
13 | name: string | null;
14 | symbol: string | null;
15 | };
16 | infusions: Array<{
17 | id: string;
18 | realm: {
19 | id: string;
20 | name: string;
21 | description: string;
22 | };
23 | balance: string;
24 | lastClaimAtTimestamp: string;
25 | events: Array<{
26 | id: string;
27 | eventType: 'INFUSE' | 'CLAIM';
28 | msgSender: { id: string; address: string };
29 | target: { id: string; address: string };
30 | amount: string;
31 | createdAtTimestamp: string;
32 | createdAtTransactionHash: string;
33 | }>;
34 | }>;
35 | }>;
36 | }
37 |
38 | export default (collection: string, tokenId: string, chainId?: number) => {
39 | const query = useHyperVibesSubgraph(
40 | `nfts.${collection}.${tokenId}.browseNftDetails`,
41 | gql`
42 | query getNftDetails($collection: String!, $tokenId: String!) {
43 | nfts(where: { collection: $collection, tokenId: $tokenId }) {
44 | id
45 | tokenId
46 | tokenUri
47 | owner {
48 | id
49 | address
50 | }
51 | collection {
52 | id
53 | address
54 | name
55 | symbol
56 | }
57 | infusions {
58 | id
59 | realm {
60 | id
61 | name
62 | description
63 | }
64 | balance
65 | lastClaimAtTimestamp
66 | events {
67 | id
68 | eventType
69 | msgSender {
70 | address
71 | }
72 | target {
73 | address
74 | }
75 | amount
76 | createdAtTimestamp
77 | createdAtTransactionHash
78 | }
79 | }
80 | }
81 | }
82 | `,
83 | { chainId, variables: { collection, tokenId } }
84 | );
85 |
86 | const [nft] = query.data?.nfts ?? [];
87 |
88 | return { ...query, nft };
89 | };
90 |
--------------------------------------------------------------------------------
/src/hooks/useClaimTokens.ts:
--------------------------------------------------------------------------------
1 | import { useCallback } from 'react';
2 | import { BigNumber } from '@ethersproject/bignumber';
3 | import useHyperVibesContract from './useHyperVibesContract';
4 |
5 | export interface Claim {
6 | realmId: string;
7 | collection: string;
8 | tokenId: string;
9 | amount: BigNumber;
10 | }
11 |
12 | export default () => {
13 | const hyperVibesContract = useHyperVibesContract();
14 |
15 | const claimTokens = useCallback(
16 | async (claim: Claim) => {
17 | return hyperVibesContract?.claim({
18 | realmId: claim.realmId,
19 | collection: claim.collection,
20 | tokenId: BigNumber.from(claim.tokenId),
21 | amount: claim.amount,
22 | });
23 | },
24 | [hyperVibesContract]
25 | );
26 |
27 | return {
28 | claimTokens,
29 | };
30 | };
31 |
--------------------------------------------------------------------------------
/src/hooks/useCollectionInfusions.ts:
--------------------------------------------------------------------------------
1 | import { gql } from 'graphql-request';
2 | import useHyperVibesSubgraph from './useHyperVibesSubgraph';
3 | import { Nft } from '../types';
4 |
5 | interface QueryResult {
6 | nfts: Nft[];
7 | }
8 |
9 | export default (collection: string) => {
10 | const res = useHyperVibesSubgraph(
11 | `collection.${collection}`,
12 | gql`
13 | query {
14 | nfts(
15 | where: {
16 | collection: "${collection}"
17 | }
18 | ) {
19 | tokenId
20 | tokenUri
21 | collection {
22 | address
23 | }
24 | infusions {
25 | balance
26 | lastClaimAtTimestamp
27 | }
28 | }
29 | }
30 | `
31 | );
32 |
33 | return res;
34 | };
35 |
--------------------------------------------------------------------------------
/src/hooks/useContract.ts:
--------------------------------------------------------------------------------
1 | import { useWallet } from 'use-wallet';
2 | import { ContractInterface } from '@ethersproject/contracts';
3 | import { getContract } from '../utils/contract';
4 |
5 | export default (contractAddress: string, abi: ContractInterface) => {
6 | const wallet = useWallet();
7 |
8 | if (!wallet.ethereum || !contractAddress) {
9 | return null;
10 | }
11 |
12 | const contract = getContract(contractAddress, abi, wallet.ethereum);
13 |
14 | return contract;
15 | };
16 |
--------------------------------------------------------------------------------
/src/hooks/useCreateRealmWizard.ts:
--------------------------------------------------------------------------------
1 | import { useCallback } from 'react';
2 | import { createGlobalState } from 'react-hooks-global-state';
3 | import { utils } from 'ethers';
4 | import useHyperVibesContract from './useHyperVibesContract';
5 | import useErc20Contract from './useErc20Contract';
6 |
7 | interface GlobalState {
8 | realmWizard: RealmWizardValues;
9 | }
10 |
11 | const initialState: GlobalState = {
12 | realmWizard: {
13 | name: '',
14 | description: '',
15 | admins: [{ value: '' }],
16 | allowedCollections: [{ value: '' }],
17 | tokenAddress: '',
18 | allowedInfusers: [{ value: '' }],
19 | allowedClaimers: [{ value: '' }],
20 | claimableTokenRate: null,
21 | minTokenInfusionAmount: null,
22 | maxInfusibleTokens: null,
23 | minClaimAmount: null,
24 | requireOwnership: 'no',
25 | allowMultiInfusion: 'yes',
26 | allowAllCollections: 'yes',
27 | allowPublicInfusion: 'yes',
28 | allowPublicClaiming: 'yes',
29 | },
30 | };
31 |
32 | export interface RealmWizardValues {
33 | name: string;
34 | description: string;
35 | admins: { value: string }[];
36 | allowedCollections: { value: string }[];
37 | tokenAddress: string;
38 | allowedInfusers: { value: string }[];
39 | allowedClaimers: { value: string }[];
40 | claimableTokenRate: string | null;
41 | minTokenInfusionAmount: string | null;
42 | maxInfusibleTokens: string | null;
43 | minClaimAmount: string | null;
44 | requireOwnership: 'yes' | 'no';
45 | allowMultiInfusion: 'yes' | 'no';
46 | allowAllCollections: 'yes' | 'no';
47 | allowPublicInfusion: 'yes' | 'no';
48 | allowPublicClaiming: 'yes' | 'no';
49 | }
50 |
51 | const { useGlobalState } = createGlobalState(initialState);
52 |
53 | export default () => {
54 | const [realm, updateRealm] = useGlobalState('realmWizard');
55 | const hyperVibesContract = useHyperVibesContract();
56 | const erc20Contract = useErc20Contract(realm.tokenAddress);
57 |
58 | const createRealm = useCallback(
59 | async (realm: RealmWizardValues) => {
60 | const decimalExponent = await erc20Contract?.decimals();
61 |
62 | return hyperVibesContract?.createRealm({
63 | name: realm.name,
64 | description: realm.description,
65 | admins: realm.admins.map(x => x.value).filter(Boolean),
66 | infusers:
67 | realm.allowPublicInfusion === 'yes'
68 | ? []
69 | : realm.allowedInfusers.map(x => x.value).filter(Boolean),
70 | claimers:
71 | realm.allowPublicClaiming === 'yes'
72 | ? []
73 | : realm.allowedClaimers.map(x => x.value).filter(Boolean),
74 | collections:
75 | realm.allowAllCollections === 'yes'
76 | ? []
77 | : realm.allowedCollections.map(x => x.value).filter(Boolean),
78 | config: {
79 | token: realm.tokenAddress,
80 | dailyRate: utils.parseUnits(
81 | realm.claimableTokenRate || '0',
82 | decimalExponent
83 | ),
84 | constraints: {
85 | minInfusionAmount: utils.parseUnits(
86 | realm.minTokenInfusionAmount || '0',
87 | decimalExponent
88 | ),
89 | maxTokenBalance: utils.parseUnits(
90 | realm.maxInfusibleTokens || '0',
91 | decimalExponent
92 | ),
93 | minClaimAmount: utils.parseUnits(
94 | realm.minClaimAmount || '0',
95 | decimalExponent
96 | ),
97 | requireNftIsOwned: realm.requireOwnership === 'yes',
98 | allowMultiInfuse: realm.allowMultiInfusion === 'yes',
99 | allowAllCollections: realm.allowAllCollections === 'yes',
100 | allowPublicInfusion: realm.allowPublicInfusion === 'yes',
101 | allowPublicClaiming: realm.allowPublicClaiming === 'yes',
102 | },
103 | },
104 | });
105 | },
106 | [hyperVibesContract, erc20Contract]
107 | );
108 |
109 | return {
110 | realm,
111 | updateRealm: (fields: RealmWizardValues) =>
112 | updateRealm({ ...realm, ...fields }),
113 | createRealm,
114 | resetRealm: () => updateRealm(initialState.realmWizard),
115 | };
116 | };
117 |
--------------------------------------------------------------------------------
/src/hooks/useCurrentMinedTokens.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import { BigNumber } from '@ethersproject/bignumber';
3 | import useHyperVibesContract from './useHyperVibesContract';
4 |
5 | export default (realmId: string, collection: string, tokenId: string) => {
6 | const hyperVibesContract = useHyperVibesContract();
7 | const [currentMinedTokens, setCurrentMinedTokens] = useState(
8 | BigNumber.from(0)
9 | );
10 |
11 | useEffect(() => {
12 | (async () => {
13 | try {
14 | if (currentMinedTokens.isZero()) {
15 | const res = await hyperVibesContract?.currentMinedTokens(
16 | realmId,
17 | collection,
18 | tokenId
19 | );
20 | setCurrentMinedTokens(BigNumber.from(res));
21 | }
22 | } catch (e) {
23 | setCurrentMinedTokens(BigNumber.from(0));
24 | }
25 | })();
26 | }, [hyperVibesContract, currentMinedTokens, realmId, collection, tokenId]);
27 |
28 | return currentMinedTokens;
29 | };
30 |
--------------------------------------------------------------------------------
/src/hooks/useEns.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import { useWallet } from 'use-wallet';
3 | import { getENS, ResolvedENS } from 'get-ens';
4 | import { JsonRpcProvider } from '@ethersproject/providers';
5 | import { RPC_URLS } from '../constants/rpc';
6 |
7 | export default (name: string) => {
8 | const { chainId = 1 } = useWallet();
9 | const [ens, setEns] = useState>({});
10 | const rpcUrl = RPC_URLS[chainId];
11 |
12 | useEffect(() => {
13 | (async () => {
14 | try {
15 | if (name.endsWith('.eth')) {
16 | const provider = new JsonRpcProvider(rpcUrl);
17 | const res = await getENS(provider)(name);
18 | setEns(res);
19 | } else {
20 | setEns({});
21 | }
22 | } catch (e) {
23 | setEns({});
24 | }
25 | })();
26 | }, [name, rpcUrl]);
27 |
28 | return ens;
29 | };
30 |
--------------------------------------------------------------------------------
/src/hooks/useErc20Allowance.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import { useWallet } from 'use-wallet';
3 | import { isAddress } from '@ethersproject/address';
4 | import { BigNumber } from '@ethersproject/bignumber';
5 | import { useLazyErc20Contract } from './useErc20Contract';
6 | import { HYPERVIBES_CONTRACT_ADDRESSES } from '../constants/contracts';
7 |
8 | export default (tokenAddress: string) => {
9 | const getErc20Contract = useLazyErc20Contract();
10 | const { account, chainId } = useWallet();
11 | const [allowance, setAllowance] = useState(BigNumber.from(0));
12 |
13 | useEffect(() => {
14 | (async () => {
15 | try {
16 | if (isAddress(tokenAddress) && account && chainId && getErc20Contract) {
17 | const res = await getErc20Contract(tokenAddress)?.allowance(
18 | account,
19 | HYPERVIBES_CONTRACT_ADDRESSES[chainId]
20 | );
21 |
22 | setAllowance(res);
23 | }
24 | } catch (e) {
25 | setAllowance(BigNumber.from(0));
26 | }
27 | })();
28 | }, [tokenAddress, account, chainId, getErc20Contract]);
29 |
30 | return {
31 | allowance,
32 | };
33 | };
34 |
--------------------------------------------------------------------------------
/src/hooks/useErc20ApproveAllowance.ts:
--------------------------------------------------------------------------------
1 | import { useCallback } from 'react';
2 | import { useWallet } from 'use-wallet';
3 | import { utils } from 'ethers';
4 | import { isAddress } from '@ethersproject/address';
5 | import { useLazyErc20Contract } from './useErc20Contract';
6 | import { HYPERVIBES_CONTRACT_ADDRESSES } from '../constants/contracts';
7 |
8 | export default () => {
9 | const getErc20Contract = useLazyErc20Contract();
10 | const { chainId } = useWallet();
11 |
12 | const approveAllowance = useCallback(
13 | async (tokenAddress: string, amount: string) => {
14 | if (chainId && isAddress(tokenAddress) && getErc20Contract) {
15 | const decimalExponent = await getErc20Contract(
16 | tokenAddress
17 | )?.decimals();
18 |
19 | return await getErc20Contract(tokenAddress)?.approve(
20 | HYPERVIBES_CONTRACT_ADDRESSES[chainId],
21 | utils.parseUnits(amount, decimalExponent)
22 | );
23 | }
24 | },
25 | [chainId, getErc20Contract]
26 | );
27 |
28 | return {
29 | approveAllowance,
30 | };
31 | };
32 |
--------------------------------------------------------------------------------
/src/hooks/useErc20Contract.ts:
--------------------------------------------------------------------------------
1 | import { useCallback } from 'react';
2 | import { useWallet } from 'use-wallet';
3 | import useContract from './useContract';
4 | import { getContract } from '../utils/contract';
5 | import erc20Abi from '../constants/abis/ERC20.json';
6 |
7 | export default (tokenAddress: string) => {
8 | const contract = useContract(tokenAddress, erc20Abi);
9 |
10 | return contract;
11 | };
12 |
13 | export const useLazyErc20Contract = () => {
14 | const wallet = useWallet();
15 |
16 | const fn = useCallback(
17 | (tokenAddress: string) =>
18 | getContract(tokenAddress, erc20Abi, wallet.ethereum),
19 | [wallet]
20 | );
21 |
22 | if (!wallet.ethereum) {
23 | return null;
24 | }
25 |
26 | return fn;
27 | };
28 |
--------------------------------------------------------------------------------
/src/hooks/useErc20Decimals.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import useErc20Contract from './useErc20Contract';
3 |
4 | export default (tokenAddress: string) => {
5 | const contract = useErc20Contract(tokenAddress);
6 | const [decimals, setDecimals] = useState();
7 |
8 | useEffect(() => {
9 | (async () => {
10 | try {
11 | if (!decimals) {
12 | const decimalExponent = await contract?.decimals();
13 | setDecimals(decimalExponent);
14 | }
15 | } catch (e) {
16 | setDecimals(undefined);
17 | }
18 | })();
19 | }, [contract, decimals, setDecimals]);
20 |
21 | return decimals;
22 | };
23 |
--------------------------------------------------------------------------------
/src/hooks/useErc20TokenDetails.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import { isAddress } from '@ethersproject/address';
3 | import { useLazyErc20Contract } from './useErc20Contract';
4 |
5 | export default (tokenAddress: string) => {
6 | const getErc20Contract = useLazyErc20Contract();
7 | const [tokenSymbol, setTokenSymbol] = useState('');
8 |
9 | useEffect(() => {
10 | (async () => {
11 | try {
12 | if (isAddress(tokenAddress) && getErc20Contract) {
13 | const symbol = await getErc20Contract(tokenAddress)?.symbol();
14 | setTokenSymbol(symbol);
15 | } else {
16 | setTokenSymbol('');
17 | }
18 | } catch (e) {
19 | setTokenSymbol('');
20 | }
21 | })();
22 | }, [tokenAddress, getErc20Contract]);
23 |
24 | return {
25 | symbol: tokenSymbol,
26 | };
27 | };
28 |
--------------------------------------------------------------------------------
/src/hooks/useErc721Contract.ts:
--------------------------------------------------------------------------------
1 | import useContract from './useContract';
2 | import erc721Abi from '../constants/abis/ERC721.json';
3 |
4 | export default (tokenAddress: string) => {
5 | const contract = useContract(tokenAddress, erc721Abi);
6 |
7 | return contract;
8 | };
9 |
--------------------------------------------------------------------------------
/src/hooks/useErc721IsApproved.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import useErc721Contract from './useErc721Contract';
3 |
4 | export default (
5 | tokenAddress: string,
6 | tokenId: string,
7 | address: string | null
8 | ) => {
9 | const contract = useErc721Contract(tokenAddress);
10 | const [isApproved, setIsApproved] = useState(false);
11 |
12 | useEffect(() => {
13 | (async () => {
14 | try {
15 | if (contract && address) {
16 | const approved = await contract?.getApproved(tokenId);
17 |
18 | const owner = await contract?.ownerOf(tokenId);
19 | const isApprovedForAll = await contract?.isApprovedForAll(
20 | owner,
21 | address
22 | );
23 |
24 | setIsApproved(
25 | owner === address || approved === address || isApprovedForAll
26 | );
27 | }
28 | } catch (e) {
29 | setIsApproved(false);
30 | }
31 | })();
32 | }, [address, contract, tokenId, setIsApproved]);
33 |
34 | return isApproved;
35 | };
36 |
--------------------------------------------------------------------------------
/src/hooks/useErc721OwnerOf.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, useCallback } from 'react';
2 | import useErc721Contract from './useErc721Contract';
3 |
4 | export default (tokenAddress: string, tokenId?: string) => {
5 | const contract = useErc721Contract(tokenAddress);
6 | const [ownerOf, setOwnerOf] = useState('');
7 |
8 | useEffect(() => {
9 | (async () => {
10 | try {
11 | if (contract && tokenId) {
12 | const res = await contract?.ownerOf(tokenId);
13 |
14 | setOwnerOf(res);
15 | }
16 | } catch (e) {
17 | setOwnerOf('');
18 | }
19 | })();
20 | }, [contract, tokenId, setOwnerOf]);
21 |
22 | return ownerOf;
23 | };
24 |
25 | export const useLazyErc721OwnerOf = (tokenAddress: string) => {
26 | const contract = useErc721Contract(tokenAddress);
27 |
28 | const fn = useCallback(
29 | async (tokenId: string) => contract?.ownerOf(tokenId),
30 | [contract]
31 | );
32 |
33 | return fn;
34 | };
35 |
--------------------------------------------------------------------------------
/src/hooks/useHyperVibesContract.ts:
--------------------------------------------------------------------------------
1 | import { useWallet } from 'use-wallet';
2 | import useContract from './useContract';
3 | import hyperVibesAbi from '../constants/abis/HyperVIBES.json';
4 | import { HYPERVIBES_CONTRACT_ADDRESSES } from '../constants/contracts';
5 |
6 | export default () => {
7 | const { chainId = 1 } = useWallet();
8 | const contract = useContract(
9 | HYPERVIBES_CONTRACT_ADDRESSES[chainId],
10 | hyperVibesAbi
11 | );
12 |
13 | return contract;
14 | };
15 |
--------------------------------------------------------------------------------
/src/hooks/useHyperVibesSubgraph.ts:
--------------------------------------------------------------------------------
1 | import { useQuery, UseQueryOptions } from 'react-query';
2 | import { request } from 'graphql-request';
3 | import { useWallet } from 'use-wallet';
4 | import { NETWORKS } from '../constants/contracts';
5 |
6 | const HYPERVIBES_SUBGRAPH_ENDPOINTS: Record = {
7 | [NETWORKS.mainnet]:
8 | 'https://api.thegraph.com/subgraphs/name/r-group-devs/hypervibes-mainnet',
9 | [NETWORKS.matic]:
10 | 'https://api.thegraph.com/subgraphs/name/r-group-devs/hypervibes-matic',
11 | [NETWORKS.ropsten]:
12 | 'https://api.thegraph.com/subgraphs/name/r-group-devs/hypervibes-ropsten',
13 | [NETWORKS.rinkeby]:
14 | 'https://api.thegraph.com/subgraphs/name/r-group-devs/hypervibes-rinkeby',
15 | [NETWORKS.goerli]:
16 | 'https://api.thegraph.com/subgraphs/name/r-group-devs/hypervibes-goerli',
17 | [NETWORKS.mumbai]:
18 | 'https://api.thegraph.com/subgraphs/name/r-group-devs/hypervibes-mumbai',
19 | };
20 |
21 | interface QueryOptions {
22 | chainId?: number;
23 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
24 | variables?: Record;
25 | }
26 |
27 | export default (
28 | queryName: string,
29 | query: string,
30 | queryOptions?: Omit<
31 | UseQueryOptions,
32 | 'queryKey' | 'queryFn'
33 | > &
34 | QueryOptions
35 | ) => {
36 | const { chainId: connectedChainId } = useWallet();
37 | const chainId = queryOptions?.chainId ?? connectedChainId ?? 1;
38 |
39 | return useQuery(
40 | // including chain id in the cache key to ensure we don't mix cached queries
41 | // across chains
42 | `${chainId}:${queryName}`,
43 | async () =>
44 | request(
45 | HYPERVIBES_SUBGRAPH_ENDPOINTS[chainId],
46 | query,
47 | queryOptions?.variables
48 | ),
49 | queryOptions
50 | );
51 | };
52 |
--------------------------------------------------------------------------------
/src/hooks/useInfuseNft.ts:
--------------------------------------------------------------------------------
1 | import { useCallback } from 'react';
2 | import { BigNumber } from '@ethersproject/bignumber';
3 | import useHyperVibesContract from './useHyperVibesContract';
4 |
5 | export interface Infusion {
6 | realmId: string;
7 | collection: string;
8 | tokenId: string;
9 | infuser: string;
10 | amount: BigNumber;
11 | }
12 |
13 | export default () => {
14 | const hyperVibesContract = useHyperVibesContract();
15 |
16 | const infuseNft = useCallback(
17 | async (infusion: Infusion) => {
18 | return hyperVibesContract?.infuse({
19 | realmId: infusion.realmId,
20 | collection: infusion.collection,
21 | tokenId: BigNumber.from(infusion.tokenId),
22 | infuser: infusion.infuser,
23 | amount: infusion.amount,
24 | comment: '',
25 | });
26 | },
27 | [hyperVibesContract]
28 | );
29 |
30 | return {
31 | infuseNft,
32 | };
33 | };
34 |
--------------------------------------------------------------------------------
/src/hooks/useListRealmNfts.ts:
--------------------------------------------------------------------------------
1 | import { gql } from 'graphql-request';
2 | import useHyperVibesSubgraph from './useHyperVibesSubgraph';
3 | import { Realm } from '../types';
4 |
5 | interface QueryResult {
6 | realm: Realm;
7 | }
8 |
9 | export default (realmId: string, chainId?: number) => {
10 | const res = useHyperVibesSubgraph(
11 | `realm.${realmId}.listRealmNts`,
12 | gql`
13 | query getRealmNfts($realmId: String!) {
14 | realm(id: $realmId) {
15 | id
16 | name
17 | infusions {
18 | id
19 | nft {
20 | id
21 | tokenId
22 | tokenUri
23 | collection {
24 | id
25 | address
26 | name
27 | symbol
28 | }
29 | }
30 | balance
31 | lastClaimAtTimestamp
32 | events {
33 | id
34 | eventType
35 | createdAtTimestamp
36 | createdAtTransactionHash
37 | msgSender {
38 | id
39 | address
40 | }
41 | target {
42 | id
43 | address
44 | }
45 | amount
46 | }
47 | }
48 | }
49 | }
50 | `,
51 | { chainId, variables: { realmId } }
52 | );
53 |
54 | return res;
55 | };
56 |
--------------------------------------------------------------------------------
/src/hooks/useListRealms.ts:
--------------------------------------------------------------------------------
1 | import { gql } from 'graphql-request';
2 | import useHyperVibesSubgraph from './useHyperVibesSubgraph';
3 | import { Realm } from '../types';
4 |
5 | interface QueryResult {
6 | realms: Realm[];
7 | }
8 |
9 | export default (chainId?: number) => {
10 | const res = useHyperVibesSubgraph(
11 | `realms.list`,
12 | gql`
13 | query {
14 | realms {
15 | id
16 | name
17 | description
18 | createdAtTimestamp
19 | token {
20 | id
21 | address
22 | name
23 | symbol
24 | decimals
25 | }
26 | infusions(first: 1) {
27 | id
28 | nft {
29 | id
30 | tokenId
31 | tokenUri
32 | collection {
33 | id
34 | address
35 | }
36 | }
37 | }
38 | }
39 | }
40 | `,
41 | { chainId }
42 | );
43 |
44 | return res;
45 | };
46 |
--------------------------------------------------------------------------------
/src/hooks/useMetadata.ts:
--------------------------------------------------------------------------------
1 | import { useQuery } from 'react-query';
2 | import { Metadata, resolveMetadata } from '../hypervibes/nft';
3 |
4 | export default (tokenUri: string | undefined | null) => {
5 | const query = useQuery(
6 | `metadata:${tokenUri}`,
7 | () => (tokenUri ? resolveMetadata(tokenUri) : Promise.resolve(undefined))
8 | );
9 |
10 | return {
11 | ...query,
12 | metadata: query.data,
13 | };
14 | };
15 |
--------------------------------------------------------------------------------
/src/hooks/useMyInfusedNfts.ts:
--------------------------------------------------------------------------------
1 | import { useWallet } from 'use-wallet';
2 | import { gql } from 'graphql-request';
3 | import useHyperVibesSubgraph from './useHyperVibesSubgraph';
4 | import { Nft } from '../types';
5 |
6 | interface QueryResult {
7 | account: {
8 | ownedNFTs: Nft[];
9 | };
10 | }
11 |
12 | export default () => {
13 | const { account } = useWallet();
14 |
15 | const res = useHyperVibesSubgraph(
16 | 'infusedNfts',
17 | gql`
18 | query {
19 | account(id: "${account?.toLowerCase()}") {
20 | id
21 | ownedNFTs {
22 | collection {
23 | id
24 | address
25 | }
26 | tokenId
27 | tokenUri
28 | infusions {
29 | realm {
30 | id
31 | }
32 | }
33 | }
34 | }
35 | }
36 | `,
37 | { enabled: !!account }
38 | );
39 |
40 | return {
41 | data: {
42 | infusedNfts: res.data?.account?.ownedNFTs,
43 | },
44 | };
45 | };
46 |
--------------------------------------------------------------------------------
/src/hooks/useMyInfusibleRealms.ts:
--------------------------------------------------------------------------------
1 | import { useWallet } from 'use-wallet';
2 | import { gql } from 'graphql-request';
3 | import useHyperVibesSubgraph from './useHyperVibesSubgraph';
4 | import { Realm } from '../types';
5 |
6 | interface QueryResult {
7 | account: {
8 | realmInfusers: { realm: Realm }[];
9 | };
10 | realms: Realm[];
11 | }
12 |
13 | export default () => {
14 | const { account } = useWallet();
15 |
16 | const res = useHyperVibesSubgraph(
17 | 'infusibleRealms',
18 | gql`
19 | query {
20 | account(id: "${account?.toLowerCase()}") {
21 | id
22 | realmInfusers {
23 | id
24 | realm {
25 | id
26 | name
27 | description
28 | requireNftIsOwned
29 | infusions {
30 | nft {
31 | tokenUri
32 | }
33 | }
34 | }
35 | }
36 | }
37 | realms(where: { allowPublicInfusion: true }) {
38 | id
39 | name
40 | description
41 | requireNftIsOwned
42 | infusions {
43 | nft {
44 | tokenUri
45 | }
46 | }
47 | }
48 | }
49 | `,
50 | { enabled: !!account }
51 | );
52 |
53 | const data = [
54 | ...(res.data?.account?.realmInfusers.map(
55 | infusibleRealm => infusibleRealm.realm
56 | ) || []),
57 | ...(res.data?.realms || []),
58 | ];
59 |
60 | return {
61 | ...res,
62 | data: res.data ? data : null,
63 | };
64 | };
65 |
--------------------------------------------------------------------------------
/src/hooks/useMyOwnedNfts.ts:
--------------------------------------------------------------------------------
1 | import { useWallet } from 'use-wallet';
2 | import { gql } from 'graphql-request';
3 | import useHyperVibesSubgraph from './useHyperVibesSubgraph';
4 | import { Nft } from '../types';
5 |
6 | interface QueryResult {
7 | account: {
8 | ownedNFTs: Nft[];
9 | };
10 | }
11 |
12 | export default () => {
13 | const { account } = useWallet();
14 |
15 | const res = useHyperVibesSubgraph(
16 | 'infusedNfts',
17 | gql`
18 | query {
19 | account(id: "${account?.toLowerCase()}") {
20 | id
21 | ownedNFTs {
22 | collection {
23 | id
24 | address
25 | }
26 | tokenId
27 | infusions {
28 | realm {
29 | id
30 | }
31 | }
32 | }
33 | }
34 | }
35 | `,
36 | { enabled: !!account }
37 | );
38 |
39 | return {
40 | data: {
41 | infusedNfts: res.data?.account.ownedNFTs,
42 | },
43 | };
44 | };
45 |
--------------------------------------------------------------------------------
/src/hooks/useMyRealmAdmins.ts:
--------------------------------------------------------------------------------
1 | import { useWallet } from 'use-wallet';
2 | import { gql } from 'graphql-request';
3 | import useHyperVibesSubgraph from './useHyperVibesSubgraph';
4 |
5 | export default () => {
6 | const { account } = useWallet();
7 |
8 | const data = useHyperVibesSubgraph(
9 | 'realms',
10 | gql`
11 | query {
12 | account(id: "${account?.toLowerCase()}") {
13 | realmAdmins {
14 | realm {
15 | id
16 | name
17 | description
18 | createdAt
19 | token {
20 | address
21 | }
22 | admins {
23 | createdAt
24 | account {
25 | id
26 | }
27 | }
28 | infusers {
29 | createdAt
30 | account {
31 | id
32 | }
33 | }
34 | }
35 | }
36 | }
37 | }
38 | `,
39 | { enabled: !!account }
40 | );
41 |
42 | return data;
43 | };
44 |
--------------------------------------------------------------------------------
/src/hooks/useNftDetails.ts:
--------------------------------------------------------------------------------
1 | import { gql } from 'graphql-request';
2 | import useHyperVibesSubgraph from './useHyperVibesSubgraph';
3 | import { Nft } from '../types';
4 |
5 | interface QueryResult {
6 | nfts: Nft[];
7 | }
8 |
9 | export default (realmId: string, collection: string, tokenId: string) => {
10 | const res = useHyperVibesSubgraph(
11 | `collection.${collection}.${tokenId}`,
12 | gql`
13 | query {
14 | nfts(
15 | where: {
16 | collection: "${collection}"
17 | tokenId: "${tokenId}"
18 | }
19 | ) {
20 | tokenUri
21 | infusions (where: { realm: "${realmId}" }) {
22 | balance
23 | lastClaimAtTimestamp
24 | }
25 | }
26 | }
27 | `
28 | );
29 |
30 | const lastClaimAtTimestamp =
31 | res.data?.nfts[0]?.infusions[0]?.lastClaimAtTimestamp;
32 |
33 | return {
34 | data: {
35 | tokenUri: res.data?.nfts[0]?.tokenUri,
36 | lastClaimAtTimestamp,
37 | },
38 | };
39 | };
40 |
--------------------------------------------------------------------------------
/src/hooks/useRealmCollections.ts:
--------------------------------------------------------------------------------
1 | import { gql } from 'graphql-request';
2 | import useHyperVibesSubgraph from './useHyperVibesSubgraph';
3 | import { Realm } from '../types';
4 |
5 | interface QueryResult {
6 | realms: Realm[];
7 | }
8 |
9 | export default (id: string) => {
10 | const res = useHyperVibesSubgraph(
11 | `realm.${id}.realmCollections`,
12 | gql`
13 | query {
14 | realms(where: { id: "${id}" }) {
15 | id
16 | name
17 | description
18 | allowAllCollections
19 | realmCollections {
20 | id
21 | collection {
22 | id
23 | name
24 | address
25 | nfts {
26 | tokenUri
27 | }
28 | }
29 | }
30 | }
31 | }
32 | `
33 | );
34 |
35 | return {
36 | data: {
37 | id: res.data?.realms[0]?.id,
38 | name: res.data?.realms[0]?.name,
39 | description: res.data?.realms[0]?.description,
40 | allowAllCollections: res.data?.realms[0]?.allowAllCollections,
41 | collections:
42 | res.data?.realms[0]?.realmCollections.map(
43 | realmCollection => realmCollection.collection
44 | ) || [],
45 | },
46 | };
47 | };
48 |
--------------------------------------------------------------------------------
/src/hooks/useRealmDetails.ts:
--------------------------------------------------------------------------------
1 | import { gql } from 'graphql-request';
2 | import { BigNumber } from '@ethersproject/bignumber';
3 | import useHyperVibesSubgraph from './useHyperVibesSubgraph';
4 | import { Realm } from '../types';
5 |
6 | interface QueryResult {
7 | realms: Realm[];
8 | }
9 |
10 | export default (id: string) => {
11 | const res = useHyperVibesSubgraph(
12 | `realm.${id}.realmDetails`,
13 | gql`
14 | query {
15 | realms(where: { id: "${id}" }) {
16 | id
17 | name
18 | description
19 | token {
20 | address
21 | }
22 | minClaimAmount
23 | minInfusionAmount
24 | maxTokenBalance
25 | requireNftIsOwned
26 | allowAllCollections
27 | allowPublicInfusion
28 | allowPublicClaiming
29 | allowMultiInfuse
30 | realmInfusers {
31 | account {
32 | id
33 | address
34 | }
35 | }
36 | realmCollections {
37 | collection {
38 | address
39 | }
40 | }
41 | realmClaimers {
42 | account {
43 | id
44 | address
45 | }
46 | }
47 | }
48 | }
49 | `
50 | );
51 |
52 | const realm = res.data?.realms[0];
53 | const infusers = realm?.realmInfusers.map(infuser => infuser.account.address);
54 | const collections = realm?.realmCollections.map(
55 | collection => collection.collection.address
56 | );
57 | const claimers = realm?.realmClaimers.map(claimer => claimer.account.address);
58 |
59 | return {
60 | data: {
61 | id: realm?.id,
62 | name: realm?.name,
63 | description: realm?.description,
64 | token: realm?.token,
65 | minClaimAmount: BigNumber.from(realm?.minClaimAmount || 0),
66 | minInfusionAmount: realm?.minInfusionAmount,
67 | maxTokenBalance: BigNumber.from(realm?.maxTokenBalance || 0),
68 | requireNftIsOwned: realm?.requireNftIsOwned,
69 | allowAllCollections: realm?.allowAllCollections,
70 | allowPublicInfusion: realm?.allowPublicInfusion,
71 | allowPublicClaiming: realm?.allowPublicClaiming,
72 | allowMultiInfuse: realm?.allowMultiInfuse,
73 | infusers,
74 | collections,
75 | claimers,
76 | },
77 | };
78 | };
79 |
--------------------------------------------------------------------------------
/src/hooks/useRealms.ts:
--------------------------------------------------------------------------------
1 | import { useWallet } from 'use-wallet';
2 | import { gql } from 'graphql-request';
3 | import useHyperVibesSubgraph from './useHyperVibesSubgraph';
4 | import { Realm } from '../types';
5 |
6 | interface QueryResult {
7 | realms: Realm[];
8 | }
9 |
10 | export default () => {
11 | const { account } = useWallet();
12 |
13 | const res = useHyperVibesSubgraph(
14 | 'realms',
15 | gql`
16 | query {
17 | realms {
18 | id
19 | name
20 | description
21 | infusions {
22 | nft {
23 | tokenUri
24 | }
25 | }
26 | }
27 | }
28 | `,
29 | { enabled: !!account }
30 | );
31 |
32 | return {
33 | ...res,
34 | data: res.data?.realms,
35 | };
36 | };
37 |
--------------------------------------------------------------------------------
/src/hypervibes/constants.ts:
--------------------------------------------------------------------------------
1 | // https://chainlist.org
2 | export const SUPPORTED_NETWORKS = {
3 | mainnet: 1,
4 | matic: 137,
5 | fantom: 250,
6 | arbitrum: 42161,
7 | ropsten: 3,
8 | rinkeby: 4,
9 | goerli: 5,
10 | mumbai: 80001,
11 | };
12 |
13 | // https://docs.hypervibes.xyz/developers/links-and-repos#contract-addresses
14 | export const CONTRACT_ADDRESSES: Record = {
15 | [SUPPORTED_NETWORKS.mainnet]: '0x26887a9F95e1794e52aE1B72Bfa404c1562Eed0E',
16 | [SUPPORTED_NETWORKS.matic]: '0x26887a9F95e1794e52aE1B72Bfa404c1562Eed0E',
17 | [SUPPORTED_NETWORKS.fantom]: '0x26887a9F95e1794e52aE1B72Bfa404c1562Eed0E',
18 | [SUPPORTED_NETWORKS.arbitrum]: '0x26887a9F95e1794e52aE1B72Bfa404c1562Eed0E',
19 | [SUPPORTED_NETWORKS.ropsten]: '0x91367F9C5a07912a4870ceA43b3893366c05b35c',
20 | [SUPPORTED_NETWORKS.rinkeby]: '0xafb96b99a0A4eF348115C6BbD99A71d3d4F52Ff1',
21 | [SUPPORTED_NETWORKS.goerli]: '0x26887a9F95e1794e52aE1B72Bfa404c1562Eed0E',
22 | [SUPPORTED_NETWORKS.mumbai]: '0x57FBF9E899E17E23d46425e33eE191C8FaD27c28',
23 | };
24 |
25 | // https://docs.hypervibes.xyz/developers/links-and-repos#subgraphs
26 | export const SUBGRAPH_ENDPOINTS: Record = {
27 | [SUPPORTED_NETWORKS.mainnet]:
28 | 'https://api.thegraph.com/subgraphs/name/r-group-devs/hypervibes-mainnet',
29 | [SUPPORTED_NETWORKS.matic]:
30 | 'https://api.thegraph.com/subgraphs/name/r-group-devs/hypervibes-matic',
31 | [SUPPORTED_NETWORKS.fantom]:
32 | 'https://api.thegraph.com/subgraphs/name/r-group-devs/hypervibes-fantom',
33 | [SUPPORTED_NETWORKS.arbitrum]:
34 | 'https://api.thegraph.com/subgraphs/name/r-group-devs/hypervibes-arbitrum-one',
35 | [SUPPORTED_NETWORKS.ropsten]:
36 | 'https://api.thegraph.com/subgraphs/name/r-group-devs/hypervibes-ropsten',
37 | [SUPPORTED_NETWORKS.rinkeby]:
38 | 'https://api.thegraph.com/subgraphs/name/r-group-devs/hypervibes-rinkeby',
39 | [SUPPORTED_NETWORKS.goerli]:
40 | 'https://api.thegraph.com/subgraphs/name/r-group-devs/hypervibes-goerli',
41 | [SUPPORTED_NETWORKS.mumbai]:
42 | 'https://api.thegraph.com/subgraphs/name/r-group-devs/hypervibes-mumbai',
43 | };
44 |
45 | export const RPC_ENDPOINTS: Record = {
46 | [SUPPORTED_NETWORKS.mainnet]: import.meta.env.REACT_APP_ETHEREUM_RPC_URL,
47 | [SUPPORTED_NETWORKS.matic]: import.meta.env.REACT_APP_POLYGON_RPC_URL,
48 | [SUPPORTED_NETWORKS.fantom]: import.meta.env.REACT_APP_FANTOM_RPC_URL,
49 | [SUPPORTED_NETWORKS.arbitrum]: import.meta.env.REACT_APP_ARBITRUM_RPC_URL,
50 | [SUPPORTED_NETWORKS.ropsten]: import.meta.env.REACT_APP_ROPSTEN_RPC_URL,
51 | [SUPPORTED_NETWORKS.rinkeby]: import.meta.env.REACT_APP_RINKEBY_RPC_URL,
52 | [SUPPORTED_NETWORKS.goerli]: import.meta.env.REACT_APP_GOERLI_RPC_URL,
53 | [SUPPORTED_NETWORKS.mumbai]: import.meta.env.REACT_APP_MUMBAI_RPC_URL,
54 | };
55 |
56 | interface ChainInfo {
57 | /** hv contract address */
58 | contract: string;
59 |
60 | /** rpc endpoint url */
61 | rpc: string;
62 |
63 | /** subgraph endpoint url */
64 | subgraph: string;
65 | }
66 |
67 | /**
68 | * get config / endpoint info by chain id
69 | */
70 | export const getChainInfo = (chainId: number): ChainInfo => {
71 | if (!Object.values(SUPPORTED_NETWORKS).includes(chainId)) {
72 | throw new Error(`unsupported network ${chainId}`);
73 | }
74 |
75 | const contract = CONTRACT_ADDRESSES[chainId];
76 | if (contract == null) {
77 | throw new Error(`missing contract address for network ${chainId}`);
78 | }
79 |
80 | const rpc = RPC_ENDPOINTS[chainId];
81 | if (typeof rpc != 'string') {
82 | throw new Error(`missing rpc node for network ${chainId}`);
83 | }
84 |
85 | const subgraph = SUBGRAPH_ENDPOINTS[chainId];
86 | if (subgraph == null) {
87 | throw new Error(`missing subgraph for network ${chainId}`);
88 | }
89 |
90 | return { contract, rpc, subgraph };
91 | };
92 |
--------------------------------------------------------------------------------
/src/hypervibes/contract.ts:
--------------------------------------------------------------------------------
1 | import { StaticJsonRpcProvider } from '@ethersproject/providers';
2 | import { BigNumber } from 'ethers';
3 | import {
4 | Provider as MulticallProvider,
5 | Contract as MulticallContract,
6 | } from 'ethers-multicall';
7 | import HYPERVIBES from './abi/HyperVIBES.json';
8 | import { getChainInfo } from './constants';
9 |
10 | export interface GetTokenViewInput {
11 | realmId: BigNumber;
12 | collection: string;
13 | tokenId: BigNumber;
14 | }
15 |
16 | export interface GetTokenViewOutput {
17 | realmId: BigNumber;
18 | collection: string;
19 | tokenId: BigNumber;
20 | // if null, there is nothing infused
21 | view: null | {
22 | lastClaimAt: number;
23 | balance: BigNumber;
24 | currentMinedTokens: BigNumber;
25 | };
26 | }
27 |
28 | /**
29 | * get infusion data about a batch of (realm,nft,tokenId) tuples directly from
30 | * the blockchain.
31 | *
32 | * Will not throw if invalid token or non-infused token, view will just be null.
33 | * Will throw on an invalid realm id
34 | */
35 | export const batchGetTokenData = async (
36 | batch: GetTokenViewInput[],
37 | chainId: number
38 | ): Promise => {
39 | const { contract, rpc } = getChainInfo(chainId);
40 |
41 | const hv = new MulticallContract(contract, HYPERVIBES);
42 | const provider = new MulticallProvider(
43 | new StaticJsonRpcProvider(rpc),
44 | chainId
45 | );
46 |
47 | const calls = batch.flatMap(d => {
48 | const getTokenView = hv.tokenData(d.realmId, d.collection, d.tokenId);
49 | const getCurrent = hv.currentMinedTokens(
50 | d.realmId,
51 | d.collection,
52 | d.tokenId
53 | );
54 | return [getTokenView, getCurrent];
55 | });
56 |
57 | const data = await provider.all(calls);
58 |
59 | const projected = batch.map((datum, idx) => {
60 | const { balance, lastClaimAt } = data[idx * 2];
61 | const currentMinedTokens = data[idx * 2 + 1];
62 | const view = lastClaimAt.eq(0)
63 | ? null
64 | : { balance, lastClaimAt, currentMinedTokens };
65 | return { ...datum, view };
66 | });
67 |
68 | return projected;
69 | };
70 |
--------------------------------------------------------------------------------
/src/hypervibes/dataloaders.ts:
--------------------------------------------------------------------------------
1 | import 'setimmediate';
2 | import memoize from 'lodash/memoize';
3 | import Dataloader from 'dataloader';
4 | import { batchGetTokenData, GetTokenViewInput } from './contract';
5 | import { batchGetIndexedInfusions, GetIndexedInfusionsInput } from './subgraph';
6 |
7 | const options = {
8 | // serialize object keys
9 | cacheKeyFn: JSON.stringify,
10 | };
11 |
12 | export const getLoaders = memoize((chainId: number) => {
13 | return {
14 | tokenData: new Dataloader(
15 | (keys: readonly GetTokenViewInput[]) =>
16 | batchGetTokenData([...keys], chainId),
17 | options
18 | ),
19 | indexedInfusion: new Dataloader(
20 | (keys: readonly GetIndexedInfusionsInput[]) =>
21 | batchGetIndexedInfusions([...keys], chainId),
22 | options
23 | ),
24 | };
25 | });
26 |
--------------------------------------------------------------------------------
/src/hypervibes/ipfs.ts:
--------------------------------------------------------------------------------
1 | import memoize from 'lodash/memoize';
2 | import PQueue from 'p-queue';
3 |
4 | // help a lil bit with ipfs throttling
5 | const ipfsRequestQueue = new PQueue({ concurrency: 4 });
6 |
7 | // resolve JSON from ipfs via hash, w/ concurrency management
8 | export const fetchIpfsJson = memoize(
9 | async (hashOrUri: string): Promise => {
10 | const hash = extractIpfsHash(hashOrUri);
11 |
12 | if (!hash) {
13 | throw new Error(`invalid ipfs uri or hash: ${hashOrUri}`);
14 | }
15 |
16 | const uri = ipfsHashAsHttp(hash);
17 | const resp = await ipfsRequestQueue.add(() => fetch(uri));
18 | const json = await resp.json();
19 | return json;
20 | }
21 | );
22 |
23 | // if a uri is an ipfs-ish uri, convert it to a https featchable able
24 | export const rewriteIpfsUri = (uri: string | undefined): string | undefined => {
25 | if (uri == null) return undefined;
26 |
27 | const isIpfsIo = /^https:\/\/ipfs.io\/ipfs/.test(uri);
28 | const isHttps = /^https:\/\//.test(uri);
29 |
30 | // if its not already https, or if its ipfs's public (slow/throttled) gateway,
31 | // then rewrite
32 | if (!isHttps || isIpfsIo) {
33 | const hash = extractIpfsHash(uri);
34 | return hash ? ipfsHashAsHttp(hash) : uri;
35 | }
36 |
37 | // else no-op
38 | return uri;
39 | };
40 |
41 | // fetchable https uri from an ipfs hash
42 | export const ipfsHashAsHttp = (hash: string): string =>
43 | `https://ipfs.hypervibes.xyz/ipfs/${hash}`;
44 |
45 | // attempt to extract ipfs hash from a uri
46 | export const extractIpfsHash = (ipfsUri: string): string | undefined => {
47 | // if it ends with anything that looks like /ipfs/WHATEVER, assume thats the
48 | // hash
49 | let match = ipfsUri.match(/ipfs\/(.*)$/);
50 |
51 | // else, try assuming everything after the protocol part of the uri is the
52 | // hash if its ipfs://
53 | if (!match) {
54 | match = ipfsUri.match(/^ipfs:\/\/(.*)$/);
55 | }
56 |
57 | // else, see if its already a hash. greedy post Qm is because of /filename.ext
58 | // patterns, lots of stuff could be valid. might be a problem later
59 | if (!match) {
60 | match = ipfsUri.match(/^(Qm.*)$/);
61 | }
62 |
63 | // give up
64 | if (!match) {
65 | return undefined;
66 | }
67 |
68 | const [, hash] = match;
69 | return hash;
70 | };
71 |
--------------------------------------------------------------------------------
/src/hypervibes/nft.ts:
--------------------------------------------------------------------------------
1 | import memoize from 'lodash/memoize';
2 | import { rewriteIpfsUri, extractIpfsHash, fetchIpfsJson } from './ipfs';
3 |
4 | export interface Metadata {
5 | name: string;
6 | description: string;
7 | image?: string;
8 | animationUrl?: string;
9 | externalUrl?: string;
10 | }
11 |
12 | // resolve metadata give a token URI
13 | export const resolveMetadata = memoize(async (uri: string) => {
14 | // base64 encoded
15 | if (uri.match(/^data:application\/json;/)) {
16 | return parseBase64MetadataUri(uri);
17 | }
18 |
19 | // ipfs-style metadata
20 | const hash = extractIpfsHash(uri);
21 |
22 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
23 | let fetched: any;
24 |
25 | // use throttled ipfs fetch if ipfs, else str8 fetch it
26 | if (hash) {
27 | fetched = await fetchIpfsJson(hash);
28 | } else {
29 | const resp = await fetch(uri);
30 | fetched = await resp.json();
31 | }
32 |
33 | const projected: Metadata = {
34 | name: fetched.name ?? '',
35 | description: fetched.description ?? '',
36 | image: resolveMetadataImage(fetched),
37 | animationUrl: fetched.animation_url
38 | ? rewriteIpfsUri(fetched.animation_url)
39 | : undefined,
40 | externalUrl: fetched.external_url ?? undefined,
41 | };
42 |
43 | return projected;
44 | });
45 |
46 | // given metadata blob, figure out the image we want to use
47 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
48 | const resolveMetadataImage = (payload: any): string | undefined => {
49 | // various metadata formats, trying to be accomodating
50 | const image = payload.image ?? payload.imageUrl ?? payload.image_url;
51 |
52 | if (image != null) {
53 | return rewriteIpfsUri(image);
54 | }
55 |
56 | return undefined;
57 | };
58 |
59 | // parse base64 encoded URI schemes
60 | const parseBase64MetadataUri = (uri: string): Metadata => {
61 | const [, encoded] = uri.match(/^data:application\/json;base64,(.*)$/) ?? [];
62 | const payload = JSON.parse(atob(encoded));
63 |
64 | const metadata: Metadata = {
65 | name: payload.name ?? '',
66 | description: payload.description ?? '',
67 | image: payload.image ?? undefined,
68 | animationUrl: payload.animation_url ?? undefined,
69 | externalUrl: payload.external_url ?? undefined,
70 | };
71 |
72 | return metadata;
73 | };
74 |
--------------------------------------------------------------------------------
/src/hypervibes/subgraph.ts:
--------------------------------------------------------------------------------
1 | import { BigNumber } from 'ethers';
2 | import request, { gql } from 'graphql-request';
3 | import { getChainInfo } from './constants';
4 |
5 | export interface GetIndexedInfusionsInput {
6 | collection: string;
7 | tokenId: BigNumber;
8 | }
9 |
10 | export interface GetIndexedInfusionsOutput {
11 | id: string;
12 | collection: string;
13 | tokenId: BigNumber;
14 | tokenUri: string;
15 | currentOwner: string;
16 | infusions: Array<{
17 | id: string;
18 | realm: {
19 | id: BigNumber;
20 | name: string;
21 | dailyRate: BigNumber;
22 | token: {
23 | address: string;
24 | symbol: string;
25 | name: string;
26 | decimals: number;
27 | };
28 | };
29 | initialInfusionBy: string;
30 | initialInfusionAmount: BigNumber;
31 | currentBalance: BigNumber;
32 | lastClaimTimestamp: BigNumber;
33 | }>;
34 | }
35 |
36 | const infusionsQuery = gql`
37 | query getInfusions($ids: [ID!]!) {
38 | nfts(where: { id_in: $ids }) {
39 | id
40 | tokenId
41 | tokenUri
42 | owner {
43 | id
44 | address
45 | }
46 | collection {
47 | id
48 | address
49 | name
50 | symbol
51 | }
52 | infusions {
53 | id
54 | balance
55 | lastClaimAtTimestamp
56 | realm {
57 | id
58 | name
59 | description
60 | dailyRate
61 | token {
62 | id
63 | address
64 | name
65 | symbol
66 | decimals
67 | }
68 | }
69 | # only query for first infusion
70 | events(first: 1, orderBy: createdAtTimestamp, orderDirection: asc) {
71 | id
72 | eventType
73 | createdAtTimestamp
74 | createdAtBlock
75 | msgSender {
76 | id
77 | address
78 | }
79 | target {
80 | id
81 | address # infuser
82 | }
83 | amount
84 | }
85 | }
86 | }
87 | }
88 | `;
89 |
90 | // WARN: the assumption about the ID format isn't great here
91 | const computeNftId = (datum: { collection: string; tokenId: BigNumber }) =>
92 | `${datum.collection}-${datum.tokenId.toString()}`;
93 |
94 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
95 | const toProjectedInfusionInfo = (item: any): GetIndexedInfusionsOutput => {
96 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
97 | const infusions = item.infusions.map((infusion: any) => {
98 | const { token } = infusion.realm;
99 | const [event] = infusion.events;
100 | if (event == null) throw new Error('missing initial infusion event');
101 | const entry: GetIndexedInfusionsOutput['infusions'][0] = {
102 | id: infusion.id,
103 | realm: {
104 | id: BigNumber.from(infusion.realm.id),
105 | name: infusion.realm.name,
106 | dailyRate: BigNumber.from(infusion.realm.dailyRate),
107 | token: {
108 | address: token.address ?? '',
109 | symbol: token.symbol ?? '',
110 | name: token.name ?? '',
111 | decimals: token.decimals,
112 | },
113 | },
114 | currentBalance: BigNumber.from(infusion.balance),
115 | lastClaimTimestamp: BigNumber.from(infusion.lastClaimAtTimestamp),
116 | initialInfusionAmount: BigNumber.from(event.amount),
117 | initialInfusionBy: event.target.address,
118 | };
119 |
120 | return entry;
121 | });
122 |
123 | const mapped: GetIndexedInfusionsOutput = {
124 | id: item.id,
125 | collection: item.collection.address,
126 | tokenId: BigNumber.from(item.tokenId),
127 | tokenUri: item.tokenUri,
128 | currentOwner: item.owner.address,
129 | infusions,
130 | };
131 |
132 | return mapped;
133 | };
134 |
135 | /**
136 | * get indexed information about a batch of NFTs
137 | *
138 | * can be used when there is a list of known NFTs presented in a UI (eg, a token
139 | * grid UX) and you want to know if it has been infused in ANY realm
140 | */
141 | export const batchGetIndexedInfusions = async (
142 | batch: GetIndexedInfusionsInput[],
143 | chainId: number
144 | ): Promise => {
145 | const { subgraph } = getChainInfo(chainId);
146 |
147 | const ids = batch.map(computeNftId);
148 | const resp = await request(subgraph, infusionsQuery, { ids });
149 |
150 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
151 | const nfts: any[] = resp.nfts;
152 |
153 | const projected = nfts.map(toProjectedInfusionInfo);
154 | const outputLookup = new Map(projected.map(nft => [computeNftId(nft), nft]));
155 | const ordered = batch.map(datum => {
156 | const projected = outputLookup.get(computeNftId(datum));
157 | if (!projected) throw new Error('batch input output mismatch');
158 | return projected;
159 | });
160 |
161 | return ordered;
162 | };
163 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { StrictMode } from 'react';
2 | import { render } from 'react-dom';
3 | import App from './App';
4 |
5 | render(
6 |
7 |
8 | ,
9 | document.getElementById('root')
10 | );
11 |
--------------------------------------------------------------------------------
/src/pages/BrowseNftsPage.tsx:
--------------------------------------------------------------------------------
1 | import { useParams } from 'react-router';
2 | import styled from 'styled-components';
3 | import { BigNumber } from 'ethers';
4 | import { NETWORKS } from '../constants/contracts';
5 | import useListRealmNfts from '../hooks/useListRealmNfts';
6 | import BannerPageHeading from '../components/BannerPageHeading';
7 | import NftGalleryCard from '../components/NftGalleryCard';
8 |
9 | interface Params {
10 | network: string;
11 | realmId: string;
12 | }
13 |
14 | const Container = styled.div``;
15 |
16 | const NftList = styled.div`
17 | margin-top: 210px;
18 | justify-content: center;
19 | display: flex;
20 | flex-wrap: wrap;
21 | `;
22 |
23 | export default () => {
24 | const { network, realmId } = useParams();
25 | const chainId = NETWORKS[network];
26 | const { data, isLoading, isError } = useListRealmNfts(realmId, chainId);
27 |
28 | if (chainId == null) {
29 | return invalid network
;
30 | }
31 |
32 | if (isLoading) {
33 | return loading realm nfts...
;
34 | }
35 |
36 | if (isError) {
37 | return error fetching nfts
;
38 | }
39 |
40 | if (data == null) {
41 | return null;
42 | }
43 |
44 | return (
45 |
46 |
50 |
51 |
52 | {data.realm.infusions.map(infusion => (
53 |
63 | ))}
64 |
65 |
66 | );
67 | };
68 |
--------------------------------------------------------------------------------
/src/pages/BrowseRealmsPage.tsx:
--------------------------------------------------------------------------------
1 | import { useParams } from 'react-router';
2 | import styled from 'styled-components';
3 | import useListRealms from '../hooks/useListRealms';
4 | import RealmList from '../components/RealmList';
5 | import { NETWORKS } from '../constants/contracts';
6 | import heading from '../assets/images/headings/explore-realms.svg';
7 |
8 | interface Params {
9 | network: string;
10 | }
11 |
12 | const Container = styled.div`
13 | text-align: center;
14 | `;
15 |
16 | const PageHeading = styled.img`
17 | margin-bottom: 75px;
18 | `;
19 |
20 | export default () => {
21 | const { network } = useParams();
22 | const chainId = NETWORKS[network];
23 | const { data, isLoading, isError } = useListRealms(chainId);
24 |
25 | if (isError) {
26 | return error fetching realms
;
27 | }
28 |
29 | if (isLoading) {
30 | return loading realms...
;
31 | }
32 |
33 | if (data == null) {
34 | return null;
35 | }
36 |
37 | return (
38 |
39 |
40 | `realms/${realmId}/nfts`}
43 | size="lg"
44 | />
45 |
46 | );
47 | };
48 |
--------------------------------------------------------------------------------
/src/pages/ChooseYourPathPage.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { Link } from 'react-router-dom';
3 | import { useWallet } from 'use-wallet';
4 | import CenteredContent from '../components/CenteredContent';
5 | import heading from '../assets/images/headings/choose-your-path.svg';
6 | import cardBack from '../assets/images/card-back.png';
7 | import exploreRealmsHr from '../assets/images/explore-realms-hr.svg';
8 | //import createRealmImage from '../assets/images/create-realm.png';
9 | //import infuseNftImage from '../assets/images/infuse-nft.png';
10 | //import claimTokensImage from '../assets/images/claim-tokens.png';
11 | //import star from '../assets/images/star.svg';
12 | //import plus from '../assets/images/plus.svg';
13 | //import flag from '../assets/images/flag.svg';
14 |
15 | const Container = styled.div`
16 | display: flex;
17 | `;
18 |
19 | const PageHeading = styled.img`
20 | margin: 3em 0 4em;
21 | `;
22 |
23 | const PathCard = styled.div`
24 | position: relative;
25 | display: flex;
26 | align-items: center;
27 | justify-content: center;
28 | margin: 0 2em;
29 | padding: 14px;
30 | width: 29.25vh;
31 | height: 45vh;
32 | min-width: 200px;
33 | min-height: 305px;
34 | max-width: 332px;
35 | max-height: 508px;
36 | background: #1c1c1c;
37 | border: 1px solid #fff;
38 | border-radius: 30px;
39 | transition: all 0.2s;
40 |
41 | &:hover {
42 | outline: 1px solid #17ffe3;
43 | border-color: #17ffe3;
44 | box-shadow: 0 0 20px 4px #bcff67;
45 | }
46 | `;
47 |
48 | const CardBackImage = styled.img`
49 | width: 100%;
50 | `;
51 |
52 | const PathImage = styled.img<{ width?: string }>`
53 | width: ${({ width }) => (width ? width : '100%')};
54 | `;
55 |
56 | const PathLabel = styled.h3`
57 | margin-top: 2.5em;
58 | font-family: '3616 Grammastile', sans-serif;
59 | font-size: 10px;
60 | font-weight: 400;
61 | line-height: 16px;
62 | color: #fff;
63 | text-align: center;
64 | text-transform: uppercase;
65 | `;
66 |
67 | const StyledLink = styled(Link)`
68 | &:hover {
69 | text-decoration: none;
70 | }
71 | `;
72 |
73 | const CardFlourish = styled.img<{ position: 'top' | 'bottom' }>`
74 | position: absolute;
75 | top: ${({ position }) => (position === 'top' ? '20px' : 'auto')};
76 | right: ${({ position }) => (position === 'top' ? '20px' : 'auto')};
77 | bottom: ${({ position }) => (position === 'bottom' ? '20px' : 'auto')};
78 | left: ${({ position }) => (position === 'bottom' ? '20px' : 'auto')};
79 | height: 9%;
80 | `;
81 |
82 | const ExploreRealmsHr = styled.img`
83 | margin-top: 45px;
84 | `;
85 |
86 | const ExploreRealmsLink = styled(Link)`
87 | margin-top: 15px;
88 | font-size: 14px;
89 | color: #bcff67;
90 | `;
91 |
92 | export default () => {
93 | const wallet = useWallet();
94 |
95 | return (
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | Create a new
107 |
108 | HyperVIBES realm
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 | Infuse an NFT
119 |
120 | with tokens
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 | Claim tokens
131 |
132 | from an NFT
133 |
134 |
135 |
136 |
137 |
138 |
145 | Explore Realms Instead
146 |
147 |
148 | );
149 | };
150 |
--------------------------------------------------------------------------------
/src/pages/ClaimTokensSelectRealmPage.tsx:
--------------------------------------------------------------------------------
1 | import { useWallet } from 'use-wallet';
2 | import ClaimTokensContainer from '../components/ClaimTokensContainer';
3 | import FormHeading from '../components/FormHeading';
4 | import RealmList from '../components/RealmList';
5 | import ConnectWalletInline from '../components/ConnectWalletInline';
6 | import useRealms from '../hooks/useRealms';
7 | import heading from '../assets/images/headings/select-realm.svg';
8 |
9 | export default () => {
10 | const { data, isLoading, isIdle } = useRealms();
11 | const wallet = useWallet();
12 |
13 | return (
14 |
15 |
16 |
17 |
18 |
19 | {!isLoading && !isIdle && wallet.account && (
20 | `realm/${realmId}/select-token`}
23 | />
24 | )}
25 |
26 | );
27 | };
28 |
--------------------------------------------------------------------------------
/src/pages/ClaimTokensSelectTokenPage.tsx:
--------------------------------------------------------------------------------
1 | import { useForm, FormProvider } from 'react-hook-form';
2 | import { useHistory, useParams } from 'react-router-dom';
3 | import { useWallet } from 'use-wallet';
4 | import ClaimTokensContainer from '../components/ClaimTokensContainer';
5 | import FormHeading from '../components/FormHeading';
6 | import NftGalleryCard from '../components/NftGalleryCard';
7 | import ConnectWalletInline from '../components/ConnectWalletInline';
8 | import EmptyState from '../components/EmptyState';
9 | import useMyInfusedNfts from '../hooks/useMyInfusedNfts';
10 | import heading from '../assets/images/headings/select-nft.svg';
11 |
12 | interface FormValues {
13 | tokenId: string;
14 | }
15 |
16 | interface Params {
17 | realmId: string;
18 | collection: string;
19 | tokenId: string;
20 | }
21 |
22 | export default () => {
23 | const methods = useForm();
24 | const { realmId } = useParams();
25 | const history = useHistory();
26 | const wallet = useWallet();
27 | const {
28 | data: { infusedNfts },
29 | } = useMyInfusedNfts();
30 |
31 | const infusedNftsInCurrentRealm = infusedNfts?.filter(nft =>
32 | nft.infusions.find(infusion => infusion.realm.id === realmId)
33 | );
34 |
35 | const onSubmit = methods.handleSubmit(data => {
36 | history.push(`token/${data.tokenId}`);
37 | });
38 |
39 | return (
40 |
41 |
42 |
43 |
44 |
45 | {wallet.account &&
46 | (!infusedNftsInCurrentRealm ||
47 | infusedNftsInCurrentRealm?.length === 0) && (
48 | You own no infused NFTs in this realm.
49 | )}
50 |
51 |
52 |
63 |
64 |
65 | );
66 | };
67 |
--------------------------------------------------------------------------------
/src/pages/ClaimTokensSuccessPage.tsx:
--------------------------------------------------------------------------------
1 | export default () => {
2 | return tokens claimed.
;
3 | };
4 |
--------------------------------------------------------------------------------
/src/pages/CreateRealmBasicInfoPage.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { useForm, FormProvider } from 'react-hook-form';
3 | import { useHistory } from 'react-router-dom';
4 | import useCreateRealmWizard, {
5 | RealmWizardValues,
6 | } from '../hooks/useCreateRealmWizard';
7 | import CreateRealmContainer from '../components/CreateRealmContainer';
8 | import FormHeading from '../components/FormHeading';
9 | import TextInput from '../components/TextInput';
10 | import MultiAddressInput from '../components/MultiAddressInput';
11 | import ButtonGroup from '../components/ButtonGroup';
12 | import BackButton from '../components/BackButton';
13 | import SubmitButton from '../components/SubmitButton';
14 | import { CREATE_REALM_STEPS } from '../constants/formSteps';
15 | import heading from '../assets/images/headings/create-your-realm.svg';
16 |
17 | const Container = styled.div``;
18 |
19 | export default () => {
20 | const { realm, updateRealm } = useCreateRealmWizard();
21 | const methods = useForm({ defaultValues: realm });
22 | const history = useHistory();
23 |
24 | const onSubmit = methods.handleSubmit(data => {
25 | updateRealm(data);
26 | history.push('select-collections');
27 | });
28 |
29 | return (
30 |
31 |
32 |
37 |
38 |
39 |
64 |
65 |
66 |
67 | );
68 | };
69 |
--------------------------------------------------------------------------------
/src/pages/CreateRealmSelectCollectionsPage.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { useForm, FormProvider } from 'react-hook-form';
3 | import { useHistory } from 'react-router-dom';
4 | import useCreateRealmWizard, {
5 | RealmWizardValues,
6 | } from '../hooks/useCreateRealmWizard';
7 | import CreateRealmContainer from '../components/CreateRealmContainer';
8 | import FormHeading from '../components/FormHeading';
9 | import RadioGroup from '../components/RadioGroup';
10 | import RadioButtonCard from '../components/RadioButtonCard';
11 | import MultiAddressInput from '../components/MultiAddressInput';
12 | import ButtonGroup from '../components/ButtonGroup';
13 | import BackButton from '../components/BackButton';
14 | import SubmitButton from '../components/SubmitButton';
15 | import { CREATE_REALM_STEPS } from '../constants/formSteps';
16 | import heading from '../assets/images/headings/select-collections.svg';
17 | import allowAnyCollectionImage from '../assets/images/allow-any-collection.png';
18 | import allowAnyCollectionSelectedImage from '../assets/images/allow-any-collection-selected.png';
19 | import allowSpecificCollectionsImage from '../assets/images/allow-specific-collections.png';
20 | import allowSpecificCollectionsSelectedImage from '../assets/images/allow-specific-collections-selected.png';
21 |
22 | const Container = styled.div``;
23 |
24 | const CollectionOptionImage = styled.div`
25 | width: 100%;
26 | height: 100%;
27 | background-position: center;
28 | background-repeat: no-repeat;
29 | transition: background-image 0.2s;
30 | `;
31 |
32 | const AllowAnyCollection = styled(CollectionOptionImage)<{
33 | isSelected: boolean;
34 | }>`
35 | background-size: 45%;
36 |
37 | background-image: ${({ isSelected }) =>
38 | isSelected
39 | ? `url(${allowAnyCollectionSelectedImage})`
40 | : `url(${allowAnyCollectionImage})`};
41 |
42 | &:hover {
43 | background-image: url(${allowAnyCollectionSelectedImage});
44 | }
45 | `;
46 |
47 | const AllowSpecificCollections = styled(CollectionOptionImage)<{
48 | isSelected: boolean;
49 | }>`
50 | background-size: 23%;
51 |
52 | background-image: ${({ isSelected }) =>
53 | isSelected
54 | ? `url(${allowSpecificCollectionsSelectedImage})`
55 | : `url(${allowSpecificCollectionsImage})`};
56 |
57 | &:hover {
58 | background-image: url(${allowSpecificCollectionsSelectedImage});
59 | }
60 | `;
61 |
62 | export default () => {
63 | const { realm, updateRealm } = useCreateRealmWizard();
64 | const methods = useForm({ defaultValues: realm });
65 | const allowAllCollections = methods.watch('allowAllCollections');
66 | const history = useHistory();
67 |
68 | const onSubmit = methods.handleSubmit(data => {
69 | updateRealm(data);
70 | history.push('set-up-infusion');
71 | });
72 |
73 | return (
74 |
75 |
76 |
81 |
82 |
83 |
120 |
121 |
122 |
123 | );
124 | };
125 |
--------------------------------------------------------------------------------
/src/pages/CreateRealmSetUpInfusionPage.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { useForm, FormProvider } from 'react-hook-form';
3 | import { useHistory } from 'react-router-dom';
4 | import useCreateRealmWizard, {
5 | RealmWizardValues,
6 | } from '../hooks/useCreateRealmWizard';
7 | import CreateRealmContainer from '../components/CreateRealmContainer';
8 | import FormHeading from '../components/FormHeading';
9 | import RadioGroup from '../components/RadioGroup';
10 | import RadioButtonCard from '../components/RadioButtonCard';
11 | import AddressInput from '../components/AddressInput';
12 | import MultiAddressInput from '../components/MultiAddressInput';
13 | import InputGroup from '../components/InputGroup';
14 | import NumberInput from '../components/NumberInput';
15 | import SwitchGroup from '../components/SwitchGroup';
16 | import Switch from '../components/Switch';
17 | import ButtonGroup from '../components/ButtonGroup';
18 | import BackButton from '../components/BackButton';
19 | import SubmitButton from '../components/SubmitButton';
20 | import { CREATE_REALM_STEPS } from '../constants/formSteps';
21 | import heading from '../assets/images/headings/set-up-infusion.svg';
22 | import allowAnyInfuserImage from '../assets/images/allow-any-infuser.png';
23 | import allowAnyInfuserSelectedImage from '../assets/images/allow-any-infuser-selected.png';
24 | import allowSpecificInfusersImage from '../assets/images/allow-specific-addresses.png';
25 | import allowSpecificInfusersSelectedImage from '../assets/images/allow-specific-addresses-selected.png';
26 |
27 | const Container = styled.div``;
28 |
29 | const InfusionOptionImage = styled.div`
30 | width: 100%;
31 | height: 100%;
32 | background-position: center;
33 | background-repeat: no-repeat;
34 | transition: background-image 0.2s;
35 | `;
36 |
37 | const AllowAnyInfuser = styled(InfusionOptionImage)<{ isSelected: boolean }>`
38 | background-size: 70%;
39 |
40 | background-image: ${({ isSelected }) =>
41 | isSelected
42 | ? `url(${allowAnyInfuserSelectedImage})`
43 | : `url(${allowAnyInfuserImage})`};
44 |
45 | &:hover {
46 | background-image: url(${allowAnyInfuserSelectedImage});
47 | }
48 | `;
49 |
50 | const AllowSpecificInfusers = styled(InfusionOptionImage)<{
51 | isSelected: boolean;
52 | }>`
53 | background-size: 41%;
54 |
55 | background-image: ${({ isSelected }) =>
56 | isSelected
57 | ? `url(${allowSpecificInfusersSelectedImage})`
58 | : `url(${allowSpecificInfusersImage})`};
59 |
60 | &:hover {
61 | background-image: url(${allowSpecificInfusersSelectedImage});
62 | }
63 | `;
64 |
65 | export default () => {
66 | const { realm, updateRealm } = useCreateRealmWizard();
67 | const methods = useForm({ defaultValues: realm });
68 | const allowPublicInfusion = methods.watch('allowPublicInfusion');
69 | const history = useHistory();
70 |
71 | const onSubmit = methods.handleSubmit(data => {
72 | updateRealm(data);
73 | history.push('set-up-claiming');
74 | });
75 |
76 | return (
77 |
78 |
79 |
84 |
85 |
162 |
163 |
164 |
165 | );
166 | };
167 |
--------------------------------------------------------------------------------
/src/pages/CreateRealmSuccessPage.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import useCreateRealmWizard from '../hooks/useCreateRealmWizard';
3 |
4 | export default () => {
5 | const { realm } = useCreateRealmWizard();
6 |
7 | useEffect(() => {
8 | console.log('done', realm);
9 | }, [realm]);
10 |
11 | return realm created.
;
12 | };
13 |
--------------------------------------------------------------------------------
/src/pages/InfuseNftInputTokenPage.tsx:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { useForm, FormProvider } from 'react-hook-form';
3 | import { useHistory, useParams } from 'react-router-dom';
4 | import { useWallet } from 'use-wallet';
5 | import InfuseNftContainer from '../components/InfuseNftContainer';
6 | import FormHeading from '../components/FormHeading';
7 | import InputGroup from '../components/InputGroup';
8 | import NumberInput from '../components/NumberInput';
9 | import ButtonGroup from '../components/ButtonGroup';
10 | import BackButton from '../components/BackButton';
11 | import SubmitButton from '../components/SubmitButton';
12 | import ConnectWalletInline from '../components/ConnectWalletInline';
13 | import FormErrors from '../components/FormErrors';
14 | import { useLazyErc721OwnerOf } from '../hooks/useErc721OwnerOf';
15 | import heading from '../assets/images/headings/select-nft.svg';
16 |
17 | interface FormValues {
18 | tokenId: string;
19 | }
20 |
21 | interface Params {
22 | realmId: string;
23 | collection: string;
24 | }
25 |
26 | export default () => {
27 | const methods = useForm();
28 | const history = useHistory();
29 | const { collection } = useParams();
30 | const getErc721OwnerOf = useLazyErc721OwnerOf(collection);
31 | const wallet = useWallet();
32 | const [formErrors, setFormErrors] = useState([]);
33 |
34 | const onSubmit = methods.handleSubmit(async data => {
35 | try {
36 | const ownerOf = await getErc721OwnerOf(data.tokenId);
37 |
38 | if (!ownerOf) {
39 | throw new Error();
40 | }
41 | } catch (e) {
42 | const errorMessage = 'Enter a valid token ID.';
43 |
44 | if (!formErrors.includes(errorMessage)) {
45 | setFormErrors([...formErrors, errorMessage]);
46 | }
47 |
48 | return false;
49 | }
50 |
51 | history.push(`token/${data.tokenId}`);
52 | });
53 |
54 | return (
55 |
56 |
57 |
58 |
59 |
60 | {wallet.account && (
61 |
62 |
77 |
78 | )}
79 |
80 | );
81 | };
82 |
--------------------------------------------------------------------------------
/src/pages/InfuseNftSelectCollectionPage.tsx:
--------------------------------------------------------------------------------
1 | import { useForm, FormProvider } from 'react-hook-form';
2 | import { useHistory, useParams } from 'react-router-dom';
3 | import InfuseNftContainer from '../components/InfuseNftContainer';
4 | import FormHeading from '../components/FormHeading';
5 | import CollectionList from '../components/CollectionList';
6 | import useRealmCollections from '../hooks/useRealmCollections';
7 | import AddressInput from '../components/AddressInput';
8 | import SubmitButton from '../components/SubmitButton';
9 | import ConnectWalletInline from '../components/ConnectWalletInline';
10 | import heading from '../assets/images/headings/select-collection.svg';
11 |
12 | interface FormValues {
13 | collectionAddress: string;
14 | }
15 |
16 | interface Params {
17 | realmId: string;
18 | }
19 |
20 | export default () => {
21 | const methods = useForm();
22 | const { realmId } = useParams();
23 | const history = useHistory();
24 | const { data } = useRealmCollections(realmId);
25 |
26 | if (!data) {
27 | return null;
28 | }
29 |
30 | const onSubmit = methods.handleSubmit(data => {
31 | history.push(`collection/${data.collectionAddress}/select-token`);
32 | });
33 |
34 | return (
35 |
36 |
37 |
38 |
39 |
40 | {data.allowAllCollections ? (
41 |
42 |
52 |
53 | ) : (
54 |
55 | )}
56 |
57 | );
58 | };
59 |
--------------------------------------------------------------------------------
/src/pages/InfuseNftSelectRealmPage.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import InfuseNftContainer from '../components/InfuseNftContainer';
3 | import FormHeading from '../components/FormHeading';
4 | import RealmList from '../components/RealmList';
5 | import ConnectWalletInline from '../components/ConnectWalletInline';
6 | import useMyInfusibleRealms from '../hooks/useMyInfusibleRealms';
7 | import heading from '../assets/images/headings/select-realm.svg';
8 |
9 | export default () => {
10 | const { data, isLoading, isIdle } = useMyInfusibleRealms();
11 |
12 | return (
13 |
14 |
15 |
16 |
17 |
18 | {!isLoading && !isIdle && (
19 |
23 | )}
24 |
25 | );
26 | };
27 |
--------------------------------------------------------------------------------
/src/pages/InfuseNftSelectTokenPage.tsx:
--------------------------------------------------------------------------------
1 | import { useForm, FormProvider } from 'react-hook-form';
2 | import { useHistory } from 'react-router-dom';
3 | import InfuseNftContainer from '../components/InfuseNftContainer';
4 | import FormHeading from '../components/FormHeading';
5 | import NumberInput from '../components/TextInput';
6 | import SubmitButton from '../components/SubmitButton';
7 | import heading from '../assets/images/headings/select-nft.svg';
8 |
9 | interface FormValues {
10 | tokenId: string;
11 | }
12 |
13 | export default () => {
14 | const methods = useForm();
15 | const history = useHistory();
16 |
17 | const onSubmit = methods.handleSubmit(data => {
18 | history.push(`token/${data.tokenId}`);
19 | });
20 |
21 | return (
22 |
23 |
24 |
25 |
26 |
31 |
32 |
33 | );
34 | };
35 |
--------------------------------------------------------------------------------
/src/pages/InfuseNftSuccessPage.tsx:
--------------------------------------------------------------------------------
1 | export default () => {
2 | return token infused.
;
3 | };
4 |
--------------------------------------------------------------------------------
/src/pages/NftListPage.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import Nft from '../components/Nft';
3 |
4 | const tokenIds = ['90467', '90468', '90472', '90473'];
5 |
6 | const Container = styled.div`
7 | display: flex;
8 | `;
9 |
10 | export default () => (
11 |
12 | {tokenIds.map(tokenId => (
13 |
18 | ))}
19 |
20 | );
21 |
--------------------------------------------------------------------------------
/src/pages/NotFoundPage.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import { Link } from 'react-router-dom';
3 | import CenteredContent from '../components/CenteredContent';
4 |
5 | const H1 = styled.h1`
6 | margin-top: 0;
7 | margin-bottom: 0.25em;
8 | font-size: 28px;
9 | letter-spacing: 5px;
10 | `;
11 |
12 | const H2 = styled.h2`
13 | margin-bottom: 0;
14 | font-size: 16px;
15 | letter-spacing: 8px;
16 | color: rgba(255, 255, 255, 0.6);
17 | `;
18 |
19 | export default () => (
20 |
21 | 404 not found :(
22 |
23 | go back home
24 |
25 |
26 | );
27 |
--------------------------------------------------------------------------------
/src/pages/PlaceholderPage.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import CenteredContent from '../components/CenteredContent';
3 |
4 | const H1 = styled.h1`
5 | margin-top: 0;
6 | margin-bottom: 0.25em;
7 | font-size: 18px;
8 | font-family: '3616 Grammastile', sans-serif;
9 | letter-spacing: 5px;
10 | `;
11 |
12 | const H2 = styled.h2`
13 | margin-bottom: 0;
14 | font-size: 16px;
15 | letter-spacing: 8px;
16 | color: rgba(255, 255, 255, 0.6);
17 | `;
18 |
19 | export default () => (
20 |
21 | ∕∕ hypervibes ∕∕
22 | coming soon
23 |
24 | );
25 |
--------------------------------------------------------------------------------
/src/providers/NftProvider.tsx:
--------------------------------------------------------------------------------
1 | import { NftProvider } from 'use-nft';
2 | import { getDefaultProvider } from '@ethersproject/providers';
3 |
4 | interface Props {
5 | children: React.ReactNode;
6 | }
7 |
8 | const ethersConfig = {
9 | provider: getDefaultProvider('homestead'),
10 | };
11 |
12 | export default ({ children }: Props) => (
13 | {children}
14 | );
15 |
--------------------------------------------------------------------------------
/src/providers/QueryProvider.tsx:
--------------------------------------------------------------------------------
1 | import { QueryClient, QueryClientProvider } from 'react-query';
2 | import { ReactQueryDevtools } from 'react-query/devtools';
3 |
4 | const queryClient = new QueryClient();
5 |
6 | interface Props {
7 | children: React.ReactNode;
8 | }
9 |
10 | export default ({ children }: Props) => (
11 |
12 | {children}
13 |
14 |
15 | );
16 |
--------------------------------------------------------------------------------
/src/providers/WalletProvider.tsx:
--------------------------------------------------------------------------------
1 | import { UseWalletProvider } from 'use-wallet';
2 |
3 | interface Props {
4 | children: React.ReactNode;
5 | }
6 |
7 | export default ({ children }: Props) => (
8 | {children}
9 | );
10 |
--------------------------------------------------------------------------------
/src/types/index.ts:
--------------------------------------------------------------------------------
1 | import { BigNumber } from '@ethersproject/bignumber';
2 |
3 | export interface Realm {
4 | id: string;
5 | name: string;
6 | description: string;
7 | token: {
8 | address: string;
9 | };
10 | minClaimAmount: string;
11 | minInfusionAmount: string;
12 | maxTokenBalance: BigNumber;
13 | requireNftIsOwned: boolean;
14 | allowAllCollections: boolean;
15 | allowPublicInfusion: boolean;
16 | allowPublicClaiming: boolean;
17 | allowMultiInfuse: boolean;
18 | infusions: Infusion[];
19 | realmCollections: { id: string; collection: Collection }[];
20 | realmInfusers: { id: string; account: Account }[];
21 | realmClaimers: { id: string; account: Account }[];
22 | }
23 |
24 | export interface Account {
25 | id: string;
26 | address: string;
27 | }
28 |
29 | export interface Collection {
30 | id: string;
31 | name: string;
32 | address: string;
33 | nfts: Nft[];
34 | }
35 |
36 | export interface Nft {
37 | collection: Collection;
38 | tokenId: string;
39 | tokenUri: string;
40 | infusions: Infusion[];
41 | }
42 |
43 | export interface Infusion {
44 | id: string;
45 | realm: Realm;
46 | balance: string;
47 | nft: Nft;
48 | lastClaimAtTimestamp: string;
49 | }
50 |
--------------------------------------------------------------------------------
/src/utils/address.ts:
--------------------------------------------------------------------------------
1 | export const shortenAddress = (address: string) =>
2 | address
3 | .slice(0, 6)
4 | .concat('...')
5 | .concat(address.slice(address.length - 4, address.length));
6 |
--------------------------------------------------------------------------------
/src/utils/contract.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Web3Provider,
3 | ExternalProvider,
4 | JsonRpcFetchFunc,
5 | } from '@ethersproject/providers';
6 | import { Contract, ContractInterface } from '@ethersproject/contracts';
7 |
8 | export const getContract = (
9 | contractAddress: string,
10 | abi: ContractInterface,
11 | externalProvider: ExternalProvider | JsonRpcFetchFunc
12 | ) => {
13 | const provider = new Web3Provider(externalProvider);
14 | const signer = provider.getSigner();
15 |
16 | return new Contract(contractAddress, abi, signer);
17 | };
18 |
--------------------------------------------------------------------------------
/src/utils/network.ts:
--------------------------------------------------------------------------------
1 | const CHAINS: Record = {
2 | 1: 'Ethereum',
3 | 3: 'Ropsten',
4 | 4: 'Rinkeby',
5 | 5: 'Goerli',
6 | 137: 'Polygon',
7 | 8001: 'Mumbai',
8 | };
9 |
10 | export const getNetworkName = (chainId: number) =>
11 | CHAINS[chainId] || 'Unsupported network';
12 |
--------------------------------------------------------------------------------
/src/utils/object.ts:
--------------------------------------------------------------------------------
1 | // These types can be improved. For now, we can copy what `_.get` has.
2 | export const getDeep = (object: any, path: string) => {
3 | let value = object;
4 |
5 | for (const key of path.split('.')) {
6 | try {
7 | value = value[key];
8 | } catch (e) {
9 | return;
10 | }
11 | }
12 |
13 | return value;
14 | };
15 |
--------------------------------------------------------------------------------
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
6 | "allowJs": false,
7 | "skipLibCheck": false,
8 | "esModuleInterop": false,
9 | "allowSyntheticDefaultImports": true,
10 | "strict": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "module": "ESNext",
13 | "moduleResolution": "Node",
14 | "resolveJsonModule": true,
15 | "isolatedModules": true,
16 | "noEmit": true,
17 | "jsx": "react-jsx"
18 | },
19 | "include": ["./src"]
20 | }
21 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite';
2 | import react from '@vitejs/plugin-react';
3 |
4 | export default defineConfig({
5 | plugins: [react()],
6 | envPrefix: 'REACT_APP_',
7 | build: {
8 | outDir: './build',
9 | sourcemap: true,
10 | manifest: true,
11 | },
12 | });
13 |
--------------------------------------------------------------------------------