├── .eslintignore
├── .eslintrc.json
├── .github
├── CONTRIBUTING.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ ├── build.yml
│ ├── stale.yml
│ ├── trello-issues.yml
│ └── trello-prs.yml
├── .gitignore
├── .gitpod.yml
├── .husky
├── .gitignore
└── pre-commit
├── .idea
├── .gitignore
├── discord.xml
├── inspectionProfiles
│ └── Project_Default.xml
├── jsLinters
│ └── eslint.xml
├── launcher.iml
├── markdown.xml
├── modules.xml
├── vcs.xml
└── watcherTasks.xml
├── .prettierignore
├── .prettierrc.json
├── .vscode
└── settings.json
├── PRIVACY-POLICY.md
├── README.md
├── dapps.json
├── dotenv
├── index.html
├── package.json
├── postcss.config.js
├── public
└── img
│ ├── Stakes-social.svg
│ ├── chainwiz.png
│ └── uzomia.png
├── renovate.json
├── src
├── App.tsx
├── components
│ ├── BackButton
│ │ └── index.tsx
│ ├── Background
│ │ └── index.tsx
│ ├── Card
│ │ └── index.tsx
│ ├── CheckBox
│ │ └── index.tsx
│ ├── ConnectButton
│ │ └── index.tsx
│ ├── CopyButton
│ │ └── index.tsx
│ ├── DPLHeader
│ │ └── index.tsx
│ ├── DPLHr
│ │ └── index.tsx
│ ├── DPLTitleBar
│ │ └── index.tsx
│ ├── Detail
│ │ └── index.tsx
│ ├── Footer
│ │ └── index.tsx
│ ├── Form
│ │ ├── FormInput.tsx
│ │ ├── FormInputLabel.tsx
│ │ └── index.tsx
│ ├── HSButton
│ │ └── index.tsx
│ ├── HSTextField
│ │ └── index.tsx
│ ├── HomeNavItem
│ │ └── index.tsx
│ ├── HowItWorks
│ │ ├── FaqCard.tsx
│ │ ├── StepsCard.tsx
│ │ └── index.tsx
│ ├── NavTabs
│ │ └── index.tsx
│ ├── ProgressStepper
│ │ ├── ProgressStep.tsx
│ │ ├── ProgressStepper.scss
│ │ └── index.tsx
│ ├── Spinner
│ │ └── index.tsx
│ ├── TitleSubSection
│ │ └── index.tsx
│ └── Tweet
│ │ └── index.tsx
├── const
│ ├── cache-path.ts
│ └── index.ts
├── context
│ ├── tokenizeContext.tsx
│ └── walletContext.ts
├── css
│ └── background.css
├── env.d.ts
├── favicon.svg
├── hooks
│ ├── useAddToWalletList.ts
│ ├── useAllowance.ts
│ ├── useApprove.ts
│ ├── useEnabledMarkets.ts
│ ├── useMarket.ts
│ ├── useMetrics.ts
│ ├── usePosition.ts
│ ├── usePositionsOfOwner.ts
│ ├── usePropertyBalances.ts
│ ├── usePropertyDetails.ts
│ └── useTerms.ts
├── img
│ ├── ANPAO.svg
│ ├── CARD.svg
│ ├── FOOTER_IMG_ Powered by Dev Protocol.png
│ ├── FOOTER_IMG_Powered by Dev Protocol.svg
│ ├── HEADING_TEXTURE.png
│ ├── PURSE.svg
│ ├── SEEDLING.svg
│ ├── g-logo.png
│ ├── logo.png
│ ├── og.png
│ └── uzomia.svg
├── index.css
├── main.tsx
├── pages
│ ├── apps
│ │ ├── AppGridItem.tsx
│ │ └── index.tsx
│ ├── auth-callback
│ │ ├── DiscordAuthCallback.tsx
│ │ ├── YouTubeAuthCallback.tsx
│ │ └── index.tsx
│ ├── errors
│ │ └── 404.tsx
│ ├── growth
│ │ └── index.tsx
│ ├── home
│ │ └── index.tsx
│ ├── how-it-works
│ │ └── index.tsx
│ ├── markdown-page
│ │ ├── MarkdownPage.module.scss
│ │ └── index.tsx
│ ├── network-select
│ │ └── index.tsx
│ ├── properties
│ │ ├── PropertyTabsContainer
│ │ │ └── index.tsx
│ │ ├── ProperySummary
│ │ │ └── index.tsx
│ │ ├── index.tsx
│ │ ├── property-holders
│ │ │ └── index.tsx
│ │ ├── property-overview
│ │ │ ├── StakeOption.tsx
│ │ │ ├── fetch-token-data.hook.tsx
│ │ │ └── index.tsx
│ │ ├── property-stakers
│ │ │ ├── index.tsx
│ │ │ └── useSTokensPositionsOfProperty.ts
│ │ └── stake
│ │ │ ├── StakeStep.tsx
│ │ │ ├── index.tsx
│ │ │ └── useLockup.ts
│ ├── tokenize-form
│ │ ├── DiscordForm.tsx
│ │ ├── GithubForm.tsx
│ │ ├── TermsModal.tsx
│ │ ├── YouTubeForm.tsx
│ │ └── index.tsx
│ ├── tokenize-market-select
│ │ ├── TokenizeLink.tsx
│ │ └── index.tsx
│ ├── tokenize-submit
│ │ ├── TokenizePreviewSubmit.tsx
│ │ ├── TokenizeResult.tsx
│ │ ├── index.tsx
│ │ └── tokenize-submit.hooks.ts
│ ├── user-positions-list
│ │ ├── UserPositionListItem.tsx
│ │ └── index.tsx
│ ├── user-properties-list
│ │ ├── UserTokenListItem.tsx
│ │ ├── fetchUserProperties.hook.tsx
│ │ └── index.tsx
│ └── wait-market
│ │ └── index.tsx
├── types
│ ├── AddressContractContainer.ts
│ └── TokenizeWindowState.ts
├── utils
│ ├── utils.test.ts
│ └── utils.ts
├── vite-env.d.ts
└── vite.d.ts
├── tailwind.config.js
├── tsconfig.json
├── vercel.json
├── vite.config.ts
└── yarn.lock
/.eslintignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | cjs
4 | esm
5 | __snapshots__
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "node": true,
6 | "jest": true
7 | },
8 | "extends": ["eslint:recommended", "plugin:react/recommended", "plugin:prettier/recommended"],
9 | "globals": {
10 | "Atomics": "readonly",
11 | "SharedArrayBuffer": "readonly"
12 | },
13 | "parser": "@typescript-eslint/parser",
14 | "parserOptions": {
15 | "sourceType": "module",
16 | "project": "./tsconfig.json",
17 | "tsconfigRootDir": ".",
18 | "ecmaFeatures": {
19 | "tsx": true
20 | }
21 | },
22 | "plugins": ["@typescript-eslint", "react", "react-hooks"],
23 | "rules": {
24 | "no-unused-vars": "off",
25 | "@typescript-eslint/no-unused-vars": [
26 | "warn",
27 | {
28 | "argsIgnorePattern": "^_",
29 | "varsIgnorePattern": "^_",
30 | "caughtErrorsIgnorePattern": "^_"
31 | }
32 | ],
33 | "react/prop-types": "off",
34 | "react-hooks/rules-of-hooks": "error",
35 | "react-hooks/exhaustive-deps": "warn",
36 | "require-atomic-updates": "off",
37 | "no-console": "off",
38 | "react/react-in-jsx-scope": "off"
39 | },
40 | "settings": {
41 | "react": {
42 | "version": "detect"
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing Guide
2 |
3 | ## How to contribute?
4 |
5 | You can find problems from [Issues](https://github.com/dev-protocol/stakes.social/issues) or fix other problems.
6 |
7 | Basic Pull Request steps:
8 |
9 | 1. Fork this repository
10 | 1. Create your feature branch: `git checkout -b awesome-feature`
11 | 1. Commit your changes: `git commit -am "Add awesome feature"`
12 | 1. Push to the branch: `git push origin awesome-feature`
13 | 1. Submit a pull request to this repository
14 |
15 | ## How to start development?
16 |
17 | First as follows:
18 |
19 | ```
20 | git clone git@github.com:YOUR-USERNAME/stakes.social.git
21 | cd stakes.social
22 | ```
23 |
24 | How to run on local:
25 |
26 | ```bash
27 | # install npm packages
28 | $ yarn
29 |
30 | # build deps
31 | $ yarn build
32 |
33 | # web run
34 | $ yarn dev
35 | ```
36 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Proposed Changes
2 |
3 | ## Implementation
4 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 |
9 | strategy:
10 | matrix:
11 | node-version: ['18']
12 |
13 | steps:
14 | - uses: actions/checkout@v2
15 | - name: Use Node.js ${{ matrix.node-version }}
16 | uses: actions/setup-node@v2
17 | with:
18 | node-version: ${{ matrix.node-version }}
19 |
20 | - name: install deps
21 | run: yarn
22 |
23 | - name: lint
24 | run: yarn lint
25 |
26 | - name: build
27 | run: yarn build
28 |
29 | - name: test
30 | run: yarn test
31 |
--------------------------------------------------------------------------------
/.github/workflows/stale.yml:
--------------------------------------------------------------------------------
1 | name: 'Close stale issues and PRs'
2 | on:
3 | schedule:
4 | - cron: '30 1 * * *'
5 |
6 | permissions:
7 | issues: write
8 | pull-requests: write
9 |
10 | jobs:
11 | stale:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/stale@v4
15 | with:
16 | stale-issue-message: 'This issue is inactive and so will be closed automatically.'
17 | stale-pr-message: 'This pull-request is inactive and so will be closed automatically.'
18 |
--------------------------------------------------------------------------------
/.github/workflows/trello-issues.yml:
--------------------------------------------------------------------------------
1 | name: Create Trello card on opened issues
2 |
3 | on:
4 | issues:
5 | types: [opened]
6 |
7 | jobs:
8 | create_trello_card_job:
9 | runs-on: ubuntu-latest
10 | name: Create Trello Card
11 | steps:
12 | - name: Call trello-github-actions
13 | uses: Yleisradio/github-action-trello-integration@v1.1.0
14 | with:
15 | action: issue_opened_create_card
16 | env:
17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
18 | TRELLO_API_KEY: ${{ secrets.TRELLO_API_KEY }}
19 | TRELLO_API_TOKEN: ${{ secrets.TRELLO_API_TOKEN }}
20 | # TRELLO_BOARD_ID must match a board. GH repo should connect
21 | # to exactly one board, but Trello board may connect to multiple
22 | # GH repositories.
23 | TRELLO_BOARD_ID: 621c8575d0c3135b08c625f3
24 | # Backlog list ID
25 | TRELLO_LIST_ID: 621c85809ffbb6647c38fb07
26 |
--------------------------------------------------------------------------------
/.github/workflows/trello-prs.yml:
--------------------------------------------------------------------------------
1 | name: Move Trello Card to Needs review list
2 |
3 | on:
4 | pull_request:
5 | types: [opened, synchronize, reopened]
6 | #types: closed #for merged or just closed PRs
7 | branches:
8 | - main
9 |
10 | jobs:
11 | move_card_when_pull_request_merged_job:
12 | runs-on: ubuntu-latest
13 | name: Move Trello Card to Needs review when Card refers to the issue referred by PR
14 | steps:
15 | - name: Call trello-github-actions
16 | id: call-trello-github-actions
17 | uses: Yleisradio/github-action-trello-integration@v1.1.0
18 | with:
19 | action: pull_request_event_move_card
20 | env:
21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
22 | TRELLO_API_KEY: ${{ secrets.TRELLO_API_KEY }}
23 | TRELLO_API_TOKEN: ${{ secrets.TRELLO_API_TOKEN }}
24 | TRELLO_BOARD_ID: 621c8575d0c3135b08c625f3
25 | # List "In progress"
26 | TRELLO_SOURCE_LIST_ID: 621c85809ffbb6647c38fb07
27 | # List "Needs review"
28 | TRELLO_TARGET_LIST_ID: 621f03bfc181d9161e31c14b
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | dist
4 | dist-ssr
5 | *.local
6 |
7 | .vercel
8 | .env
--------------------------------------------------------------------------------
/.gitpod.yml:
--------------------------------------------------------------------------------
1 | # This configuration file was automatically generated by Gitpod.
2 | # Please adjust to your needs (see https://www.gitpod.io/docs/config-gitpod-file)
3 | # and commit this file to your remote git repository to share the goodness with others.
4 |
5 | tasks:
6 | - init: yarn install && yarn run build
7 | command: yarn run dev
8 | vscode:
9 | extensions:
10 | - dbaeumer.vscode-eslint
11 |
12 |
--------------------------------------------------------------------------------
/.husky/.gitignore:
--------------------------------------------------------------------------------
1 | _
2 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | yarn lint-staged --allow-empty
5 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 |
--------------------------------------------------------------------------------
/.idea/discord.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/jsLinters/eslint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/launcher.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/markdown.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/watcherTasks.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | # Ignore artifacts:
3 | build
4 | coverage
5 | src/reportWebVitals.ts
6 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "semi": false,
4 | "printWidth": 120,
5 | "trailingComma": "none",
6 | "arrowParens": "avoid"
7 | }
8 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.codeActionsOnSave": {
3 | "source.fixAll": true
4 | },
5 | "editor.formatOnSave": true,
6 | "eslint.validate": ["javascript", "typescript"],
7 | "[typescript]": {
8 | "editor.defaultFormatter": "esbenp.prettier-vscode"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Niwa
2 |
3 | ## Development
4 |
5 | For local development please create a .env file using the dotenv file found in the root folder as a template
6 |
7 | ```
8 | # this can be arbitrum-one, polygon-mainnet, or polygon-mumbai
9 | VITE_L2_NETWORK=polygon-mumbai
10 | VITE_INFURA_PROJECT_ID=
11 | VITE_INFURA_BASE_ENDPOINT=
12 | VITE_IS_ROOT= # boolean
13 | ```
14 |
15 | Setting `VITE_IS_ROOT` to `true` will prompt the network select page.
16 | Otherwise, it will route to whatever `VITE_L2_NETWORK` you have setup with correct Infura details.
17 |
18 | ### use OAuth YouTube Account
19 |
20 | The following environment variables need to be set:
21 |
22 | ```
23 | VITE_YOUTUBE_CLIENT_ID=XXX.apps.googleusercontent.com
24 | VITE_YOUTUBE_AUTH_REDIRECT_URI=https://HOST:PORT/auth/youtube/callback
25 | ```
26 |
27 | See also:
28 | https://developers.google.com/youtube/v3/getting-started
29 |
30 | ### use OAuth Discord Account
31 |
32 | The following environment variables need to be set:
33 |
34 | ```
35 | VITE_DISCORD_CLIENT_ID=YOUR_CLIENT_ID
36 | VITE_DISCORD_CLIENT_SECRET=YOUR_CLIENT_SECRET
37 | VITE_DISCORD_AUTH_REDIRECT_URI=http://HOST:PORT/auth/discord/callback
38 | ```
39 |
40 | See also:
41 | https://discord.com/developers/docs/topics/oauth2
42 |
43 | ### Adding your dApp to the Apps Page
44 |
45 | Create a PR with your dapp added in `dapps.json` file in the root directory.
46 |
--------------------------------------------------------------------------------
/dapps.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "Stakes Social",
4 | "url": "https://stakes.social",
5 | "description": "Explore projects and support your favorite creators",
6 | "logoUrl": "/img/Stakes-social.svg"
7 | },
8 | {
9 | "name": "μ-zomia",
10 | "url": "https://mzomia.social/",
11 | "description": "μ-zomia is a platform and community for people who love music, people who are involved in music, and people who want to support it to support each other.",
12 | "logoUrl": "/img/uzomia.png"
13 | },
14 | {
15 | "name": "Chainwiz",
16 | "url": "https://www.chainwhiz.app/",
17 | "description": "Chainwhiz is an open-source bounty marketplace connecting Web3 projects with builders and communities. Work on bounties from fields like Front End, Back End, Design, Article-writing, and others and get paid in cryptocurrency.",
18 | "logoUrl": "/img/chainwiz.png"
19 | }
20 | ]
--------------------------------------------------------------------------------
/dotenv:
--------------------------------------------------------------------------------
1 | # this can be arbitrum-one, polygon-mainnet, or polygon-mumbai
2 |
3 | VITE_L2_NETWORK=polygon-mumbai
4 | VITE_INFURA_PROJECT_ID=
5 | VITE_INFURA_BASE_ENDPOINT=
6 | VITE_IS_ROOT=
7 |
8 | # for YouTube
9 |
10 | VITE_YOUTUBE_CLIENT_ID=XXX.apps.googleusercontent.com
11 | VITE_YOUTUBE_AUTH_REDIRECT_URI=https://HOST:PORT/auth/youtube/callback
12 |
13 | #for Discord
14 | VITE_DISCORD_CLIENT_ID=YOUR_CLIENT_ID
15 | VITE_DISCORD_CLIENT_SECRET=YOUR_CLIENT_SECRET
16 | VITE_DISCORD_AUTH_REDIRECT_URI=http://HOST:PORT/auth/discord/callback
17 | VITE_WALLET_CONNECT_PROJECT_ID=VITE_WALLET_CONNECT_PROJECT_ID
18 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Niwa
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
26 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "launchpad",
3 | "version": "0.0.0",
4 | "scripts": {
5 | "dev": "vite",
6 | "build": "tsc && vite build",
7 | "preview": "vite preview",
8 | "lint": "eslint --fix -c ./.eslintrc.json './src/**/*.{ts,tsx}'",
9 | "test": "vitest",
10 | "coverage": "vitest --coverage"
11 | },
12 | "dependencies": {
13 | "@devprotocol/dev-kit": "6.0.9",
14 | "@devprotocol/khaos-kit": "^1.4.0",
15 | "@devprotocol/util-ts": "^2.2.1",
16 | "@metamask/detect-provider": "^1.2.0",
17 | "@typescript-eslint/eslint-plugin": "^6.7.0",
18 | "@typescript-eslint/parser": "^6.7.0",
19 | "@walletconnect/web3-provider": "^1.7.1",
20 | "@web3modal/wagmi": "3.0.0-alpha.5",
21 | "browserify-zlib": "^0.2.0",
22 | "eslint": "^8.49.0",
23 | "eslint-config-prettier": "^9.0.0",
24 | "eslint-plugin-prettier": "^5.0.0",
25 | "eslint-plugin-react": "^7.33.2",
26 | "eslint-plugin-react-hooks": "^4.6.0",
27 | "ethers": "^5.5.2",
28 | "events": "^3.3.0",
29 | "prettier": "^3.0.3",
30 | "prettier-plugin-tailwindcss": "^0.5.4",
31 | "process": "^0.11.10",
32 | "react": "^17.0.2",
33 | "react-dom": "^17.0.2",
34 | "react-icons": "^4.3.1",
35 | "react-markdown": "^9.0.0",
36 | "react-router-dom": "^6.2.1",
37 | "rehype-raw": "^7.0.0",
38 | "sass": "^1.49.0",
39 | "stream-browserify": "^3.0.0",
40 | "swr": "^1.1.2",
41 | "tailwindcss": "^3.0.18",
42 | "util": "^0.12.4",
43 | "viem": "^1.10.9",
44 | "wagmi": "^1.4.1",
45 | "web3": "^1.6.1"
46 | },
47 | "devDependencies": {
48 | "@babel/preset-react": "7.16.7",
49 | "@devprotocol/khaos-core": "1.6.0",
50 | "@rollup/plugin-inject": "4.0.4",
51 | "@types/jest": "27.4.1",
52 | "@types/react": "17.0.43",
53 | "@types/react-dom": "17.0.14",
54 | "@vitejs/plugin-react": "1.3.0",
55 | "autoprefixer": "10.4.16",
56 | "postcss": "8.4.12",
57 | "typescript": "^5.2.2",
58 | "vite": "2.9.1",
59 | "vite-plugin-markdown": "2.1.0",
60 | "vitest": "0.9.3"
61 | },
62 | "exports": {
63 | ".": {
64 | "import": "./index.esm.js",
65 | "require": "./index.cjs.js"
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/public/img/Stakes-social.svg:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/public/img/chainwiz.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dev-protocol/niwa/66eb9dedc30ce9700ff636fa09fcffeeae479649/public/img/chainwiz.png
--------------------------------------------------------------------------------
/public/img/uzomia.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dev-protocol/niwa/66eb9dedc30ce9700ff636fa09fcffeeae479649/public/img/uzomia.png
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "enabled": false
3 | }
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'
2 | import Home from './pages/home'
3 | import UserPropertiesListPage from './pages/user-properties-list'
4 | import UserPositionsListPage from './pages/user-positions-list'
5 | import GrowthPage from './pages/growth'
6 | import PropertyOverviewPage from './pages/properties/property-overview'
7 | import TokenizeMarketSelect from './pages/tokenize-market-select'
8 | import TokenizeFormPage from './pages/tokenize-form'
9 | import TokenizeSubmit from './pages/tokenize-submit'
10 | import AuthCallbackPage from './pages/auth-callback'
11 |
12 | import ConnectButton from './components/ConnectButton'
13 | import DPLHeader from './components/DPLHeader'
14 |
15 | import { useWalletProviderContext, WalletContext } from './context/walletContext'
16 | import { TokenizeProvider } from './context/tokenizeContext'
17 |
18 | import PageNotFound from './pages/errors/404'
19 | import StakePage from './pages/properties/stake'
20 | import NetworkSelectPage from './pages/network-select'
21 | import { Background } from './components/Background'
22 | import WaitMarketPage from './pages/wait-market'
23 | import MarkdownPage from './pages/markdown-page'
24 | import { ReactComponent as PrivacyPolicy } from '../PRIVACY-POLICY.md'
25 | import Footer from './components/Footer'
26 | import PropertyHoldersPage from './pages/properties/property-holders'
27 | import PropertyOutlet from './pages/properties'
28 | import PropertyTabsContainer from './pages/properties/PropertyTabsContainer'
29 | import AppsPage from './pages/apps'
30 | import HowItWorksPage from './pages/how-it-works'
31 | import PropertyStakersPage from './pages/properties/property-stakers'
32 | import { useTerms } from './hooks/useTerms'
33 | import ReactMarkdown from 'react-markdown'
34 | import rehypeRaw from 'rehype-raw'
35 |
36 | function App() {
37 | const walletProviderContext = useWalletProviderContext()
38 | const isRoot = import.meta.env.VITE_IS_ROOT === 'true'
39 | const { terms } = useTerms()
40 |
41 | return (
42 |
43 |
44 |
45 | {isRoot && (
46 |
47 |
48 |
49 |
50 |
51 |
52 | } />
53 | { }} />
54 |
58 | {{terms} }
59 |
60 | }
61 | />
62 | } />
63 | } />
64 |
65 |
66 |
67 |
68 |
69 |
70 | )}
71 |
72 | {!isRoot && (
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 |
107 | {{terms} }
108 |
109 | }
110 | />
111 | } />
112 | } />
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | )}
122 |
123 |
124 | )
125 | }
126 |
127 | export default App
128 |
--------------------------------------------------------------------------------
/src/components/BackButton/index.tsx:
--------------------------------------------------------------------------------
1 | import { FunctionComponent } from 'react'
2 | import { FaChevronLeft } from 'react-icons/fa'
3 | import { Link } from 'react-router-dom'
4 |
5 | interface BackButtonProps {
6 | title: string
7 | path: string
8 | }
9 |
10 | const BackButton: FunctionComponent = ({ title, path }) => {
11 | return (
12 |
13 |
14 |
15 | {title}
16 |
17 |
18 | )
19 | }
20 |
21 | export default BackButton
22 |
--------------------------------------------------------------------------------
/src/components/Background/index.tsx:
--------------------------------------------------------------------------------
1 | import { FunctionComponent } from 'react'
2 |
3 | interface BackgroundProps {}
4 |
5 | const Background: FunctionComponent = () => {
6 | return
7 | }
8 |
9 | export { Background }
10 |
--------------------------------------------------------------------------------
/src/components/Card/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | interface CardProps {
4 | isDisabled?: boolean
5 | }
6 |
7 | const Card: React.FC = ({ children, isDisabled = false }) => {
8 | return (
9 |
14 | {children}
15 |
16 | )
17 | }
18 |
19 | export default Card
20 |
--------------------------------------------------------------------------------
/src/components/CheckBox/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | interface CheckBoxProps {
4 | isChecked: boolean
5 | text: string
6 | name: string
7 | onChange: () => {}
8 | }
9 |
10 | const CheckBox: React.FC = ({ isChecked, text, name, onChange }) => {
11 | return (
12 |
13 |
14 | {text}
15 |
16 | )
17 | }
18 |
19 | export default CheckBox
20 |
--------------------------------------------------------------------------------
/src/components/ConnectButton/index.tsx:
--------------------------------------------------------------------------------
1 | import { providers } from 'ethers'
2 | import { whenDefined } from '@devprotocol/util-ts'
3 | import React, { useEffect, useState } from 'react'
4 | import { useProvider } from '../../context/walletContext'
5 | import HSButton from '../HSButton'
6 | import { Link } from 'react-router-dom'
7 | import { FaChevronRight, FaExclamationTriangle } from 'react-icons/fa'
8 | import { crunchAddress, deployedNetworkToReadable } from '../../utils/utils'
9 | import { WagmiConfig } from 'wagmi'
10 | import { watchWalletClient } from '@wagmi/core'
11 | import { polygonMumbai, polygon, mainnet } from 'wagmi/chains'
12 | import { createWeb3Modal, defaultWagmiConfig, useWeb3Modal } from '@web3modal/wagmi/react'
13 |
14 | type ConnectButtonParams = {}
15 |
16 | const web3ModalProjectId = import.meta.env.VITE_WALLET_CONNECT_PROJECT_ID || ''
17 | const chains = [polygonMumbai, polygon, mainnet]
18 | const wagmiConfig = defaultWagmiConfig({ chains, projectId: web3ModalProjectId, appName: 'Niwa' })
19 | createWeb3Modal({ wagmiConfig, projectId: web3ModalProjectId, chains })
20 |
21 | const ConnectButton: React.FC = () => {
22 | const [address, setAddress] = useState(null)
23 |
24 | const web3Modal = useWeb3Modal()
25 | const { ethersProvider, setEthersProvider, isValidConnectedNetwork } = useProvider()
26 |
27 | useEffect(() => {
28 | watchWalletClient({}, wallet => {
29 | whenDefined(wallet, async wal => {
30 | const connectedProvider = wal.transport
31 | const newProvider = whenDefined(connectedProvider, p => new providers.Web3Provider(p))
32 | setEthersProvider(newProvider)
33 | }) ?? setEthersProvider(undefined)
34 | })
35 | })
36 |
37 | useEffect(() => {
38 | const getProvider = async (): Promise => {
39 | if (ethersProvider) {
40 | const currentAddress = await ethersProvider.getSigner().getAddress()
41 | setAddress(currentAddress)
42 | } else {
43 | setAddress(null)
44 | }
45 | }
46 |
47 | getProvider()
48 | }, [ethersProvider])
49 |
50 | return (
51 |
52 |
53 | {address && (
54 |
55 |
56 | {isValidConnectedNetwork && (
57 |
58 |
{deployedNetworkToReadable()}
59 |
60 |
61 |
62 |
{crunchAddress(address)}
63 |
64 |
65 |
66 |
67 | )}
68 | {!isValidConnectedNetwork && (
69 |
70 |
71 |
72 | Connect Wallet to {deployedNetworkToReadable()}
73 |
74 |
75 | )}
76 |
77 |
78 | )}
79 | {!address && (
80 |
web3Modal.open()} type="filled">
81 | Connect Wallet
82 |
83 | )}
84 |
85 |
86 | )
87 | }
88 |
89 | export default ConnectButton
90 |
--------------------------------------------------------------------------------
/src/components/CopyButton/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { FaRegCopy } from 'react-icons/fa'
3 |
4 | interface CopyButtonProps {
5 | textToCopy: string
6 | }
7 |
8 | const CopyButton: React.FC = ({ textToCopy }) => {
9 | const copyToClipboard = () => navigator.clipboard.writeText(textToCopy)
10 |
11 | return (
12 |
13 |
14 |
15 | )
16 | }
17 |
18 | export default CopyButton
19 |
--------------------------------------------------------------------------------
/src/components/DPLHeader/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router-dom'
3 |
4 | interface LauncherHeaderProps {
5 | // Props
6 | }
7 |
8 | const LauncherHeader: React.FC = ({ children }) => {
9 | return (
10 |
19 | )
20 | }
21 |
22 | export default LauncherHeader
23 |
--------------------------------------------------------------------------------
/src/components/DPLHr/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | interface DPLHrProps {
4 | // Props
5 | }
6 |
7 | const DPLHr: React.FC = () => {
8 | return
9 | }
10 |
11 | export { DPLHr }
12 |
--------------------------------------------------------------------------------
/src/components/DPLTitleBar/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | interface DPLTitleBarProps {
4 | title: string
5 | className?: string
6 | }
7 |
8 | const DPLTitleBar: React.FC = ({ title, className }) => {
9 | return (
10 | <>
11 |
14 | {title}
15 |
16 | >
17 | )
18 | }
19 |
20 | export default DPLTitleBar
21 |
--------------------------------------------------------------------------------
/src/components/Detail/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | interface DetailProps {
4 | label: string
5 | valueElem: React.ReactElement
6 | }
7 |
8 | const Detail: React.FC = ({ label, valueElem }) => {
9 | return (
10 |
11 | {label}
12 | {valueElem}
13 |
14 | )
15 | }
16 |
17 | export default Detail
18 |
--------------------------------------------------------------------------------
/src/components/Footer/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { FaDiscord, FaGithubSquare } from 'react-icons/fa'
3 | import { Link } from 'react-router-dom'
4 | import { DEPLOYMENTS } from '../../const'
5 | import { DPLHr } from '../DPLHr'
6 | import FooterImg from '../../img/FOOTER_IMG_Powered by Dev Protocol.svg'
7 |
8 | interface FooterProps {}
9 |
10 | interface DPLFooterProps {
11 | className?: string
12 | }
13 |
14 | const DPLFooter: React.FC = ({ className, children }) => {
15 | return (
16 |
17 |
18 | {children}
19 |
20 | )
21 | }
22 |
23 | const DPLFooterSection: React.FC = ({ children }) => {
24 | return {children}
25 | }
26 |
27 | const Footer: React.FC = () => {
28 | return (
29 |
30 |
31 |
32 |
49 |
50 |
51 | Terms and Conditions
52 |
53 |
54 | Privacy Policy
55 |
56 |
57 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | )
78 | }
79 |
80 | export default Footer
81 |
--------------------------------------------------------------------------------
/src/components/Form/FormInput.tsx:
--------------------------------------------------------------------------------
1 | import { FunctionComponent } from 'react'
2 |
3 | interface FormInputProps {
4 | id: string
5 | placeholder?: string
6 | value: string
7 | onChange: (val: string) => void
8 | disabled: boolean
9 | }
10 |
11 | const FormInput: FunctionComponent = ({ id, placeholder, value, onChange, disabled }) => {
12 | return (
13 | onChange(e.target.value)}
18 | disabled={disabled}
19 | />
20 | )
21 | }
22 |
23 | export default FormInput
24 |
--------------------------------------------------------------------------------
/src/components/Form/FormInputLabel.tsx:
--------------------------------------------------------------------------------
1 | import { FunctionComponent } from 'react'
2 |
3 | interface FormInputLabelProps {
4 | label: string
5 | id: string
6 | required: boolean
7 | }
8 |
9 | const FormInputLabel: FunctionComponent = ({ label, id, required }) => {
10 | return (
11 |
12 | {label}
13 | {required && * }
14 |
15 | )
16 | }
17 |
18 | export default FormInputLabel
19 |
--------------------------------------------------------------------------------
/src/components/Form/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import HSTextField from '../HSTextField'
3 |
4 | interface FormFieldProps {
5 | label: string
6 | id: string
7 | required?: boolean
8 | placeholder?: string
9 | disabled?: boolean
10 | value: string
11 | isError?: boolean
12 | onChange?: (val: string) => void
13 | }
14 |
15 | const FormField: React.FC = ({
16 | label,
17 | id,
18 | required = false,
19 | disabled = false,
20 | placeholder,
21 | value,
22 | onChange,
23 | isError,
24 | children
25 | }) => {
26 | return (
27 |
28 | (onChange ? onChange(val) : () => {})}
35 | isError={isError}
36 | isRequired={required}
37 | isDisabled={disabled}
38 | >
39 | {children}
40 |
41 |
42 | )
43 | }
44 |
45 | export default FormField
46 |
--------------------------------------------------------------------------------
/src/components/HSButton/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router-dom'
3 |
4 | type ButtonStyle = 'outlined' | 'filled' | 'danger' | 'success'
5 |
6 | interface HSButtonProps {
7 | label?: string
8 | icon?: React.ReactElement | string
9 | type?: ButtonStyle
10 | link?: string
11 | context?: 'button' | 'submit' | 'reset' | undefined
12 | onClick?: React.MouseEventHandler | (() => void)
13 | isDisabled?: boolean
14 | }
15 |
16 | const HSButton: React.FC = ({
17 | label,
18 | icon,
19 | type = 'filled',
20 | link,
21 | onClick,
22 | isDisabled,
23 | children,
24 | context = 'button'
25 | }) => {
26 | const assertBackground = (type: ButtonStyle) => {
27 | switch (type) {
28 | case 'filled':
29 | return 'bg-black'
30 | case 'danger':
31 | case 'success':
32 | return 'bg-white'
33 |
34 | case 'outlined':
35 | return 'transparent'
36 |
37 | default:
38 | return 'bg-blue-500'
39 | }
40 | }
41 |
42 | const assertText = (type: ButtonStyle) => {
43 | switch (type) {
44 | case 'filled':
45 | return 'text-white'
46 | case 'outlined':
47 | return 'text-blue'
48 | case 'danger':
49 | return 'text-red'
50 | case 'success':
51 | return 'text-success'
52 |
53 | default:
54 | return 'bg-blue-500'
55 | }
56 | }
57 |
58 | const assertBorder = (type: ButtonStyle) => {
59 | switch (type) {
60 | case 'outlined':
61 | return 'border-blue-400 border-2 hover:border-blue-700'
62 | case 'danger':
63 | return 'border-red-500'
64 | case 'success':
65 | return 'border-success'
66 | case 'filled':
67 | default:
68 | return 'border-transparent'
69 | }
70 | }
71 |
72 | const btnStyles = {
73 | background: assertBackground(type),
74 | text: assertText(type),
75 | border: assertBorder(type)
76 | }
77 |
78 | const ButtonBase = (
79 |
88 | {icon && {icon} }
89 | {label || (children && {label || children} )}
90 |
91 | )
92 |
93 | if (!link) {
94 | return ButtonBase
95 | } else {
96 | const isLinkExternal: boolean = link.charAt(0) !== '/' && link.charAt(0) !== '#'
97 | return isLinkExternal ? (
98 |
99 | {ButtonBase}
100 |
101 | ) : (
102 | {ButtonBase}
103 | )
104 | }
105 | }
106 |
107 | export default HSButton
108 |
--------------------------------------------------------------------------------
/src/components/HSTextField/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | interface HSTextFieldProps {
4 | name: string
5 | label: string
6 | type: string
7 | placeholder?: string
8 | value?: string
9 | isError?: boolean
10 | isRequired?: boolean
11 | isDisabled?: boolean
12 | onChange: (val: string) => void
13 | }
14 |
15 | const HSTextField: React.FC = ({
16 | name,
17 | label,
18 | type,
19 | placeholder,
20 | value,
21 | isError,
22 | isRequired,
23 | isDisabled,
24 | onChange,
25 | children
26 | }) => {
27 | const inputClasses = 'border border-gray-300 rounded py-2 px-sm'
28 |
29 | return (
30 |
31 |
32 | {label} {isRequired && * }
33 |
34 | {type == 'textarea' ? (
35 |
57 | )
58 | }
59 |
60 | export default HSTextField
61 |
--------------------------------------------------------------------------------
/src/components/HomeNavItem/index.tsx:
--------------------------------------------------------------------------------
1 | import { FunctionComponent } from 'react'
2 | import { Link } from 'react-router-dom'
3 | import { FaChevronRight } from 'react-icons/fa'
4 |
5 | interface HomeNavItemProps {
6 | title: string
7 | message: string
8 | path?: string
9 | isExternal: boolean
10 | isDisabled?: boolean
11 | className?: string
12 | }
13 |
14 | const HomeNavItem: FunctionComponent = ({
15 | title,
16 | message,
17 | path,
18 | isExternal,
19 | isDisabled,
20 | className
21 | }) => {
22 | return (
23 | <>
24 | {isDisabled && (
25 |
26 |
27 |
28 | )}
29 | {isExternal && (
30 |
31 |
32 |
33 | )}
34 |
35 | {!isExternal && path && (
36 |
37 |
38 |
39 | )}
40 | >
41 | )
42 | }
43 |
44 | interface HomeNavItemContentProps {
45 | title: string
46 | message: string
47 | isDisabled?: boolean
48 | className?: string
49 | }
50 |
51 | const HomeNavItemContent: FunctionComponent = ({
52 | title,
53 | message,
54 | className,
55 | isDisabled = false
56 | }) => {
57 | return (
58 |
63 |
64 |
{title}
65 |
66 |
67 |
68 |
69 |
{message}
70 |
71 | )
72 | }
73 |
74 | export default HomeNavItem
75 |
--------------------------------------------------------------------------------
/src/components/HowItWorks/FaqCard.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | interface FaqCardProps {
4 | question?: string
5 | answer?: string
6 | }
7 |
8 | const FaqCard: React.FC = ({ question, answer, children }) => {
9 | return (
10 |
11 |
{question}
12 |
{answer || children}
13 |
14 | )
15 | }
16 |
17 | export default FaqCard
18 |
--------------------------------------------------------------------------------
/src/components/HowItWorks/StepsCard.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | interface StepsCardProps {
4 | label?: string | React.ReactElement
5 | media?: string
6 | mediaAlt?: string
7 | }
8 |
9 | const StepsCard: React.FC = ({ label, media, mediaAlt }) => {
10 | return (
11 |
12 | {media &&
}
13 |
{label}
14 |
15 | )
16 | }
17 |
18 | export default StepsCard
19 |
--------------------------------------------------------------------------------
/src/components/HowItWorks/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import StepsCard from './StepsCard'
3 |
4 | import Anpao from '../../img/ANPAO.svg'
5 | import Card from '../../img/CARD.svg'
6 | import Purse from '../../img/PURSE.svg'
7 | import Seedling from '../../img/SEEDLING.svg'
8 | import FaqCard from './FaqCard'
9 | import { FaExternalLinkAlt } from 'react-icons/fa'
10 | import { getMajorDexUrl } from '../../utils/utils'
11 |
12 | interface HowItWorksProps {
13 | // Props
14 | }
15 |
16 | const HowItWorks: React.FC = () => {
17 | const dex = getMajorDexUrl()
18 | return (
19 |
20 |
How it works
21 |
22 |
25 | Get DEV{dex ? : ''}
26 |
27 | }
28 | media={Card}
29 | mediaAlt="Image"
30 | />
31 |
32 |
33 |
34 |
35 |
36 |
37 | DEV is a token to support your favorite creators and earn rewards. It allows for long-term support through
38 | staking, rather than temporary donations or trading. As a supporter, you can get exclusive NFTs for supporters
39 | and special Perks. 🌱
40 |
41 |
42 | {/* eslint-disable-next-line react/no-unescaped-entities */}
43 | It's not a donation, but a temporary deposit of DEV tokens to financially support the creators. If you stop
44 | {/* eslint-disable-next-line react/no-unescaped-entities */}
45 | staking, the DEV token you deposited will be returned to you. It's like reusable money.
46 |
47 |
48 | Your reward will increase slightly every 15 seconds (1 block) while you are staking.
49 |
50 |
51 | Yes, you can easily manage your staking position at any time on stakes.social. Please go to{' '}
52 | stakes.social and use `Portfolio` feature.
53 |
54 |
55 |
56 | )
57 | }
58 |
59 | export default HowItWorks
60 |
--------------------------------------------------------------------------------
/src/components/NavTabs/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { NavLink } from 'react-router-dom'
3 |
4 | interface NavTabsProps {}
5 |
6 | export const NavTabs: React.FC = ({ children }) => {
7 | return {children}
8 | }
9 |
10 | interface NavTabItemProps {
11 | title: string
12 | path: string
13 | }
14 |
15 | export const NavTabItem: React.FC = ({ title, path }) => {
16 | return (
17 |
20 | `mr-sm rounded px-sm py-1 shadow ${isActive ? `bg-black from-primary to-secondary text-white` : 'bg-white'}`
21 | }
22 | to={path}
23 | >
24 | {title}
25 |
26 | )
27 | }
28 |
--------------------------------------------------------------------------------
/src/components/ProgressStepper/ProgressStep.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { FaCheck } from 'react-icons/fa'
3 |
4 | interface ProgressStepProps {
5 | label: string
6 | index: number
7 | currentStep: number
8 | isCompleted: boolean
9 | }
10 |
11 | const ProgressStep: React.FC = ({ label, index, currentStep, isCompleted }) => {
12 | return (
13 |
14 |
19 | {isCompleted && }
20 | {!isCompleted && {index + 1} }
21 |
22 |
{label}
23 |
24 |
25 |
26 | )
27 | }
28 |
29 | export default ProgressStep
30 |
--------------------------------------------------------------------------------
/src/components/ProgressStepper/ProgressStepper.scss:
--------------------------------------------------------------------------------
1 | .md-step:first-child .md-step-bar-left,
2 | .md-step:last-child .md-step-bar-right {
3 | display: none;
4 | }
5 |
6 | .md-step .md-step-bar-left,
7 | .md-step .md-step-bar-right {
8 | top: 36px;
9 | }
10 | .md-step .md-step-bar-right {
11 | right: 0;
12 | left: 50%;
13 | margin-left: 20px;
14 | }
15 | .md-step .md-step-bar-left {
16 | left: 0;
17 | right: 50%;
18 | margin-right: 20px;
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/ProgressStepper/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ProgressStep from './ProgressStep'
3 | import './ProgressStepper.scss'
4 |
5 | interface ProgressStepperProps {
6 | currentStep: number
7 | completedStep: number
8 | stepLabels: readonly string[]
9 | }
10 |
11 | // credit to https://codepen.io/thdeux/pen/zBGNrM for the basis of this stepper
12 | const ProgressStepper: React.FC = ({ currentStep, completedStep, stepLabels }) => {
13 | return (
14 |
15 | {stepLabels.map((label, i) => (
16 |
17 | ))}
18 |
19 | )
20 | }
21 |
22 | export default ProgressStepper
23 |
--------------------------------------------------------------------------------
/src/components/Spinner/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | interface SpinnerProps {}
4 |
5 | export const Spinner: React.FC = () => {
6 | return (
7 |
14 |
18 |
22 |
23 | )
24 | }
25 |
26 | interface SectionLoadingProps {}
27 |
28 | export const SectionLoading: React.FC = () => {
29 | return (
30 |
31 |
32 |
33 | )
34 | }
35 |
--------------------------------------------------------------------------------
/src/components/TitleSubSection/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | interface TitleSubSectionProps {
4 | classNames?: string
5 | }
6 |
7 | const TitleSubSection: React.FC = ({ classNames, children }) => {
8 | return {children}
9 | }
10 |
11 | export default TitleSubSection
12 |
--------------------------------------------------------------------------------
/src/components/Tweet/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { FaTwitter } from 'react-icons/fa'
3 |
4 | interface TweetProps {
5 | params: { [key: string]: string }
6 | className?: string
7 | }
8 |
9 | const TweetLarge: React.FC = ({ params, className, children }) => {
10 | return (
11 |
17 | {children}
18 |
19 |
20 | )
21 | }
22 |
23 | export { TweetLarge }
24 |
--------------------------------------------------------------------------------
/src/const/cache-path.ts:
--------------------------------------------------------------------------------
1 | export const SWRCachePath = {
2 | getPropertyData: (address?: string) => `propertyData/${address}`,
3 | getUserPropertyList: (address?: string) => `userPropertyList/${address}`,
4 | getEnabledMarkets: () => `enabledMarkets`,
5 | getMarket: (address: string) => `market/${address}`,
6 | getPropertyMetrics: (address: string) => `propertyMetrics/${address}`,
7 | useGetAssetsByProperties: (propertyAddress?: string) => `useGetAssetsByProperties/${propertyAddress}`,
8 | usePropertyDetails: (propertyAddress?: string) => `propertyDetails/${propertyAddress}`,
9 | getSTokensPositionsOfProperty: (propertyAddress?: string) => `sTokensPositionsOfProperty/${propertyAddress}`
10 | } as const
11 |
--------------------------------------------------------------------------------
/src/const/index.ts:
--------------------------------------------------------------------------------
1 | export enum Market {
2 | GITHUB = 'GITHUB',
3 | YOUTUBE = 'YOUTUBE',
4 | DISCORD = 'DISCORD',
5 | INVALID = 'INVALID'
6 | }
7 |
8 | export const EMPTY_USER_TOKEN_PATH = '0x00'
9 |
10 | export const ERROR_MSG = {
11 | no_provider: 'No user provider found. Is your wallet connected?',
12 | invalid_network: 'No valid network name found. Are you using L2 Arbitrum or Polygon?',
13 | no_matching_market_options: 'No matching market addresses found',
14 | no_matching_market: 'No matching market address found',
15 | no_property_address: 'No property address found'
16 | } as const
17 |
18 | export const FORM_HINT = {
19 | symbol_length: 'Symbol should be 3 to 4 characters long (for example DEV)'
20 | } as const
21 |
22 | export const DEPLOYMENTS = {
23 | arbitrum_one: 'https://arbitrum.niwa.xyz',
24 | polygon_mumbai: 'https://polygon-mumbai.niwa.xyz',
25 | polygon_mainnet: 'https://polygon.niwa.xyz'
26 | }
27 |
28 | export const NETWORK_RPC_URLS = {
29 | arbitrum_one: 'https://arb1.arbitrum.io/rpc',
30 | polygon_mumbai: 'https://rpc-mumbai.maticvigil.com/',
31 | polygon_mainnet: 'https://polygon-rpc.com/'
32 | }
33 |
34 | export const TOKENIZE_STEP_LABELS = ['Select Market', 'Enter Token Details', 'Create Token'] as const
35 |
36 | export const TERMS_OF_SERVICE_URL = 'https://raw.githubusercontent.com/dev-protocol/legal/main/TERMS-OF-USE.md'
37 |
--------------------------------------------------------------------------------
/src/context/tokenizeContext.tsx:
--------------------------------------------------------------------------------
1 | import { UndefinedOr } from '@devprotocol/util-ts'
2 | import { ethers } from 'ethers'
3 | import React, { Dispatch, SetStateAction, useCallback, useEffect, useState } from 'react'
4 | import { useProvider } from '../context/walletContext'
5 | import { isValidNetwork } from '../utils/utils'
6 |
7 | export type GithubFormParams = {}
8 |
9 | export type ITokenize = {
10 | assetName: string
11 | setAssetName: Dispatch>
12 | tokenName: string
13 | setTokenName: Dispatch>
14 | tokenSymbol: string
15 | setTokenSymbol: Dispatch>
16 | personalAccessToken: string
17 | setPersonalAccessToken: Dispatch>
18 | isValid: boolean
19 | setIsValid: Dispatch>
20 | network: UndefinedOr
21 | setNetwork: Dispatch>>
22 | address: string
23 | setAddress: Dispatch>
24 | validateForm: () => void
25 | agreedToTerms: boolean
26 | setAgreedToTerms: Dispatch>
27 | reset: () => void
28 | }
29 |
30 | const tokenize: ITokenize = {
31 | assetName: '',
32 | setAssetName: () => {},
33 | tokenName: '',
34 | setTokenName: () => {},
35 | tokenSymbol: '',
36 | setTokenSymbol: () => {},
37 | personalAccessToken: '',
38 | setPersonalAccessToken: () => {},
39 | isValid: false,
40 | setIsValid: () => {},
41 | network: undefined,
42 | setNetwork: () => {},
43 | address: '',
44 | setAddress: () => {},
45 | validateForm: () => {},
46 | agreedToTerms: false,
47 | setAgreedToTerms: () => {},
48 | reset: () => {}
49 | }
50 |
51 | export const TokenizeContext = React.createContext(tokenize)
52 |
53 | export const TokenizeProvider: React.FC = ({ children }) => {
54 | const [assetName, setAssetName] = useState('')
55 | const [tokenName, setTokenName] = useState('')
56 | const [tokenSymbol, setTokenSymbol] = useState('')
57 | const [personalAccessToken, setPersonalAccessToken] = useState('')
58 | const [isValid, setIsValid] = useState(false)
59 | const [network, setNetwork] = useState>()
60 | const [address, setAddress] = useState('')
61 | const [agreedToTerms, setAgreedToTerms] = useState(false)
62 | const { ethersProvider } = useProvider()
63 |
64 | const detectNetwork = useCallback(async () => {
65 | if (ethersProvider) {
66 | const net = await ethersProvider.detectNetwork()
67 | setNetwork(net)
68 | }
69 | }, [ethersProvider, setNetwork])
70 |
71 | useEffect(() => {
72 | if (ethersProvider) {
73 | detectNetwork()
74 | ;(async () => {
75 | const userAddress = await ethersProvider.getSigner().getAddress()
76 | setAddress(userAddress)
77 | })()
78 | }
79 | }, [ethersProvider, detectNetwork])
80 |
81 | const validateForm = useCallback(() => {
82 | if (assetName.length <= 0) {
83 | setIsValid(false)
84 | return
85 | }
86 |
87 | if (tokenName.length < 3) {
88 | setIsValid(false)
89 | return
90 | }
91 |
92 | if (!isValidNetwork(network?.chainId)) {
93 | setIsValid(false)
94 | return
95 | }
96 | if (tokenSymbol.length < 3 || tokenSymbol.length > 4) {
97 | setIsValid(false)
98 | return
99 | }
100 | if (personalAccessToken.length <= 0) {
101 | setIsValid(false)
102 | return
103 | }
104 |
105 | setIsValid(true)
106 | }, [assetName.length, tokenName.length, tokenSymbol.length, personalAccessToken.length, network?.chainId])
107 |
108 | useEffect(() => validateForm(), [assetName, tokenName, tokenSymbol, personalAccessToken, validateForm])
109 |
110 | const reset = () => {
111 | setAssetName('')
112 | setTokenName('')
113 | setTokenSymbol('')
114 | setPersonalAccessToken('')
115 | setIsValid(false)
116 | setAgreedToTerms(false)
117 | }
118 |
119 | return (
120 |
142 | {children}
143 |
144 | )
145 | }
146 |
--------------------------------------------------------------------------------
/src/context/walletContext.ts:
--------------------------------------------------------------------------------
1 | import { createContext, Dispatch, SetStateAction, useContext, useEffect, useMemo, useState } from 'react'
2 | import { providers, utils } from 'ethers'
3 | import { UndefinedOr } from '@devprotocol/util-ts'
4 | import {
5 | connectedNetworkMatchesDeployment,
6 | deployedNetworkToChainId,
7 | deployedNetworkToReadable,
8 | getExplorerUrl,
9 | getRpcUrlByChainId,
10 | infuraEndpoint
11 | } from '../utils/utils'
12 |
13 | export interface IWallet {
14 | ethersProvider?: providers.Web3Provider
15 | setEthersProvider: Dispatch>>
16 | nonConnectedEthersProvider: providers.JsonRpcProvider
17 | isValidConnectedNetwork: boolean
18 | setIsValidConnectedNetwork: Dispatch>
19 | }
20 |
21 | const nonConnectedEthersProvider = () => new providers.JsonRpcProvider(infuraEndpoint())
22 |
23 | export const wallet: IWallet = {
24 | ethersProvider: undefined,
25 | setEthersProvider: () => {},
26 | nonConnectedEthersProvider: nonConnectedEthersProvider(),
27 | isValidConnectedNetwork: false,
28 | setIsValidConnectedNetwork: () => {}
29 | }
30 |
31 | export function useWalletProviderContext(): IWallet {
32 | const [ethersProvider, setEthersProvider] = useState>(undefined)
33 | const [isValidConnectedNetwork, setIsValidConnectedNetwork] = useState(true)
34 |
35 | useEffect(() => {
36 | if (!ethersProvider) {
37 | return
38 | }
39 |
40 | // pulled from https://docs.metamask.io/guide/rpc-api.html#usage-with-wallet-switchethereumchain
41 | const promptWalletNetworkChange = async (chainId: number) => {
42 | console.log('promptWalletNetworkChange: ', chainId)
43 |
44 | // Check if MetaMask is installed
45 | // MetaMask injects the global API into window.ethereum
46 | if (ethersProvider && ethersProvider.provider && ethersProvider.provider.request) {
47 | try {
48 | // check if the chain to connect to is installed
49 | await ethersProvider.provider.request({
50 | method: 'wallet_switchEthereumChain',
51 | params: [{ chainId: `${utils.hexValue(deployedNetworkToChainId())}` }] // chainId must be in hexadecimal numbers
52 | })
53 | } catch (error: any) {
54 | console.log('error: ', error)
55 | // This error code indicates that the chain has not been added to MetaMask
56 | // if it is not, then install it into the user MetaMask
57 | if (error.code === 4902) {
58 | try {
59 | await ethersProvider.provider.request({
60 | method: 'wallet_addEthereumChain',
61 | params: [
62 | {
63 | chainId: utils.hexValue(deployedNetworkToChainId()),
64 | chainName: deployedNetworkToReadable(),
65 | nativeCurrency: {
66 | name: chainId === 421611 || chainId === 42161 ? 'Ether' : 'Matic',
67 | symbol: chainId === 421611 || chainId === 42161 ? 'ETH' : 'MATIC', // 2-6 characters long
68 | decimals: 18
69 | },
70 | rpcUrls: [getRpcUrlByChainId(chainId)],
71 | blockExplorerUrls: [getExplorerUrl()]
72 | }
73 | ]
74 | })
75 | } catch (addError) {
76 | console.error(addError)
77 | }
78 | }
79 | console.error(error)
80 | }
81 | } else {
82 | // if no window.ethereum then MetaMask is not installed
83 | console.error('metamask not installed')
84 | }
85 | }
86 |
87 | ;(async () => {
88 | setIsValidConnectedNetwork(false)
89 | const chainId = (await ethersProvider.getNetwork()).chainId
90 | if (connectedNetworkMatchesDeployment(chainId)) {
91 | setIsValidConnectedNetwork(true)
92 | return
93 | }
94 |
95 | promptWalletNetworkChange(chainId)
96 | })()
97 | }, [ethersProvider])
98 |
99 | const context = useMemo(
100 | () => ({
101 | ethersProvider,
102 | setEthersProvider,
103 | nonConnectedEthersProvider: ethersProvider ?? nonConnectedEthersProvider(),
104 | isValidConnectedNetwork,
105 | setIsValidConnectedNetwork
106 | }),
107 | [ethersProvider, setEthersProvider, isValidConnectedNetwork]
108 | )
109 | return context
110 | }
111 |
112 | export const WalletContext = createContext(wallet)
113 |
114 | export function useProvider(): IWallet {
115 | return useContext(WalletContext)
116 | }
117 |
--------------------------------------------------------------------------------
/src/css/background.css:
--------------------------------------------------------------------------------
1 | .background {
2 | background: linear-gradient(
3 | 148.95deg,
4 | #eceff3 5.89%,
5 | #eee6e5 22.2%,
6 | #f7e8e1 31.57%,
7 | #fbefdf 38.2%,
8 | #f0f6e9 43.75%,
9 | #e6f5f2 48.55%,
10 | #e8edf9 54.76%,
11 | #ebecf5 64.71%,
12 | #eceff3 116.08%
13 | );
14 | background-size: 180% 180%;
15 | animation: gradient-animation 12s ease infinite;
16 | }
17 |
18 | @keyframes gradient-animation {
19 | 0% {
20 | background-position: 0% 50%;
21 | }
22 | 50% {
23 | background-position: 100% 50%;
24 | }
25 | 100% {
26 | background-position: 0% 50%;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/env.d.ts:
--------------------------------------------------------------------------------
1 | interface ImportMetaEnv {
2 | readonly VITE_L2_NETWORK: 'arbitrum-one' | 'polygon-mainnet' | 'polygon-mumbai'
3 | readonly VITE_INFURA_PROJECT_ID: string
4 | readonly VITE_BASE_INFURA_ENDPOINT: string
5 | readonly VITE_IS_ROOT: string
6 | readonly VITE_WALLET_CONNECT_PROJECT_ID: string
7 | }
8 |
9 | interface ImportMeta {
10 | readonly env: ImportMetaEnv
11 | }
12 |
--------------------------------------------------------------------------------
/src/favicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/hooks/useAddToWalletList.ts:
--------------------------------------------------------------------------------
1 | import { UndefinedOr } from '@devprotocol/util-ts'
2 | import { useState } from 'react'
3 | import { isError } from '../utils/utils'
4 | import { providers } from 'ethers'
5 |
6 | export const useAddToWalletList = () => {
7 | const [error, setError] = useState>()
8 | const callback = async (
9 | tokenSymbol: string,
10 | newPropertyAddress: string,
11 | ethersProvider: providers.Web3Provider | undefined
12 | ) => {
13 | const tokenDecimals = 18
14 |
15 | try {
16 | if (!ethersProvider || !ethersProvider.provider || !ethersProvider.provider.request) {
17 | return
18 | }
19 |
20 | ethersProvider.provider.request({
21 | method: 'wallet_watchAsset',
22 | params: [
23 | {
24 | type: 'ERC20', // Initially only supports ERC20, but eventually more!
25 | options: {
26 | address: newPropertyAddress, // The address that the token is at.
27 | symbol: tokenSymbol, // A ticker symbol or shorthand, up to 5 chars.
28 | decimals: tokenDecimals // The number of decimals in the token
29 | }
30 | }
31 | ]
32 | })
33 | } catch (error) {
34 | console.log(error)
35 | setError(isError(error) ? error.message : `${error}`)
36 | }
37 | }
38 | return { addToWalletList: callback, error }
39 | }
40 |
--------------------------------------------------------------------------------
/src/hooks/useAllowance.ts:
--------------------------------------------------------------------------------
1 | import { useProvider } from '../context/walletContext'
2 | import { UndefinedOr } from '@devprotocol/util-ts'
3 | import { BigNumber, providers } from 'ethers'
4 | import { createDevContract } from '@devprotocol/dev-kit/l2'
5 | import { mapProviderToDevContracts } from '../utils/utils'
6 | import { useCallback, useState } from 'react'
7 | import { ERROR_MSG } from '../const'
8 |
9 | export const getAllowance = async ({
10 | provider,
11 | spenderAddress,
12 | tokenAddress,
13 | userAddress
14 | }: {
15 | provider: providers.BaseProvider
16 | spenderAddress: string
17 | tokenAddress: string
18 | userAddress: string
19 | }) => {
20 | const dev = createDevContract(provider)(tokenAddress)
21 | return BigNumber.from((await dev.allowance(userAddress, spenderAddress)) ?? 0)
22 | }
23 |
24 | export const useDevAllowance = () => {
25 | const { ethersProvider } = useProvider()
26 | const [isLoading, setIsLoading] = useState(false)
27 | const [error, setError] = useState>()
28 | const callback = useCallback(
29 | async (spenderAddress: string) => {
30 | setIsLoading(true)
31 | setError(undefined)
32 | if (!ethersProvider) {
33 | setIsLoading(false)
34 | setError(ERROR_MSG.no_provider)
35 | return
36 | }
37 |
38 | const [userAddress, networkContracts] = await Promise.all([
39 | ethersProvider.getSigner().getAddress(),
40 | mapProviderToDevContracts(ethersProvider)
41 | ])
42 | if (!networkContracts) {
43 | setIsLoading(false)
44 | setError(ERROR_MSG.invalid_network)
45 | return
46 | }
47 |
48 | setIsLoading(false)
49 | return await getAllowance({
50 | provider: ethersProvider,
51 | spenderAddress,
52 | tokenAddress: networkContracts.token,
53 | userAddress
54 | })
55 | },
56 | [ethersProvider]
57 | )
58 | return { fetchAllowance: callback, error, isLoading }
59 | }
60 |
--------------------------------------------------------------------------------
/src/hooks/useApprove.ts:
--------------------------------------------------------------------------------
1 | import { useProvider } from '../context/walletContext'
2 | import { UndefinedOr } from '@devprotocol/util-ts'
3 | import { constants, providers } from 'ethers'
4 | import { createDevContract } from '@devprotocol/dev-kit/l2'
5 | import { mapProviderToDevContracts } from '../utils/utils'
6 | import { useCallback, useState } from 'react'
7 | import { ERROR_MSG } from '../const'
8 |
9 | export const approve = async ({
10 | provider,
11 | spenderAddress,
12 | tokenAddress
13 | }: {
14 | provider: providers.BaseProvider
15 | spenderAddress: string
16 | tokenAddress: string
17 | }) => {
18 | const dev = createDevContract(provider)(tokenAddress)
19 | return await dev.approve(spenderAddress, constants.MaxUint256.toString())
20 | }
21 |
22 | // For testing
23 | export const revoke = async ({
24 | provider,
25 | spenderAddress
26 | }: {
27 | provider: providers.BaseProvider
28 | spenderAddress: string
29 | }) => {
30 | const networkContracts = await mapProviderToDevContracts(provider)
31 | if (!networkContracts) {
32 | return
33 | }
34 |
35 | const dev = createDevContract(provider)(networkContracts.token)
36 | return await dev.approve(spenderAddress, constants.Zero.toString())
37 | }
38 |
39 | export const useDevApprove = () => {
40 | const { ethersProvider } = useProvider()
41 | const [isLoading, setIsLoading] = useState(false)
42 | const [error, setError] = useState>()
43 | const callback = useCallback(
44 | async (spenderAddress: string) => {
45 | setIsLoading(true)
46 | setError(undefined)
47 | if (!ethersProvider) {
48 | setIsLoading(false)
49 | setError(ERROR_MSG.no_provider)
50 | return
51 | }
52 |
53 | const networkContracts = await mapProviderToDevContracts(ethersProvider)
54 | if (!networkContracts) {
55 | setIsLoading(false)
56 | setError(ERROR_MSG.invalid_network)
57 | return
58 | }
59 |
60 | try {
61 | setIsLoading(false)
62 | return await approve({
63 | provider: ethersProvider,
64 | spenderAddress,
65 | tokenAddress: networkContracts.token
66 | })
67 | } catch (error) {
68 | setError(`Error approving ${spenderAddress}`)
69 | setIsLoading(false)
70 | return
71 | }
72 | },
73 | [ethersProvider]
74 | )
75 | return { approve: callback, error, isLoading }
76 | }
77 |
--------------------------------------------------------------------------------
/src/hooks/useEnabledMarkets.ts:
--------------------------------------------------------------------------------
1 | import { useProvider } from '../context/walletContext'
2 | import useSWR from 'swr'
3 | import { SWRCachePath } from '../const/cache-path'
4 | import { UndefinedOr, whenDefined } from '@devprotocol/util-ts'
5 | import { providers } from 'ethers'
6 | import { createMarketContract, createMarketFactoryContract, MarketContract } from '@devprotocol/dev-kit/l2'
7 | import { mapProviderToDevContracts } from '../utils/utils'
8 | import { AddressContractContainer } from '../types/AddressContractContainer'
9 |
10 | export const getEnabledMarkets = async (
11 | provider: providers.BaseProvider
12 | ): Promise[]>> => {
13 | const networkDevContracts = await mapProviderToDevContracts(provider)
14 | if (!networkDevContracts) {
15 | return
16 | }
17 | const marketFactory = createMarketFactoryContract(provider)(networkDevContracts.marketFactory)
18 | const enabledMarkets = await marketFactory.getEnabledMarkets()
19 | const markets = enabledMarkets.map(address => ({ address, contract: createMarketContract(provider)(address) }))
20 | return markets
21 | }
22 |
23 | export const useEnabledMarkets = () => {
24 | const { nonConnectedEthersProvider } = useProvider()
25 | const { data, error } = useSWR(
26 | SWRCachePath.getEnabledMarkets(),
27 | () => whenDefined(nonConnectedEthersProvider, client => getEnabledMarkets(client)),
28 | {
29 | onError: err => {
30 | console.log(err)
31 | },
32 | revalidateOnFocus: false,
33 | focusThrottleInterval: 0
34 | }
35 | )
36 | return { enabledMarkets: data, error }
37 | }
38 |
--------------------------------------------------------------------------------
/src/hooks/useMarket.ts:
--------------------------------------------------------------------------------
1 | import { useProvider } from '../context/walletContext'
2 | import useSWR from 'swr'
3 | import { SWRCachePath } from '../const/cache-path'
4 | import { whenDefined } from '@devprotocol/util-ts'
5 | import { providers } from 'ethers'
6 | import { createMarketContract } from '@devprotocol/dev-kit/l2'
7 | import { mapProviderToDevContracts } from '../utils/utils'
8 |
9 | export const getMarket = async (provider: providers.BaseProvider, marketAddress: string) => {
10 | const networkDevContracts = await mapProviderToDevContracts(provider)
11 | if (!networkDevContracts) {
12 | return
13 | }
14 | const marketContract = createMarketContract(provider)(marketAddress)
15 | return marketContract
16 | }
17 |
18 | export const useMarket = (marketAddress: string) => {
19 | const { nonConnectedEthersProvider } = useProvider()
20 | const { data, error } = useSWR(
21 | SWRCachePath.getMarket(marketAddress),
22 | () => whenDefined(nonConnectedEthersProvider, client => getMarket(client, marketAddress)),
23 | {
24 | onError: err => {
25 | console.log(err)
26 | },
27 | revalidateOnFocus: false,
28 | focusThrottleInterval: 0
29 | }
30 | )
31 | return { market: data, error }
32 | }
33 |
--------------------------------------------------------------------------------
/src/hooks/useMetrics.ts:
--------------------------------------------------------------------------------
1 | import { useProvider } from '../context/walletContext'
2 | import useSWR from 'swr'
3 | import { SWRResponse } from 'swr'
4 | import { SWRCachePath } from '../const/cache-path'
5 | import { UndefinedOr, whenDefined, whenDefinedAll } from '@devprotocol/util-ts'
6 | import { providers } from 'ethers'
7 | import {
8 | createMarketBehaviorContract,
9 | createMarketContract,
10 | createMetricsContract,
11 | createMetricsFactoryContract
12 | } from '@devprotocol/dev-kit/l2'
13 | import { mapProviderToDevContracts } from '../utils/utils'
14 | import { getMarket } from './useMarket'
15 |
16 | const metricsOfProperty = async (provider: providers.BaseProvider, address?: string) => {
17 | if (!address) {
18 | return
19 | }
20 | const networkDevContracts = await mapProviderToDevContracts(provider)
21 | if (!networkDevContracts) {
22 | return
23 | }
24 | const metricsFactory = createMetricsFactoryContract(provider)(networkDevContracts.metricsFactory)
25 | const metricsAddresses = await metricsFactory.metricsOfProperty(address)
26 | return metricsAddresses
27 | }
28 |
29 | export const getId = async (provider: providers.BaseProvider, marketBehavior: string, metricsAddress: string) => {
30 | const marketBehaviorContract = createMarketBehaviorContract(provider)(marketBehavior)
31 | const id = await marketBehaviorContract.getId(metricsAddress)
32 | return id
33 | }
34 |
35 | export const getMarketMetrics = async (provider: providers.BaseProvider, marketAddress: string) => {
36 | const networkDevContracts = await mapProviderToDevContracts(provider)
37 | if (!networkDevContracts) {
38 | return
39 | }
40 | const metrics = createMetricsContract(provider)(marketAddress)
41 | return metrics.market()
42 | }
43 |
44 | export const getMarketMetricsById = async (provider: providers.BaseProvider, marketAddress: string, id: string) => {
45 | const market = createMarketContract(provider)(marketAddress)
46 | const behavior = await market.behavior()
47 | const marketBehavior = createMarketBehaviorContract(provider)(behavior)
48 | const metricsAddress = await marketBehavior.getMetrics(id) // for example github repo name or youtube channel id
49 | return metricsAddress
50 | }
51 |
52 | export type AssetProperty = {
53 | market: UndefinedOr
54 | id: UndefinedOr
55 | }
56 |
57 | export const getAssetsByProperties = async (provider: providers.BaseProvider, propertyAddress: string) => {
58 | const metrics = await metricsOfProperty(provider, propertyAddress)
59 | const markets = await whenDefined(metrics, met => Promise.all(met.map(m => getMarketMetrics(provider, m))))
60 | const behaviors = await whenDefined(markets, marks =>
61 | Promise.all(marks.map(mak => whenDefined(mak, async m => (await getMarket(provider, m))?.behavior())))
62 | )
63 | const ids = await whenDefinedAll([behaviors, metrics], ([behav, mets]) =>
64 | Promise.all(behav.map((beh, i) => whenDefinedAll([beh, mets[i]], ([b, m]) => getId(provider, b, m))))
65 | )
66 | const res = whenDefinedAll([ids, markets], ([idx, marks]) => marks.map((market, i) => ({ market, id: idx[i] })))
67 | return res
68 | }
69 |
70 | export const useGetAssetsByProperties = (propertyAddress?: string): SWRResponse, any> => {
71 | const { nonConnectedEthersProvider } = useProvider()
72 | return useSWR(SWRCachePath.useGetAssetsByProperties(propertyAddress), () =>
73 | whenDefinedAll([nonConnectedEthersProvider, propertyAddress], async ([client, property]) => {
74 | return await getAssetsByProperties(client, property)
75 | })
76 | )
77 | }
78 |
--------------------------------------------------------------------------------
/src/hooks/usePosition.ts:
--------------------------------------------------------------------------------
1 | import { useProvider } from '../context/walletContext'
2 | import { UndefinedOr } from '@devprotocol/util-ts'
3 | import { providers } from 'ethers'
4 | import { createSTokensContract } from '@devprotocol/dev-kit/l2'
5 | import { mapProviderToDevContracts } from '../utils/utils'
6 | import { useCallback, useState } from 'react'
7 | import { ERROR_MSG } from '../const'
8 |
9 | export const getPosition = async ({
10 | provider,
11 | tokenId,
12 | sTokenAddress
13 | }: {
14 | provider: providers.BaseProvider
15 | tokenId: number
16 | sTokenAddress: string
17 | }) => {
18 | const sTokens = createSTokensContract(provider)(sTokenAddress)
19 | return await sTokens.positions(tokenId)
20 | }
21 |
22 | export const usePosition = () => {
23 | const { nonConnectedEthersProvider } = useProvider()
24 | const [isLoading, setIsLoading] = useState(false)
25 | const [error, setError] = useState>()
26 | const callback = useCallback(
27 | async (tokenId: number) => {
28 | setIsLoading(true)
29 | setError(undefined)
30 | if (!nonConnectedEthersProvider) {
31 | setIsLoading(false)
32 | setError(ERROR_MSG.no_provider)
33 | return
34 | }
35 |
36 | const networkContracts = await mapProviderToDevContracts(nonConnectedEthersProvider)
37 | if (!networkContracts) {
38 | setIsLoading(false)
39 | setError(ERROR_MSG.invalid_network)
40 | return
41 | }
42 |
43 | try {
44 | setIsLoading(false)
45 | return await getPosition({
46 | provider: nonConnectedEthersProvider,
47 | tokenId,
48 | sTokenAddress: networkContracts.sTokens
49 | })
50 | } catch (error) {
51 | setError(`Fetching sToken Positions of ${tokenId}`)
52 | setIsLoading(false)
53 | return
54 | }
55 | },
56 | [nonConnectedEthersProvider]
57 | )
58 | return { fetchPosition: callback, error, isLoading }
59 | }
60 |
--------------------------------------------------------------------------------
/src/hooks/usePositionsOfOwner.ts:
--------------------------------------------------------------------------------
1 | import { useProvider } from '../context/walletContext'
2 | import { UndefinedOr } from '@devprotocol/util-ts'
3 | import { providers } from 'ethers'
4 | import { createSTokensContract } from '@devprotocol/dev-kit/l2'
5 | import { mapProviderToDevContracts } from '../utils/utils'
6 | import { useCallback, useState } from 'react'
7 | import { ERROR_MSG } from '../const'
8 |
9 | export const getPositionsOfOwner = async ({
10 | provider,
11 | userAddress,
12 | sTokenAddress
13 | }: {
14 | provider: providers.BaseProvider
15 | userAddress: string
16 | sTokenAddress: string
17 | }) => {
18 | const sTokens = createSTokensContract(provider)(sTokenAddress)
19 | return await sTokens.positionsOfOwner(userAddress)
20 | }
21 |
22 | export const usePositionsOfOwner = () => {
23 | const { nonConnectedEthersProvider } = useProvider()
24 | const [isLoading, setIsLoading] = useState(false)
25 | const [error, setError] = useState>()
26 | const callback = useCallback(
27 | async (userAddress: string) => {
28 | setIsLoading(true)
29 | setError(undefined)
30 | if (!nonConnectedEthersProvider) {
31 | setIsLoading(false)
32 | setError(ERROR_MSG.no_provider)
33 | return
34 | }
35 |
36 | const networkContracts = await mapProviderToDevContracts(nonConnectedEthersProvider)
37 | if (!networkContracts) {
38 | setIsLoading(false)
39 | setError(ERROR_MSG.invalid_network)
40 | return
41 | }
42 |
43 | try {
44 | setIsLoading(false)
45 | return await getPositionsOfOwner({
46 | provider: nonConnectedEthersProvider,
47 | userAddress,
48 | sTokenAddress: networkContracts.sTokens
49 | })
50 | } catch (error) {
51 | setError(`Fetching sToken Positions of ${userAddress}`)
52 | setIsLoading(false)
53 | return
54 | }
55 | },
56 | [nonConnectedEthersProvider]
57 | )
58 | return { fetchPositionsOfOwner: callback, error, isLoading }
59 | }
60 |
--------------------------------------------------------------------------------
/src/hooks/usePropertyBalances.ts:
--------------------------------------------------------------------------------
1 | import { whenDefined } from '@devprotocol/util-ts'
2 | import { providers } from 'ethers'
3 | import useSWR from 'swr'
4 | import { SWRCachePath } from '../const/cache-path'
5 | import { useProvider } from '../context/walletContext'
6 | import { getPropertyData } from '../utils/utils'
7 |
8 | export const getPropertyBalances = async (provider: providers.JsonRpcProvider, propertyAddress?: string) => {
9 | if (!propertyAddress) {
10 | return
11 | }
12 | const balances = await (await getPropertyData(provider, propertyAddress)).getBalances()
13 | return balances
14 | }
15 |
16 | export const usePropertyBalances = (propertyAddress?: string) => {
17 | const { nonConnectedEthersProvider } = useProvider()
18 | const { data, error } = useSWR(
19 | SWRCachePath.getUserPropertyList(propertyAddress),
20 | () => whenDefined(nonConnectedEthersProvider, client => getPropertyBalances(client, propertyAddress)),
21 | {
22 | onError: err => {
23 | console.log(err)
24 | },
25 | revalidateOnFocus: false,
26 | focusThrottleInterval: 0
27 | }
28 | )
29 | return { propertyBalances: data, error }
30 | }
31 |
--------------------------------------------------------------------------------
/src/hooks/usePropertyDetails.ts:
--------------------------------------------------------------------------------
1 | import { MarketContract } from '@devprotocol/dev-kit/l2'
2 | import { UndefinedOr } from '@devprotocol/util-ts'
3 | import { useState } from 'react'
4 | import useSWR from 'swr'
5 | import { Market } from '../const'
6 | import { SWRCachePath } from '../const/cache-path'
7 | import { useProvider } from '../context/walletContext'
8 | import { AddressContractContainer } from '../types/AddressContractContainer'
9 | import { getMarketFromString, getPropertyData, matchMarketToAsset } from '../utils/utils'
10 | import { getEnabledMarkets } from './useEnabledMarkets'
11 | import { getAssetsByProperties } from './useMetrics'
12 |
13 | export type PropertyDetails = {
14 | propertyName: string
15 | propertySymbol: string
16 | id: UndefinedOr
17 | marketAddress: string
18 | market: Market
19 | }
20 |
21 | export const usePropertyDetails = (propertyAddress?: string) => {
22 | const { nonConnectedEthersProvider } = useProvider()
23 | const [isLoading, setIsLoading] = useState(false)
24 | const [error, setError] = useState>('')
25 |
26 | const matchMarket = async (propertyAddress: string, enabledMarkets: AddressContractContainer[]) => {
27 | const flatten = await Promise.all(
28 | enabledMarkets.map(async m => ({
29 | market: m,
30 | properties: await m.contract.getAuthenticatedProperties()
31 | }))
32 | )
33 |
34 | const matchingMarket = flatten.find(m => {
35 | return m.properties.includes(propertyAddress)
36 | })?.market
37 |
38 | if (!matchingMarket) {
39 | return
40 | }
41 | const market = getMarketFromString(await matchingMarket.contract.name())
42 | return {
43 | market,
44 | address: matchingMarket.address
45 | }
46 | }
47 |
48 | const onError = (message: string) => {
49 | setError(message)
50 | setIsLoading(false)
51 | }
52 |
53 | const { data } = useSWR(
54 | SWRCachePath.usePropertyDetails(propertyAddress),
55 | async () => {
56 | if (!propertyAddress) {
57 | setError('No property address provided')
58 | return
59 | }
60 | setError(undefined)
61 | setIsLoading(true)
62 |
63 | const [propertyData, enabledMarkets, assetProperties] = await Promise.all([
64 | getPropertyData(nonConnectedEthersProvider, propertyAddress),
65 | getEnabledMarkets(nonConnectedEthersProvider),
66 | getAssetsByProperties(nonConnectedEthersProvider, propertyAddress)
67 | ])
68 |
69 | if (!propertyData) {
70 | onError(`No Property Data found for ${propertyAddress}`)
71 | return
72 | }
73 |
74 | if (!enabledMarkets) {
75 | onError('No enabled markets found. Please try again later')
76 | return
77 | }
78 |
79 | if (!assetProperties || assetProperties.length <= 0) {
80 | onError(`No properties found for ${propertyAddress}`)
81 | return
82 | }
83 |
84 | const [propertyName, propertySymbol] = await Promise.all([propertyData.name(), propertyData.symbol()])
85 |
86 | const matchingMarket = await matchMarket(propertyAddress, enabledMarkets)
87 | if (!matchingMarket) {
88 | onError(`No matching market found`)
89 | return
90 | }
91 |
92 | const assetProperty = matchMarketToAsset(matchingMarket.address, assetProperties)
93 | const id = assetProperty?.id
94 |
95 | setIsLoading(false)
96 |
97 | return {
98 | propertyName,
99 | propertySymbol,
100 | id,
101 | marketAddress: matchingMarket.address,
102 | market: matchingMarket.market
103 | }
104 | },
105 | {
106 | onError: err => {
107 | console.log(err)
108 | setError(err)
109 | setIsLoading(false)
110 | },
111 | revalidateOnFocus: false,
112 | focusThrottleInterval: 0
113 | }
114 | )
115 | return { propertyDetails: data, error, isLoading }
116 | }
117 |
--------------------------------------------------------------------------------
/src/hooks/useTerms.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react'
2 | import { TERMS_OF_SERVICE_URL } from '../const'
3 |
4 | /**
5 | * Fetches the terms and conditions from the TERMS_OF_SERVICE_URL const
6 | */
7 | export const useTerms = () => {
8 | const [terms, setTerms] = useState('')
9 |
10 | const fetchTerms = async () => {
11 | const response = await fetch(TERMS_OF_SERVICE_URL)
12 | const terms = await response.text()
13 | setTerms(terms)
14 | }
15 |
16 | useEffect(() => {
17 | fetchTerms()
18 | }, [])
19 |
20 | return { terms }
21 | }
22 |
--------------------------------------------------------------------------------
/src/img/ANPAO.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/img/CARD.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/img/FOOTER_IMG_ Powered by Dev Protocol.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dev-protocol/niwa/66eb9dedc30ce9700ff636fa09fcffeeae479649/src/img/FOOTER_IMG_ Powered by Dev Protocol.png
--------------------------------------------------------------------------------
/src/img/HEADING_TEXTURE.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dev-protocol/niwa/66eb9dedc30ce9700ff636fa09fcffeeae479649/src/img/HEADING_TEXTURE.png
--------------------------------------------------------------------------------
/src/img/PURSE.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/img/SEEDLING.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/img/g-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dev-protocol/niwa/66eb9dedc30ce9700ff636fa09fcffeeae479649/src/img/g-logo.png
--------------------------------------------------------------------------------
/src/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dev-protocol/niwa/66eb9dedc30ce9700ff636fa09fcffeeae479649/src/img/logo.png
--------------------------------------------------------------------------------
/src/img/og.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dev-protocol/niwa/66eb9dedc30ce9700ff636fa09fcffeeae479649/src/img/og.png
--------------------------------------------------------------------------------
/src/img/uzomia.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&family=Sen:wght@400;700&family=Syne:wght@400;500;600;700&display=swap');
2 | @import './css/background.css';
3 | @tailwind base;
4 | @tailwind components;
5 | @tailwind utilities;
6 |
--------------------------------------------------------------------------------
/src/main.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import App from './App'
4 | import './index.css'
5 |
6 | ReactDOM.render(
7 |
8 |
9 | ,
10 | document.getElementById('root')
11 | )
12 |
--------------------------------------------------------------------------------
/src/pages/apps/AppGridItem.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Card from '../../components/Card'
3 |
4 | interface AppGridItemProps {
5 | title: string
6 | url: string
7 | description: string
8 | logo: string
9 | }
10 |
11 | const AppGridItem: React.FC = ({ title, url, description, logo }) => {
12 | return (
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
28 |
29 |
{description}
30 |
31 |
32 | )
33 | }
34 |
35 | export default AppGridItem
36 |
--------------------------------------------------------------------------------
/src/pages/apps/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import BackButton from '../../components/BackButton'
3 | import DPLTitleBar from '../../components/DPLTitleBar'
4 | import AppGridItem from './AppGridItem'
5 | import { UndefinedOr } from '@devprotocol/util-ts'
6 | import Card from '../../components/Card'
7 |
8 | interface AppsPageProps {}
9 |
10 | export type AppItem = {
11 | name: string
12 | url: string
13 | description: string
14 | logoUrl: string
15 | }
16 |
17 | const AppsPage: React.FC = () => {
18 | const [apps, setApps] = useState([])
19 | const [error, setError] = useState>()
20 |
21 | useEffect(() => {
22 | fetchAppsList()
23 | }, [])
24 |
25 | const fetchAppsList = async () => {
26 | try {
27 | const res = await fetch('https://raw.githubusercontent.com/dev-protocol/niwa/main/dapps.json')
28 | const appsList: AppItem[] = await res.json()
29 | setApps(appsList)
30 | } catch (error) {
31 | setError(String(error))
32 | console.error('error fetching apps list: ', error)
33 | }
34 | }
35 |
36 | return (
37 |
38 |
39 |
40 |
41 | {apps.map(app => (
42 |
43 | ))}
44 |
45 | {error && (
46 |
47 | {error}
48 |
49 | )}
50 |
51 | )
52 | }
53 |
54 | export default AppsPage
55 |
--------------------------------------------------------------------------------
/src/pages/auth-callback/DiscordAuthCallback.tsx:
--------------------------------------------------------------------------------
1 | import { ChangeEvent, FunctionComponent, useEffect, useState, useContext } from 'react'
2 | import { useParams, useNavigate, useLocation } from 'react-router-dom'
3 | import useSWR from 'swr'
4 | import { Market } from '../../const'
5 | import HSButton from '../../components/HSButton'
6 | import { TokenizeContext } from '../../context/tokenizeContext'
7 | import { getMarketFromString, useQuery } from '../../utils/utils'
8 | import { UndefinedOr } from '@devprotocol/util-ts'
9 | import { TokenizeWindowState } from '../../types/TokenizeWindowState'
10 |
11 | interface AuthCallbackPageProps {}
12 |
13 | const REQUIRED_PERMISSIONS = 8 // Administrator
14 |
15 | const DiscordAuthCallbackPage: FunctionComponent = () => {
16 | const params = useParams()
17 | const [error, setError] = useState>()
18 | const { search, hash } = useLocation()
19 | const target = search ? search : hash
20 | const queryParams = useQuery(target)
21 | const navigate = useNavigate()
22 | const [isVerify, setIsVerify] = useState(false)
23 | const [market, setMarket] = useState>()
24 |
25 | const encodedStateParam: string = queryParams.state
26 | const decodedStateParam: TokenizeWindowState = JSON.parse(window.atob(decodeURIComponent(encodedStateParam)))
27 | const { isPopup } = decodedStateParam
28 |
29 | const clientId = import.meta.env.VITE_DISCORD_CLIENT_ID
30 | const clientSecret = import.meta.env.VITE_DISCORD_CLIENT_SECRET
31 | const redirectUri = encodeURI((import.meta.env.VITE_DISCORD_AUTH_REDIRECT_URI as string) || '')
32 | const swrOptions = {
33 | revalidateOnFocus: false,
34 | shouldRetryOnError: false,
35 | focusThrottleInterval: 0
36 | }
37 |
38 | const oauthCode = queryParams.code
39 | const { data: verifyData } = useSWR(
40 | oauthCode !== '' ? 'discord/verify' : null,
41 | async () => {
42 | const verifyUrl = `https://discord.com/api/oauth2/token`
43 | const body = new URLSearchParams({
44 | client_id: clientId as string,
45 | client_secret: clientSecret as string,
46 | grant_type: 'authorization_code',
47 | code: (oauthCode as string) || '',
48 | redirect_uri: redirectUri
49 | })
50 | return fetch(verifyUrl, {
51 | method: 'POST',
52 | headers: {
53 | 'Content-Type': 'application/x-www-form-urlencoded'
54 | },
55 | body
56 | }).then((res: any) => {
57 | return res.json().then((value: any) => {
58 | if (value.scope !== 'guilds') {
59 | throw new Error('invalid audience')
60 | }
61 | value.access_token && setIsVerify(true)
62 | return { accessToken: value.access_token }
63 | })
64 | })
65 | },
66 | swrOptions
67 | )
68 | const { data: guildData } = useSWR(
69 | verifyData && verifyData.accessToken ? 'discord/dataapi' : null,
70 | async () => {
71 | const discordGuildApiUrl = `https://discord.com/api/users/@me/guilds`
72 | return fetch(discordGuildApiUrl, {
73 | headers: {
74 | Authorization: `Bearer ${verifyData.accessToken}`
75 | }
76 | }).then((res: any) => {
77 | const data = res.json()
78 | return data.then((datas: any) => {
79 | const usableGuilds = datas.filter((d: any) => (REQUIRED_PERMISSIONS & d.permissions) === REQUIRED_PERMISSIONS)
80 | return usableGuilds
81 | })
82 | })
83 | },
84 | swrOptions
85 | )
86 | const { assetName, setAssetName, setPersonalAccessToken } = useContext(TokenizeContext)
87 |
88 | const displayMessage = (msg: string) => {
89 | setError(msg)
90 | console.log(msg)
91 | }
92 |
93 | useEffect(() => {
94 | const _market = getMarketFromString(params.market)
95 |
96 | setMarket(_market)
97 |
98 | if (_market !== Market.DISCORD) {
99 | return displayMessage('invalid market')
100 | }
101 |
102 | if (verifyData === undefined || guildData === undefined) {
103 | return
104 | }
105 |
106 | if (!isVerify) {
107 | return displayMessage('invalid oauth')
108 | }
109 |
110 | setPersonalAccessToken(verifyData?.accessToken)
111 | }, [params, navigate, setMarket, market, guildData, isVerify, setPersonalAccessToken, verifyData, setAssetName])
112 |
113 | const onChange = (v: ChangeEvent) => {
114 | setAssetName(v.target.value)
115 | }
116 |
117 | const onSubmit = () => {
118 | if (!assetName) {
119 | return setError('select guild')
120 | }
121 |
122 | const redirectUri = new URL('/tokenize/discord', location.origin)
123 | if (isPopup) {
124 | redirectUri.searchParams.set('popup', 'true')
125 | }
126 |
127 | return navigate(redirectUri)
128 | }
129 |
130 | return (
131 |
132 | {error && (
133 |
134 | {error && Error tokenizing asset: *{error} }
135 |
136 | )}
137 | {verifyData && guildData ? (
138 |
139 |
140 | {guildData.length === 0 &&
You do not have your own discord server (guild)
}
141 |
169 |
170 |
171 | ) : (
172 |
waiting...
173 | )}
174 |
175 | )
176 | }
177 |
178 | export default DiscordAuthCallbackPage
179 |
--------------------------------------------------------------------------------
/src/pages/auth-callback/YouTubeAuthCallback.tsx:
--------------------------------------------------------------------------------
1 | import { FunctionComponent, useEffect, useState, useContext } from 'react'
2 | import { useParams, useNavigate, useLocation } from 'react-router-dom'
3 | import useSWR from 'swr'
4 | import { Market } from '../../const'
5 | import { TokenizeContext } from '../../context/tokenizeContext'
6 | import { getMarketFromString, useQuery } from '../../utils/utils'
7 | import { UndefinedOr } from '@devprotocol/util-ts'
8 | import { TokenizeWindowState } from '../../types/TokenizeWindowState'
9 |
10 | interface AuthCallbackPageProps {}
11 |
12 | const YouTubeAuthCallbackPage: FunctionComponent = () => {
13 | const params = useParams()
14 | const [error, setError] = useState>()
15 | const { search, hash } = useLocation()
16 | const target = search ? search : hash
17 | const queryParams = useQuery(target)
18 | const navigate = useNavigate()
19 | const [isVerify, setIsVerify] = useState(false)
20 | const [market, setMarket] = useState>()
21 |
22 | const encodedStateParam: string = queryParams.state
23 | const decodedStateParam: TokenizeWindowState = JSON.parse(window.atob(decodeURIComponent(encodedStateParam)))
24 |
25 | const { isPopup } = decodedStateParam
26 |
27 | const clientId = import.meta.env.VITE_YOUTUBE_CLIENT_ID
28 | const swrOptions = {
29 | revalidateOnFocus: false,
30 | shouldRetryOnError: false,
31 | focusThrottleInterval: 0
32 | }
33 |
34 | const accessToken = queryParams.access_token || ''
35 | const { data: verifyData } = useSWR(
36 | accessToken !== '' ? 'google/verify' : null,
37 | async () => {
38 | const verifyUrl = `https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=${accessToken}`
39 | return fetch(verifyUrl).then((res: any) => {
40 | return res.json().then((value: any) => {
41 | const email = value.email
42 | if (value.audience !== clientId) {
43 | throw new Error('invalid audience')
44 | }
45 | setIsVerify(true)
46 | return { email }
47 | })
48 | })
49 | },
50 | swrOptions
51 | )
52 | const { data: youtubeData } = useSWR(
53 | accessToken !== '' ? 'youtube/dataapi' : null,
54 | async () => {
55 | const youtubeDataApiUrl = `https://www.googleapis.com/youtube/v3/channels?part=id,snippet,brandingSettings,statistics&mine=true&access_token=${accessToken}`
56 | return fetch(youtubeDataApiUrl).then((res: any) => {
57 | const data = res.json()
58 | return data.then((value: any) => {
59 | return value.items.map((item: any) => {
60 | const channelId = item.id
61 | const videoCount = item.statistics.videoCount
62 | const viewCount = item.statistics.viewCount
63 | const keywords = item.brandingSettings.channel.keywords
64 | const title = item.snippet.title
65 | const description = item.snippet.description
66 | return { channelId, videoCount, viewCount, keywords, title, description }
67 | })
68 | })
69 | })
70 | },
71 | swrOptions
72 | )
73 | const { setAssetName, setPersonalAccessToken } = useContext(TokenizeContext)
74 |
75 | const displayMessage = (msg: string) => {
76 | setError(msg)
77 | console.log(msg)
78 | }
79 |
80 | useEffect(() => {
81 | const _market = getMarketFromString(params.market)
82 |
83 | setMarket(_market)
84 |
85 | if (_market !== Market.YOUTUBE) {
86 | return displayMessage('invalid market')
87 | }
88 |
89 | if (!accessToken || !verifyData || !youtubeData || !isVerify) {
90 | return displayMessage('invalid oauth')
91 | }
92 |
93 | setAssetName(youtubeData.pop().channelId)
94 | setPersonalAccessToken(accessToken)
95 |
96 | const redirectUri = new URL('/tokenize/youtube', location.origin)
97 | if (isPopup) {
98 | redirectUri.searchParams.set('popup', 'true')
99 | }
100 |
101 | return navigate(redirectUri)
102 | }, [
103 | params,
104 | navigate,
105 | setMarket,
106 | market,
107 | youtubeData,
108 | isVerify,
109 | setPersonalAccessToken,
110 | accessToken,
111 | verifyData,
112 | setAssetName,
113 | isPopup
114 | ])
115 |
116 | return (
117 |
118 | {error ? (
119 |
120 | {error && Error tokenizing asset: *{error} }
121 |
122 | ) : (
123 |
waiting...
124 | )}
125 |
126 | )
127 | }
128 |
129 | export default YouTubeAuthCallbackPage
130 |
--------------------------------------------------------------------------------
/src/pages/auth-callback/index.tsx:
--------------------------------------------------------------------------------
1 | import { FunctionComponent } from 'react'
2 | import { useParams } from 'react-router-dom'
3 | import { Market } from '../../const'
4 | import BackButton from '../../components/BackButton'
5 | import { getMarketFromString, marketToReadable } from '../../utils/utils'
6 | import YouTubeAuthCallbackPage from './YouTubeAuthCallback'
7 | import DiscordAuthCallbackPage from './DiscordAuthCallback'
8 |
9 | interface AuthCallbackPageProps {}
10 |
11 | const AuthCallbackPage: FunctionComponent = () => {
12 | const params = useParams()
13 | const market = getMarketFromString(params.market)
14 |
15 | return (
16 |
17 |
27 | {market === Market.YOUTUBE ? (
28 |
29 | ) : market === Market.DISCORD ? (
30 |
31 | ) : (
32 |
35 | )}
36 |
37 | )
38 | }
39 |
40 | export default AuthCallbackPage
41 |
--------------------------------------------------------------------------------
/src/pages/errors/404.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import BackButton from '../../components/BackButton'
3 | import DPLTitleBar from '../../components/DPLTitleBar'
4 |
5 | interface PageNotFoundProps {
6 | // Props
7 | }
8 |
9 | const PageNotFound: React.FC = () => {
10 | return (
11 |
12 |
13 |
14 |
Sorry, we could not find the page you were looking for.
15 |
16 | )
17 | }
18 |
19 | export default PageNotFound
20 |
--------------------------------------------------------------------------------
/src/pages/growth/index.tsx:
--------------------------------------------------------------------------------
1 | import { FunctionComponent } from 'react'
2 |
3 | interface GrowthPageProps {}
4 |
5 | const GrowthPage: FunctionComponent = () => {
6 | return (
7 |
8 | Coming Soon :)
9 |
10 | )
11 | }
12 |
13 | export default GrowthPage
14 |
--------------------------------------------------------------------------------
/src/pages/home/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import HomeNavItem from '../../components/HomeNavItem'
3 |
4 | interface HomeProps {}
5 |
6 | const Home: React.FC = () => {
7 | return (
8 |
24 | )
25 | }
26 |
27 | export default Home
28 |
--------------------------------------------------------------------------------
/src/pages/how-it-works/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | interface HomeProps {}
4 |
5 | const Rect: React.FC = ({ children }) => {
6 | return
7 | }
8 |
9 | const Home: React.FC = () => {
10 | return (
11 |
12 | How it works
13 |
14 | VIDEO
24 |
25 |
26 |
34 |
35 |
36 | )
37 | }
38 |
39 | export default Home
40 |
--------------------------------------------------------------------------------
/src/pages/markdown-page/MarkdownPage.module.scss:
--------------------------------------------------------------------------------
1 | .markdown {
2 | h1 {
3 | @apply text-3xl font-bold my-md;
4 | }
5 | h2 {
6 | @apply text-xl font-bold my-sm;
7 | }
8 | p {
9 | @apply mb-sm;
10 | }
11 | a {
12 | @apply text-link;
13 | }
14 | ol {
15 | @apply list-decimal;
16 | }
17 | ul {
18 | @apply list-disc;
19 | }
20 | ol,
21 | ul {
22 | @apply my-sm pl-md;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/pages/markdown-page/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styles from './MarkdownPage.module.scss' // Import regular stylesheet
3 | import Card from '../../components/Card'
4 |
5 | interface MarkdownPageProps {}
6 |
7 | const MarkdownPage: React.FC = ({ children }) => {
8 | return (
9 |
10 | {children}
11 |
12 | )
13 | }
14 |
15 | export default MarkdownPage
16 |
--------------------------------------------------------------------------------
/src/pages/network-select/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import HomeNavItem from '../../components/HomeNavItem'
3 | import HSButton from '../../components/HSButton'
4 | import Eyecatching from '../../img/og.png'
5 |
6 | interface NetworkSelectPageProps {}
7 |
8 | const NetworkSelectPage: React.FC = () => {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 | How it works
16 |
17 |
18 |
19 |
20 | Choose network
21 |
27 |
33 |
39 |
40 |
41 | )
42 | }
43 |
44 | export default NetworkSelectPage
45 |
--------------------------------------------------------------------------------
/src/pages/properties/PropertyTabsContainer/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Outlet, useParams } from 'react-router-dom'
3 | import { usePropertyDetails } from '../../../hooks/usePropertyDetails'
4 | import PropertySummaryHead from '../ProperySummary'
5 |
6 | interface PropertyTabsContainerProps {}
7 |
8 | const PropertyTabsContainer: React.FC = () => {
9 | const { hash } = useParams()
10 | const { propertyDetails, isLoading, error } = usePropertyDetails(hash)
11 | return (
12 | <>
13 | {' '}
14 | {hash && propertyDetails && !isLoading && (
15 | <>
16 |
17 |
18 | >
19 | )}
20 | >
21 | )
22 | }
23 |
24 | export default PropertyTabsContainer
25 |
--------------------------------------------------------------------------------
/src/pages/properties/ProperySummary/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { FaDiscord, FaExternalLinkAlt, FaGithub, FaQuestionCircle, FaYoutube } from 'react-icons/fa'
3 | import { Market } from '../../../const'
4 | import { PropertyDetails } from '../../../hooks/usePropertyDetails'
5 | import { crunchAddress, deployedNetworkToReadable, getExplorerUrl } from '../../../utils/utils'
6 | import DPLTitleBar from '../../../components/DPLTitleBar'
7 | import { NavTabItem, NavTabs } from '../../../components/NavTabs'
8 | import CopyButton from '../../../components/CopyButton'
9 | import HSButton from '../../../components/HSButton'
10 | import { useAddToWalletList } from '../../../hooks/useAddToWalletList'
11 | import { useProvider } from '../../../context/walletContext'
12 |
13 | interface PropertySummaryHeadProps {
14 | propertyDetails: PropertyDetails
15 | hash: string
16 | }
17 |
18 | const PropertySummaryHead: React.FC = ({ propertyDetails, hash }) => {
19 | const { ethersProvider } = useProvider()
20 | const { addToWalletList } = useAddToWalletList()
21 |
22 | const addToUserWallet = () => {
23 | addToWalletList(propertyDetails.propertySymbol, hash, ethersProvider)
24 | }
25 |
26 | return (
27 | <>
28 |
29 |
30 |
31 | + Add To Wallet List
32 |
33 |
34 |
35 | {hash}
36 |
37 |
38 |
73 |
84 |
85 |
86 |
87 |
88 |
89 | >
90 | )
91 | }
92 |
93 | export default PropertySummaryHead
94 |
--------------------------------------------------------------------------------
/src/pages/properties/index.tsx:
--------------------------------------------------------------------------------
1 | import { UndefinedOr } from '@devprotocol/util-ts'
2 | import React from 'react'
3 | import { Outlet, useOutletContext, useParams } from 'react-router-dom'
4 | import Card from '../../components/Card'
5 | import { SectionLoading } from '../../components/Spinner'
6 | import { PropertyDetails, usePropertyDetails } from '../../hooks/usePropertyDetails'
7 |
8 | interface PropertyOutletProps {}
9 |
10 | type ContextType = {
11 | propertyDetails: PropertyDetails | null
12 | isPropertyDetailsLoading: boolean
13 | propertyDetailsError: UndefinedOr
14 | }
15 |
16 | export function usePropertyOutlet() {
17 | return useOutletContext()
18 | }
19 |
20 | const PropertyOutlet: React.FC = () => {
21 | const { hash } = useParams()
22 | const { propertyDetails, isLoading, error } = usePropertyDetails(hash)
23 |
24 | return (
25 |
26 | {hash && propertyDetails && !isLoading && (
27 | <>
28 |
29 | >
30 | )}
31 | {isLoading &&
}
32 | {error && (
33 |
34 | {error}
35 |
36 | )}
37 |
38 | )
39 | }
40 |
41 | export default PropertyOutlet
42 |
--------------------------------------------------------------------------------
/src/pages/properties/property-holders/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import { Link, useParams } from 'react-router-dom'
3 | import Card from '../../../components/Card'
4 | import Detail from '../../../components/Detail'
5 | import { SectionLoading } from '../../../components/Spinner'
6 | import { usePropertyBalances } from '../../../hooks/usePropertyBalances'
7 | import { crunchAddress, toDisplayAmount } from '../../../utils/utils'
8 | import { usePropertyOutlet } from '..'
9 |
10 | interface PropertyHoldersPageProps {}
11 |
12 | const PropertyHoldersPage: React.FC = () => {
13 | const { hash } = useParams()
14 | const { propertyBalances, error: balancesError } = usePropertyBalances(hash)
15 | const { propertyDetails, isPropertyDetailsLoading, propertyDetailsError } = usePropertyOutlet()
16 | const [sortedBalances, setSortedBalances] = useState<{ account: string; balance: string }[]>()
17 |
18 | useEffect(() => {
19 | if (!propertyBalances) {
20 | return
21 | }
22 | const sorted = propertyBalances.map(pb => pb).sort((pb1, pb2) => +pb2.balance - +pb1.balance)
23 | setSortedBalances(sorted)
24 | }, [propertyBalances])
25 |
26 | return (
27 | <>
28 | {propertyDetails && !isPropertyDetailsLoading && (
29 |
30 | {sortedBalances && sortedBalances.length > 0 && (
31 |
32 | {sortedBalances.map(pb => (
33 |
34 |
35 | {crunchAddress(pb.account)}} />
36 |
40 | {toDisplayAmount(pb.balance)} {propertyDetails.propertySymbol}
41 |
42 | }
43 | />
44 |
45 |
46 | ))}
47 |
48 | )}
49 |
50 | )}
51 | {isPropertyDetailsLoading && }
52 | {balancesError && {balancesError} }
53 | {propertyDetailsError && {propertyDetailsError} }
54 | >
55 | )
56 | }
57 |
58 | export default PropertyHoldersPage
59 |
--------------------------------------------------------------------------------
/src/pages/properties/property-overview/StakeOption.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import { Link } from 'react-router-dom'
3 | import Card from '../../../components/Card'
4 | import { isNumberInput } from '../../../utils/utils'
5 |
6 | interface StakeOptionProps {
7 | optionName: string
8 | fixedAmount?: number
9 | isCustom: boolean
10 | propertyAddress: string
11 | }
12 |
13 | const StakeOption: React.FC = ({ optionName, fixedAmount, isCustom, propertyAddress }) => {
14 | const [stakeAmount, setStakeAmount] = useState(fixedAmount)
15 | const [formValid, setFormValid] = useState(!isCustom)
16 |
17 | const onChange = (val: string) => {
18 | // empty string
19 | if (val.length <= 0) {
20 | setStakeAmount(undefined)
21 | setFormValid(false)
22 | return
23 | }
24 |
25 | // not valid number input
26 | if (!isNumberInput(val)) {
27 | return
28 | }
29 |
30 | setFormValid(+val > 0)
31 | setStakeAmount(+val)
32 | }
33 |
34 | return (
35 |
36 |
37 |
{optionName}
38 |
39 | {!isCustom && (
40 |
41 |
42 | {stakeAmount}
43 | DEV
44 |
45 |
46 | )}
47 |
48 | {isCustom && (
49 |
onChange(e.target.value)}
53 | placeholder="1000"
54 | />
55 | )}
56 |
57 |
63 | Stake
64 |
65 |
66 | Stake a{!isCustom && {optionName} }
67 | {isCustom && custom amount }
68 | and get a thank you
69 |
70 |
71 |
72 | )
73 | }
74 |
75 | export default StakeOption
76 |
--------------------------------------------------------------------------------
/src/pages/properties/property-overview/fetch-token-data.hook.tsx:
--------------------------------------------------------------------------------
1 | import { whenDefinedAll } from '@devprotocol/util-ts'
2 | import useSWR from 'swr'
3 | import { SWRCachePath } from '../../../const/cache-path'
4 | import { useProvider } from '../../../context/walletContext'
5 | import { getPropertyData } from '../../../utils/utils'
6 |
7 | export const usePropertyData = (propertyAddress?: string) => {
8 | const { nonConnectedEthersProvider } = useProvider()
9 | const { data, error } = useSWR(
10 | SWRCachePath.getPropertyData(propertyAddress),
11 | () =>
12 | whenDefinedAll([nonConnectedEthersProvider, propertyAddress], ([client, property]) =>
13 | getPropertyData(client, property)
14 | ),
15 | {
16 | onError: err => {
17 | console.log(err)
18 | },
19 | revalidateOnFocus: false,
20 | focusThrottleInterval: 0
21 | }
22 | )
23 | return { propertyData: data, error }
24 | }
25 |
--------------------------------------------------------------------------------
/src/pages/properties/property-overview/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useParams } from 'react-router-dom'
3 | import StakeOption from './StakeOption'
4 | import HowItWorks from '../../../components/HowItWorks'
5 | import { DPLHr } from '../../../components/DPLHr'
6 | import { TweetLarge } from '../../../components/Tweet'
7 | import { usePropertyOutlet } from '..'
8 |
9 | interface TokenProps {}
10 |
11 | const PropertyOverviewPage: React.FC = () => {
12 | const { hash } = useParams()
13 | const { propertyDetails, propertyDetailsError } = usePropertyOutlet()
14 | const options = [
15 | {
16 | amount: 10,
17 | name: 'candy',
18 | isCustom: false
19 | },
20 | {
21 | amount: 100,
22 | name: 'coffee',
23 | isCustom: false
24 | },
25 | {
26 | amount: 500,
27 | name: 'bento',
28 | isCustom: false
29 | },
30 | {
31 | name: 'custom',
32 | isCustom: true
33 | }
34 | ]
35 |
36 | return (
37 |
38 | {propertyDetails && hash && (
39 | <>
40 |
41 | {hash &&
42 | options.map(option => (
43 |
50 | ))}
51 |
52 |
53 |
59 | Share and grow this project together
60 |
61 |
62 |
63 |
64 |
65 | >
66 | )}
67 |
68 | {propertyDetailsError &&
{propertyDetailsError}
}
69 |
70 | )
71 | }
72 |
73 | export default PropertyOverviewPage
74 |
--------------------------------------------------------------------------------
/src/pages/properties/property-stakers/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react'
2 | import { FaFunnelDollar } from 'react-icons/fa'
3 | import { Link, useParams } from 'react-router-dom'
4 | import Card from '../../../components/Card'
5 | import Detail from '../../../components/Detail'
6 | import { crunchAddress, toDisplayAmount } from '../../../utils/utils'
7 | import { useSTokensPositionsOfProperty } from './useSTokensPositionsOfProperty'
8 |
9 | interface PropertyStakersPageProps {}
10 |
11 | const PropertyStakersPage: React.FC = () => {
12 | const { hash } = useParams()
13 | const { sTokensPositionsOfProperty, error } = useSTokensPositionsOfProperty(hash)
14 |
15 | useEffect(() => {
16 | console.log('sTokensPositionsOfProperty: ', sTokensPositionsOfProperty)
17 | }, [sTokensPositionsOfProperty])
18 |
19 | return (
20 | <>
21 | {error && {error}
}
22 | <>
23 | {sTokensPositionsOfProperty && (
24 | <>
25 | {sTokensPositionsOfProperty.length > 0 && (
26 |
27 | {sTokensPositionsOfProperty.map((pos, i) => (
28 |
29 |
30 |
31 | {crunchAddress(pos.owner)}} />
32 |
33 |
34 | {toDisplayAmount(pos.amount)}} />
35 | {toDisplayAmount(pos.pendingReward)}} />
36 | {toDisplayAmount(pos.cumulativeReward)}}
39 | />
40 | {toDisplayAmount(pos.price)}} />
41 |
42 |
43 | ))}
44 |
45 | )}
46 |
47 | {sTokensPositionsOfProperty.length <= 0 && (
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
No stakers found for this property.
56 |
57 | Stake Now
58 |
59 |
60 |
61 |
62 | )}
63 | >
64 | )}
65 | >
66 | >
67 | )
68 | }
69 |
70 | export default PropertyStakersPage
71 |
--------------------------------------------------------------------------------
/src/pages/properties/property-stakers/useSTokensPositionsOfProperty.ts:
--------------------------------------------------------------------------------
1 | import { createSTokensContract } from '@devprotocol/dev-kit/l2'
2 | import { whenDefined } from '@devprotocol/util-ts'
3 | import { providers } from 'ethers'
4 | import useSWR from 'swr'
5 | import { SWRCachePath } from '../../../const/cache-path'
6 | import { useProvider } from '../../../context/walletContext'
7 | import { mapProviderToDevContracts } from '../../../utils/utils'
8 |
9 | export const getSTokensPositionsOfProperty = async (
10 | provider: providers.JsonRpcProvider,
11 | sTokensAddress: string,
12 | propertyAddress?: string
13 | ) => {
14 | if (!propertyAddress) {
15 | return
16 | }
17 | const contract = createSTokensContract(provider)(sTokensAddress)
18 | const positionsOfProperty = await contract.positionsOfProperty(propertyAddress)
19 |
20 | const positionsCalls = positionsOfProperty.map(id => contract.positions(id))
21 | const ownerCalls = positionsOfProperty.map(id => contract.ownerOf(id))
22 | const positions = await Promise.all([...positionsCalls])
23 | const owners = await Promise.all([...ownerCalls])
24 | const res = positions.map((pos, i) => ({
25 | owner: owners[i],
26 | ...pos
27 | }))
28 | return res
29 | }
30 |
31 | export const useSTokensPositionsOfProperty = (propertyAddress?: string) => {
32 | const { nonConnectedEthersProvider } = useProvider()
33 | const { data, error } = useSWR(
34 | SWRCachePath.getSTokensPositionsOfProperty(propertyAddress),
35 | () =>
36 | whenDefined(nonConnectedEthersProvider, async client => {
37 | const networkContracts = await mapProviderToDevContracts(nonConnectedEthersProvider)
38 | if (!networkContracts) {
39 | return
40 | }
41 |
42 | return getSTokensPositionsOfProperty(client, networkContracts.sTokens, propertyAddress)
43 | }),
44 | {
45 | onError: err => {
46 | console.log(err)
47 | },
48 | revalidateOnFocus: false,
49 | focusThrottleInterval: 0
50 | }
51 | )
52 | return { sTokensPositionsOfProperty: data, error }
53 | }
54 |
--------------------------------------------------------------------------------
/src/pages/properties/stake/StakeStep.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { FaCheck } from 'react-icons/fa'
3 | import HSButton from '../../../components/HSButton'
4 |
5 | interface StakeStepProps {
6 | isComplete: boolean
7 | isDisabled: boolean
8 | isVisible: boolean
9 | name: string
10 | label: string
11 | btnText: string
12 | onClick: () => void
13 | }
14 |
15 | const StakeStep: React.FC = ({ isComplete, isDisabled, label, onClick, name, btnText, isVisible }) => {
16 | return (
17 |
18 |
19 | {name}
20 |
21 |
22 |
23 | {label}
24 |
25 |
26 |
27 | )
28 | }
29 |
30 | export default StakeStep
31 |
--------------------------------------------------------------------------------
/src/pages/properties/stake/index.tsx:
--------------------------------------------------------------------------------
1 | import { UndefinedOr } from '@devprotocol/util-ts'
2 | import { BigNumber, constants, utils } from 'ethers'
3 | import React, { useEffect, useState } from 'react'
4 | import { FaExclamationTriangle } from 'react-icons/fa'
5 | import { useNavigate, useParams, useSearchParams } from 'react-router-dom'
6 | import BackButton from '../../../components/BackButton'
7 | import Card from '../../../components/Card'
8 | import { DPLHr } from '../../../components/DPLHr'
9 | import DPLTitleBar from '../../../components/DPLTitleBar'
10 | import HowItWorks from '../../../components/HowItWorks'
11 | import { SectionLoading } from '../../../components/Spinner'
12 | import { ERROR_MSG } from '../../../const'
13 | import { useProvider } from '../../../context/walletContext'
14 | import { useDevAllowance } from '../../../hooks/useAllowance'
15 | import { useDevApprove } from '../../../hooks/useApprove'
16 | import { usePropertyDetails } from '../../../hooks/usePropertyDetails'
17 | import { isNumberInput, mapProviderToDevContracts } from '../../../utils/utils'
18 | import StakeStep from './StakeStep'
19 | import { useLockup } from './useLockup'
20 |
21 | interface StakePageProps {}
22 |
23 | const StakePage: React.FC = () => {
24 | const [searchParams] = useSearchParams()
25 | const [amount, setAmount] = useState(0)
26 | const [error, setError] = useState>()
27 | const { ethersProvider, isValidConnectedNetwork } = useProvider()
28 | const { lockup, isLoading: lockupLoading } = useLockup()
29 | const [isStakingComplete, setIsStakingComplete] = useState(false)
30 | const [lockupAddress, setLockupAddress] = useState>()
31 | const { fetchAllowance, isLoading: allowanceIsLoading, error: allowanceError } = useDevAllowance()
32 | const { approve, isLoading: approveIsLoading, error: approveError } = useDevApprove()
33 | const [allowance, setAllowance] = useState>()
34 | const navigate = useNavigate()
35 |
36 | const { hash } = useParams()
37 | const { propertyDetails, isLoading, error: propertyDetailsError } = usePropertyDetails(hash)
38 |
39 | useEffect(() => {
40 | if (!ethersProvider) {
41 | return
42 | }
43 |
44 | ;(async () => {
45 | const networkContracts = await mapProviderToDevContracts(ethersProvider)
46 | if (!networkContracts) {
47 | setError(ERROR_MSG.invalid_network)
48 | return
49 | }
50 | setLockupAddress(networkContracts.lockup)
51 | const _allowance = await fetchAllowance(networkContracts.lockup)
52 | setAllowance(_allowance)
53 | })()
54 | }, [ethersProvider, fetchAllowance])
55 |
56 | useEffect(() => {
57 | setError(propertyDetailsError ?? allowanceError ?? approveError)
58 | }, [propertyDetailsError, allowanceError, approveError])
59 |
60 | useEffect(() => {
61 | const _amount = searchParams.get('amount')
62 | if (_amount && isNumberInput(_amount)) {
63 | setAmount(+_amount)
64 | }
65 | }, [searchParams])
66 |
67 | const lockupHandler = async () => {
68 | if (!hash) {
69 | setError(ERROR_MSG.no_property_address)
70 | return
71 | }
72 | if (amount <= 0) {
73 | setError('Insufficient amount')
74 | return
75 | }
76 | if (!ethersProvider) {
77 | setError(ERROR_MSG.no_provider)
78 | return
79 | }
80 |
81 | const success = await lockup(hash, utils.parseUnits(`${amount}`))
82 | if (!success) {
83 | setError('There was an error staking')
84 | return
85 | }
86 |
87 | setIsStakingComplete(true)
88 | }
89 |
90 | const navigateToUserPositions = async () => {
91 | if (!ethersProvider) {
92 | return
93 | }
94 |
95 | navigate(`/${await ethersProvider.getSigner().getAddress()}/positions`)
96 | }
97 |
98 | const approveHandler = async () => {
99 | if (!lockupAddress) {
100 | setError('Error finding DEV lockup address')
101 | return
102 | }
103 |
104 | const success = await approve(lockupAddress)
105 | console.log('success is: ', success)
106 | if (!success) {
107 | setError('Error approving Dev Lockup Contract')
108 | return
109 | }
110 | setAllowance(constants.MaxUint256)
111 | }
112 |
113 | return (
114 | <>
115 | {propertyDetails && (
116 |
117 |
118 |
119 |
120 | {(!ethersProvider || !isValidConnectedNetwork) && (
121 |
122 |
123 |
124 |
125 | Please connect wallet to stake.
126 |
127 |
128 |
129 | )}
130 | {ethersProvider && isValidConnectedNetwork && (
131 |
132 |
141 |
152 |
161 |
162 | )}
163 |
164 |
165 |
166 |
167 |
168 | )}
169 | {isLoading && }
170 | {error && {error} }
171 | >
172 | )
173 | }
174 |
175 | export default StakePage
176 |
--------------------------------------------------------------------------------
/src/pages/properties/stake/useLockup.ts:
--------------------------------------------------------------------------------
1 | import { UndefinedOr } from '@devprotocol/util-ts'
2 | import { BigNumber, providers } from 'ethers'
3 | import { useCallback, useState } from 'react'
4 | import { ERROR_MSG } from '../../../const'
5 | import { useProvider } from '../../../context/walletContext'
6 | import { positionsCreate } from '@devprotocol/dev-kit/agent'
7 |
8 | export const lockup = async ({
9 | provider,
10 | propertyAddress,
11 | amount,
12 | userAddress
13 | }: {
14 | provider: providers.BaseProvider
15 | propertyAddress: string
16 | amount: BigNumber
17 | userAddress: string
18 | }) => {
19 | return await positionsCreate({
20 | provider: provider,
21 | destination: propertyAddress,
22 | amount: amount.toString(),
23 | from: userAddress
24 | })
25 | }
26 |
27 | export const useLockup = () => {
28 | const { ethersProvider } = useProvider()
29 | const [isLoading, setIsLoading] = useState(false)
30 | const [error, setError] = useState>()
31 | const callback = useCallback(
32 | async (propertyAddress: string, amount: BigNumber) => {
33 | setIsLoading(true)
34 | setError(undefined)
35 | if (!ethersProvider) {
36 | setIsLoading(false)
37 | setError(ERROR_MSG.no_provider)
38 | return
39 | }
40 |
41 | const userAddress = await ethersProvider.getSigner().getAddress()
42 |
43 | try {
44 | setIsLoading(false)
45 | const tx = await lockup({
46 | provider: ethersProvider,
47 | propertyAddress,
48 | amount,
49 | userAddress
50 | })
51 | console.log('tx is: ', tx)
52 | if (!tx) {
53 | setError(`Tx failed`)
54 | setIsLoading(false)
55 | return
56 | }
57 | const approve = await tx.approveIfNeeded()
58 | const stake = await approve.waitOrSkip()
59 | const res = await stake.wait()
60 | return res
61 | } catch (error) {
62 | console.error(error)
63 | setError(`Error staking on ${propertyAddress}`)
64 | setIsLoading(false)
65 | return
66 | }
67 | },
68 | [ethersProvider]
69 | )
70 | return { lockup: callback, error, isLoading }
71 | }
72 |
--------------------------------------------------------------------------------
/src/pages/tokenize-form/DiscordForm.tsx:
--------------------------------------------------------------------------------
1 | import { FunctionComponent, useContext, useState } from 'react'
2 | import { useNavigate } from 'react-router-dom'
3 | import FormField from '../../components/Form'
4 | import { TokenizeContext } from '../../context/tokenizeContext'
5 | import HSButton from '../../components/HSButton'
6 | import { FORM_HINT } from '../../const'
7 | import { TokenizeWindowState } from '../../types/TokenizeWindowState'
8 | import TermsModal from './TermsModal'
9 |
10 | interface DiscordFormProps {
11 | isPopup: boolean
12 | }
13 |
14 | const DiscordForm: FunctionComponent = ({ isPopup }) => {
15 | const navigate = useNavigate()
16 | const {
17 | network,
18 | address,
19 | tokenName,
20 | setTokenName,
21 | tokenSymbol,
22 | setTokenSymbol,
23 | setAgreedToTerms,
24 | isValid,
25 | personalAccessToken,
26 | assetName
27 | } = useContext(TokenizeContext)
28 | const [isTermsModalVisible, setIsTermsModalVisible] = useState(false)
29 |
30 | const onAuthDiscordAccount = () => {
31 | const clientId = import.meta.env.VITE_DISCORD_CLIENT_ID
32 | const redirectUri = encodeURI((import.meta.env.VITE_DISCORD_AUTH_REDIRECT_URI as string) || '')
33 |
34 | const scope = encodeURI('guilds')
35 | const tokenizePageState: TokenizeWindowState = {
36 | isPopup
37 | }
38 | const stateParam = encodeURIComponent(window.btoa(JSON.stringify(tokenizePageState)))
39 | const url = `https://discord.com/api/oauth2/authorize?client_id=${clientId}&redirect_uri=${redirectUri}&scope=${scope}&response_type=code&prompt=consent&state=${stateParam}`
40 |
41 | window.location.assign(url)
42 | }
43 |
44 | const onPreview = () => {
45 | if (!isValid) {
46 | return
47 | }
48 |
49 | setAgreedToTerms(true)
50 |
51 | navigate(isPopup ? '/tokenize/discord/preview?popup=true' : '/tokenize/discord/preview')
52 | }
53 |
54 | return (
55 |
56 | {assetName && personalAccessToken ? (
57 | <>
58 |
59 |
Server ID: {assetName}
60 |
61 |
62 |
70 | Minting only available on Arbitrum and Polyon.
71 |
72 |
80 | setTokenName(val)}
86 | />
87 | {
93 | if (val.length <= 4) {
94 | setTokenSymbol(val.toUpperCase())
95 | }
96 | }}
97 | >
98 | {FORM_HINT.symbol_length}
99 |
100 |
101 |
102 | setIsTermsModalVisible(true)}>
103 | Preview
104 |
105 |
106 |
onPreview()} />
107 | >
108 | ) : (
109 |
110 |
111 | Authorize Discord Account
112 |
113 |
114 | )}
115 |
116 | )
117 | }
118 |
119 | export default DiscordForm
120 |
--------------------------------------------------------------------------------
/src/pages/tokenize-form/GithubForm.tsx:
--------------------------------------------------------------------------------
1 | import { FunctionComponent, useContext, useState } from 'react'
2 | import { useNavigate } from 'react-router-dom'
3 | import FormField from '../../components/Form'
4 | import { TokenizeContext } from '../../context/tokenizeContext'
5 | import HSButton from '../../components/HSButton'
6 | import { isValidNetwork } from '../../utils/utils'
7 | import { FORM_HINT } from '../../const'
8 | import TermsModal from './TermsModal'
9 |
10 | interface GithubFormProps {
11 | isPopup: boolean
12 | }
13 |
14 | const GithubForm: FunctionComponent = ({ isPopup }) => {
15 | const navigate = useNavigate()
16 | const {
17 | network,
18 | address,
19 | isValid,
20 | assetName,
21 | setAssetName,
22 | tokenName,
23 | setTokenName,
24 | tokenSymbol,
25 | setTokenSymbol,
26 | personalAccessToken,
27 | setPersonalAccessToken,
28 | setAgreedToTerms
29 | } = useContext(TokenizeContext)
30 |
31 | const [isTermsModalVisible, setIsTermsModalVisible] = useState(false)
32 |
33 | const submit = () => {
34 | if (!isValid) {
35 | return
36 | }
37 |
38 | setAgreedToTerms(true)
39 |
40 | navigate(isPopup ? '/tokenize/github/preview?popup=true' : '/tokenize/github/preview')
41 | }
42 |
43 | return (
44 |
45 |
46 |
55 | Minting only available on Arbitrum and Polyon.
56 |
57 |
58 |
66 |
setAssetName(val)}
73 | />
74 | setTokenName(val)}
80 | />
81 | {
87 | if (val.length <= 4) {
88 | setTokenSymbol(val.toUpperCase())
89 | }
90 | }}
91 | >
92 | {FORM_HINT.symbol_length}
93 |
94 |
114 |
115 |
116 |
117 | setIsTermsModalVisible(true)}>
118 | Preview
119 |
120 |
121 |
submit()} />
122 |
123 | )
124 | }
125 |
126 | export default GithubForm
127 |
--------------------------------------------------------------------------------
/src/pages/tokenize-form/TermsModal.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import styles from '../markdown-page/MarkdownPage.module.scss' // Import regular stylesheet
3 | import HSButton from '../../components/HSButton'
4 | import ReactMarkdown from 'react-markdown'
5 | import rehypeRaw from 'rehype-raw'
6 | import { useTerms } from '../../hooks/useTerms'
7 |
8 | interface TermsModalProps {
9 | visible: boolean
10 | onConfirm: () => {}
11 | }
12 |
13 | const TermsModal: React.FC = ({ onConfirm, visible }) => {
14 | const [acceptEnabled, setAcceptEnabled] = useState(false)
15 | const { terms } = useTerms()
16 |
17 | const handleScroll = (e: React.UIEvent) => {
18 | const target = e.target as HTMLDivElement
19 | const bottom = target.scrollHeight - target.scrollTop - target.clientHeight < 10
20 | if (bottom) {
21 | setAcceptEnabled(true)
22 | }
23 | }
24 |
25 | return visible ? (
26 | (visible = false)}
30 | >
31 |
32 |
33 |
34 |
35 | {terms}
36 |
37 |
38 |
39 |
40 |
41 | I Accept
42 |
43 |
44 |
45 |
46 |
47 | ) : (
48 |
49 | )
50 | }
51 |
52 | export default TermsModal
53 |
--------------------------------------------------------------------------------
/src/pages/tokenize-form/YouTubeForm.tsx:
--------------------------------------------------------------------------------
1 | import { FunctionComponent, useContext, useState } from 'react'
2 | import { useNavigate } from 'react-router-dom'
3 | import FormField from '../../components/Form'
4 | import { TokenizeContext } from '../../context/tokenizeContext'
5 | import HSButton from '../../components/HSButton'
6 | import gLogo from '../../img/g-logo.png'
7 | import { FORM_HINT } from '../../const'
8 | import { TokenizeWindowState } from '../../types/TokenizeWindowState'
9 | import TermsModal from './TermsModal'
10 |
11 | interface YouTubeFormProps {
12 | isPopup: boolean
13 | }
14 |
15 | const YouTubeForm: FunctionComponent = ({ isPopup }) => {
16 | const navigate = useNavigate()
17 | const {
18 | network,
19 | address,
20 | tokenName,
21 | setTokenName,
22 | tokenSymbol,
23 | setTokenSymbol,
24 | setAgreedToTerms,
25 | isValid,
26 | personalAccessToken,
27 | assetName
28 | } = useContext(TokenizeContext)
29 | const [isTermsModalVisible, setIsTermsModalVisible] = useState(false)
30 |
31 | const onAuthYoutubeAccount = () => {
32 | const clientId = import.meta.env.VITE_YOUTUBE_CLIENT_ID
33 | const redirectUri = encodeURI((import.meta.env.VITE_YOUTUBE_AUTH_REDIRECT_URI as string) || '')
34 | const scope = encodeURI(
35 | 'https://www.googleapis.com/auth/youtube.readonly https://www.googleapis.com/auth/userinfo.email'
36 | )
37 | const tokenizePageState: TokenizeWindowState = {
38 | isPopup
39 | }
40 | const stateParam = encodeURIComponent(window.btoa(JSON.stringify(tokenizePageState)))
41 | const url = `https://accounts.google.com/o/oauth2/auth?client_id=${clientId}&redirect_uri=${redirectUri}&scope=${scope}&response_type=token&state=${stateParam}`
42 |
43 | window.location.assign(url)
44 | }
45 |
46 | const onPreview = () => {
47 | if (!isValid) {
48 | return
49 | }
50 |
51 | setAgreedToTerms(true)
52 |
53 | navigate(isPopup ? '/tokenize/youtube/preview?popup=true' : '/tokenize/youtube/preview')
54 | }
55 |
56 | return (
57 |
58 | {assetName && personalAccessToken ? (
59 | <>
60 |
61 |
Channel ID: {assetName}
62 |
63 |
64 |
72 | Minting only available on Arbitrum and Polyon.
73 |
74 |
82 | setTokenName(val)}
88 | />
89 | {
95 | if (val.length <= 4) {
96 | setTokenSymbol(val.toUpperCase())
97 | }
98 | }}
99 | >
100 | {FORM_HINT.symbol_length}
101 |
102 |
103 |
104 |
105 | setIsTermsModalVisible(true)}>
106 | Preview
107 |
108 |
109 |
onPreview()} />
110 | >
111 | ) : (
112 |
113 |
114 |
Authorize your YouTube Account
115 |
116 | To authorize your YouTube account, we will use your Google account (YouTube Data API).
117 |
118 |
124 |
125 |
129 | Sign in with Google
130 |
131 |
132 |
Why is Google OAuth necessary?
133 |
134 | To verify you are the owner of the YouTube channel.
135 | To eliminate spoofing.
136 | We use only the readonly API to get the channel id info.
137 |
138 |
139 |
140 | )}
141 |
142 | )
143 | }
144 |
145 | export default YouTubeForm
146 |
--------------------------------------------------------------------------------
/src/pages/tokenize-form/index.tsx:
--------------------------------------------------------------------------------
1 | import { FunctionComponent, useEffect, useState } from 'react'
2 | import { useParams, useNavigate, useLocation } from 'react-router-dom'
3 | import { Market, TOKENIZE_STEP_LABELS } from '../../const'
4 | import { getMarketFromString, marketToReadable, useQuery } from '../../utils/utils'
5 | import DPLTitleBar from '../../components/DPLTitleBar'
6 | import GithubForm from './GithubForm'
7 | import YouTubeForm from './YouTubeForm'
8 | import DiscordForm from './DiscordForm'
9 | import BackButton from '../../components/BackButton'
10 | import { UndefinedOr } from '@devprotocol/util-ts'
11 | import TitleSubSection from '../../components/TitleSubSection'
12 | import { FaDiscord, FaGithub, FaYoutube } from 'react-icons/fa'
13 | import ProgressStepper from '../../components/ProgressStepper'
14 |
15 | interface TokenizeFormPageProps {}
16 |
17 | const TokenizeFormPage: FunctionComponent = () => {
18 | const params = useParams()
19 | const navigate = useNavigate()
20 | const [market, setMarket] = useState>()
21 |
22 | const { search, hash } = useLocation()
23 | const target = search ? search : hash
24 | const queryParams = useQuery(target)
25 | const isPopup = Boolean(queryParams.popup)
26 |
27 | useEffect(() => {
28 | const _market = getMarketFromString(params.market)
29 |
30 | setMarket(_market)
31 |
32 | if (_market === Market.INVALID) {
33 | navigate('/tokenize')
34 | return
35 | }
36 | }, [params, navigate, setMarket, market])
37 |
38 | return (
39 |
40 |
41 |
45 |
46 |
49 |
50 |
51 |
52 | {market === Market.GITHUB && }
53 | {market === Market.YOUTUBE && }
54 | {market === Market.DISCORD && }
55 | {marketToReadable(market)} Project Information
56 |
57 |
58 | {market === Market.GITHUB &&
}
59 | {market === Market.YOUTUBE &&
}
60 | {market === Market.DISCORD &&
}
61 |
62 | )
63 | }
64 |
65 | export default TokenizeFormPage
66 |
--------------------------------------------------------------------------------
/src/pages/tokenize-market-select/TokenizeLink.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router-dom'
3 | import Card from '../../components/Card'
4 |
5 | interface TokenizeLinkProps {
6 | title: string
7 | icon?: React.ReactElement
8 | details: string | React.ReactElement
9 | disabled?: boolean
10 | path: string
11 | }
12 |
13 | const TokenizeLink: React.FC = ({ title, icon, details, path, disabled = false }) => {
14 | const LinkContents = (
15 |
16 |
17 |
18 | {icon}
19 | {title}
20 |
21 | {disabled && Coming Soon }
22 |
23 |
{details}
24 |
25 | )
26 |
27 | return disabled ? (
28 | {LinkContents}
29 | ) : (
30 |
31 | {LinkContents}
32 |
33 | )
34 | }
35 |
36 | export default TokenizeLink
37 |
--------------------------------------------------------------------------------
/src/pages/tokenize-market-select/index.tsx:
--------------------------------------------------------------------------------
1 | import { FunctionComponent, useContext, useEffect } from 'react'
2 | import BackButton from '../../components/BackButton'
3 | import DPLTitleBar from '../../components/DPLTitleBar'
4 | import TokenizeLink from './TokenizeLink'
5 | import { FaDiscord, FaGithub, FaLightbulb, FaTwitter, FaYoutube } from 'react-icons/fa'
6 | import TitleSubSection from '../../components/TitleSubSection'
7 | import { TokenizeContext } from '../../context/tokenizeContext'
8 | import ProgressStepper from '../../components/ProgressStepper'
9 | import { TOKENIZE_STEP_LABELS } from '../../const'
10 |
11 | interface TokenizeMarketSelectProps {}
12 |
13 | const TokenizeMarketSelect: FunctionComponent = () => {
14 | const { reset } = useContext(TokenizeContext)
15 |
16 | useEffect(() => {
17 | reset()
18 | }, [reset])
19 |
20 | return (
21 |
22 |
23 |
24 |
27 |
28 |
29 | Tokenize Your Project
30 |
31 |
32 |
33 |
34 |
35 |
}
38 | details="Tokenize your GitHub repository to make OSS more active"
39 | path="/tokenize/github"
40 | />
41 |
42 |
}
45 | details="Tokenize your YouTube channel to pursue what you love"
46 | disabled={false}
47 | path="/tokenize/youtube"
48 | />
49 |
50 |
}
53 | details="Tokenize your Discord Guild to pursue what you love"
54 | path="/tokenize/discord"
55 | />
56 |
57 |
}
60 | details="Tokenize your Twitter account to make your community more active"
61 | disabled={true}
62 | path="/tokenize/youtube"
63 | />
64 |
65 |
}
68 | details={
69 | <>
70 | Suggest platform integration requests in{' '}
71 |
77 | the forum
78 |
79 | >
80 | }
81 | disabled={true}
82 | path="/tokenize/youtube"
83 | />
84 |
85 |
86 |
87 | )
88 | }
89 |
90 | export default TokenizeMarketSelect
91 |
--------------------------------------------------------------------------------
/src/pages/tokenize-submit/TokenizePreviewSubmit.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import FormField from '../../components/Form'
3 | import HSButton from '../../components/HSButton'
4 | import { Market } from '../../const'
5 |
6 | interface TokenizePreviewSubmitProps {
7 | networkName: string
8 | address: string
9 | assetName: string
10 | tokenName: string
11 | tokenSymbol: string
12 | pat: string
13 | market?: Market
14 | isDisabled: boolean
15 | submit: () => {}
16 | }
17 |
18 | const TokenizePreviewSubmit: React.FC = ({
19 | networkName,
20 | address,
21 | assetName,
22 | tokenName,
23 | tokenSymbol,
24 | pat,
25 | market,
26 | isDisabled,
27 | submit
28 | }) => {
29 | return (
30 |
31 |
32 |
40 |
48 |
62 |
63 |
64 |
65 |
66 |
67 |
78 |
79 |
80 |
81 |
82 |
83 | Sign and submit
84 |
85 |
86 |
87 | )
88 | }
89 |
90 | export default TokenizePreviewSubmit
91 |
--------------------------------------------------------------------------------
/src/pages/tokenize-submit/TokenizeResult.tsx:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useEffect } from 'react'
2 | import HSButton from '../../components/HSButton'
3 | import { SectionLoading } from '../../components/Spinner'
4 | import { Market } from '../../const'
5 | import { useAddToWalletList } from '../../hooks/useAddToWalletList'
6 | import { useProvider } from '../../context/walletContext'
7 |
8 | interface TokenizeResultProps {
9 | isLoading: boolean
10 | errorMessage?: string
11 | newPropertyAddress?: string
12 | market?: Market
13 | tokenSymbol: string
14 | }
15 |
16 | const TokenizeResult: React.FC = ({
17 | isLoading,
18 | errorMessage,
19 | newPropertyAddress,
20 | market,
21 | tokenSymbol
22 | }) => {
23 | const { ethersProvider } = useProvider()
24 | const { addToWalletList } = useAddToWalletList()
25 |
26 | const addToUserWallet = useCallback(async () => {
27 | if (!newPropertyAddress || (newPropertyAddress && newPropertyAddress === '')) {
28 | return
29 | }
30 | addToWalletList(tokenSymbol, newPropertyAddress, ethersProvider)
31 | }, [tokenSymbol, newPropertyAddress, addToWalletList, ethersProvider])
32 |
33 | useEffect(() => {
34 | addToUserWallet()
35 | }, [addToUserWallet, newPropertyAddress])
36 |
37 | return (
38 | <>
39 | {isLoading && }
40 | {!isLoading && (
41 | <>
42 | {newPropertyAddress && (
43 |
44 |
Tokenization Success
45 |
46 |
47 |
Your Token
48 | {newPropertyAddress}
49 |
50 |
51 |
52 |
53 | )}
54 | {!newPropertyAddress && (
55 |
56 |
Tokenization Error
57 |
58 | {errorMessage ?? `Error tokenizing ${market} property`}
59 |
64 |
65 |
66 | )}
67 | >
68 | )}
69 | >
70 | )
71 | }
72 |
73 | export default TokenizeResult
74 |
--------------------------------------------------------------------------------
/src/pages/tokenize-submit/index.tsx:
--------------------------------------------------------------------------------
1 | import { UndefinedOr } from '@devprotocol/util-ts'
2 | import { FunctionComponent, useContext, useEffect, useState } from 'react'
3 | import { useLocation, useNavigate, useParams } from 'react-router-dom'
4 | import BackButton from '../../components/BackButton'
5 | import { Market, TOKENIZE_STEP_LABELS } from '../../const'
6 | import { TokenizeContext } from '../../context/tokenizeContext'
7 | import { getMarketFromString, marketToReadable, useQuery } from '../../utils/utils'
8 | import { useCreateAndAuthenticate, useCreateKhaosPubSign } from './tokenize-submit.hooks'
9 | import DPLTitleBar from '../../components/DPLTitleBar'
10 | import TokenizeResult from './TokenizeResult'
11 | import TokenizePreviewSubmit from './TokenizePreviewSubmit'
12 | import ProgressStepper from '../../components/ProgressStepper'
13 |
14 | interface TokenizeSubmitProps {}
15 |
16 | const TokenizeSubmit: FunctionComponent = () => {
17 | const params = useParams()
18 | const navigate = useNavigate()
19 | const [market, setMarket] = useState>()
20 | const [error, setError] = useState>()
21 | const [isLoading, setIsLoading] = useState(false)
22 | const [newPropertyAddress, setNewPropertyAddress] = useState>()
23 | const { network, address, isValid, assetName, tokenName, tokenSymbol, personalAccessToken, validateForm } =
24 | useContext(TokenizeContext)
25 | const { createKhaosPubSign, error: khaosError } = useCreateKhaosPubSign()
26 | const { createAndAuthenticate, error: tokenizeError } = useCreateAndAuthenticate()
27 | const { search, hash } = useLocation()
28 | const target = search ? search : hash
29 | const queryParams = useQuery(target)
30 | const isPopup = Boolean(queryParams.popup)
31 |
32 | useEffect(() => {
33 | const _market = getMarketFromString(params.market)
34 |
35 | if (_market === Market.INVALID) {
36 | navigate('/tokenize')
37 | return
38 | }
39 |
40 | setMarket(_market)
41 | }, [params, navigate, setMarket, market])
42 |
43 | useEffect(() => {
44 | if (tokenizeError) {
45 | setError(tokenizeError)
46 | }
47 | if (khaosError) {
48 | setError(khaosError)
49 | }
50 | }, [tokenizeError, khaosError])
51 |
52 | useEffect(() => {
53 | validateForm()
54 | }, [validateForm])
55 |
56 | const submit = async () => {
57 | setIsLoading(true)
58 | if (!isValid) {
59 | setError('Form invalid')
60 | setIsLoading(false)
61 | return
62 | }
63 |
64 | const pubSig = await createKhaosPubSign({
65 | assetName,
66 | personalAccessToken,
67 | signId:
68 | market === Market.YOUTUBE ? 'youtube-market' : market === Market.DISCORD ? 'discord-market' : 'github-market'
69 | })
70 | if (!pubSig) {
71 | setError('No pubsig found')
72 | setIsLoading(false)
73 | return
74 | }
75 |
76 | if (!market) {
77 | setError('No market set')
78 | setIsLoading(false)
79 | return
80 | }
81 |
82 | const propertyAddress = await createAndAuthenticate(tokenName, tokenSymbol, assetName, pubSig, market)
83 | if (!propertyAddress) {
84 | setError('No property address created')
85 | setIsLoading(false)
86 | return
87 | }
88 | setNewPropertyAddress(propertyAddress)
89 | setIsLoading(false)
90 |
91 | if (isPopup) {
92 | window.opener.postMessage({ address: propertyAddress }, '*')
93 | window.close()
94 | }
95 | }
96 |
97 | const submitDisabled = () => {
98 | return !isValid || isLoading
99 | }
100 |
101 | return (
102 |
103 |
107 |
108 |
109 |
112 |
113 | {!isLoading && !error && !newPropertyAddress && (
114 |
125 | )}
126 |
127 | {(isLoading || error || newPropertyAddress) && (
128 |
129 |
136 |
137 | )}
138 |
139 | )
140 | }
141 |
142 | export default TokenizeSubmit
143 |
--------------------------------------------------------------------------------
/src/pages/tokenize-submit/tokenize-submit.hooks.ts:
--------------------------------------------------------------------------------
1 | import { createPropertyFactoryContract } from '@devprotocol/dev-kit/l2'
2 | import { sign } from '@devprotocol/khaos-kit'
3 | import { UndefinedOr } from '@devprotocol/util-ts'
4 | import { useCallback, useState } from 'react'
5 | import { ERROR_MSG, Market } from '../../const'
6 | import { useProvider } from '../../context/walletContext'
7 | import { getMarketMetricsById } from '../../hooks/useMetrics'
8 | import {
9 | getNetworkMarketAddresses,
10 | getValidNetworkName,
11 | mapProviderToDevContracts,
12 | selectMarketAddressOption
13 | } from '../../utils/utils'
14 |
15 | type ICreateKhaosPubSignParams = {
16 | signId: string // ie 'github-market'
17 | personalAccessToken: string
18 | assetName: string // ie the github slug like "dev-protocol/protocol"
19 | }
20 |
21 | export const useCreateKhaosPubSign = () => {
22 | const [isLoading, setIsLoading] = useState(false)
23 | const [error, setError] = useState>()
24 | const { ethersProvider } = useProvider()
25 |
26 | const callback = useCallback(
27 | async ({ personalAccessToken, assetName, signId = 'github-market' }: ICreateKhaosPubSignParams) => {
28 | setIsLoading(true)
29 | setError(undefined)
30 | if (!ethersProvider) {
31 | setError(ERROR_MSG.no_provider)
32 | setIsLoading(false)
33 | return
34 | }
35 |
36 | try {
37 | const networkName = await getValidNetworkName((await ethersProvider.getNetwork()).chainId)
38 | if (!networkName) {
39 | setError(ERROR_MSG.invalid_network)
40 | setIsLoading(false)
41 | return
42 | }
43 |
44 | const signMessage = await ethersProvider.getSigner().signMessage(assetName)
45 | const signer = await sign(signId, networkName)
46 | const res = await signer({
47 | signature: signMessage,
48 | secret: personalAccessToken,
49 | message: assetName
50 | })
51 |
52 | setIsLoading(false)
53 | return res.publicSignature
54 | } catch (error) {
55 | console.error('createKhaosPubSign error: ', error)
56 | setError(`Failed to sign ${signId} market asset`)
57 | setIsLoading(false)
58 | console.log(error)
59 | }
60 | },
61 | [ethersProvider]
62 | )
63 |
64 | return { createKhaosPubSign: callback, isLoading, error }
65 | }
66 |
67 | export const useCreateAndAuthenticate = () => {
68 | const { ethersProvider } = useProvider()
69 | const [isLoading, setIsLoading] = useState(false)
70 | const [error, setError] = useState>()
71 |
72 | const callback = useCallback(
73 | async (tokenName: string, tokenSymbol: string, assetName: string, khaosPubSig: string, market: Market) => {
74 | setIsLoading(true)
75 | setError(undefined)
76 |
77 | if (!ethersProvider) {
78 | setError(ERROR_MSG.no_provider)
79 | setIsLoading(false)
80 | return
81 | }
82 |
83 | try {
84 | const networkDevContracts = await mapProviderToDevContracts(ethersProvider)
85 | if (!networkDevContracts) {
86 | setError(ERROR_MSG.invalid_network)
87 | setIsLoading(false)
88 | return
89 | }
90 | const propertyFactoryContract = await createPropertyFactoryContract(ethersProvider)(
91 | networkDevContracts.propertyFactory
92 | )
93 |
94 | const signer = ethersProvider.getSigner()
95 | const userAddress = await signer.getAddress()
96 | const marketOptions = await getNetworkMarketAddresses(ethersProvider)
97 | if (!marketOptions) {
98 | setError(ERROR_MSG.no_matching_market_options)
99 | setIsLoading(false)
100 | return
101 | }
102 | const marketAddress = selectMarketAddressOption(market, marketOptions)
103 | if (!marketAddress) {
104 | setError(ERROR_MSG.no_matching_market)
105 | setIsLoading(false)
106 | return
107 | }
108 |
109 | const metricsAddress = await getMarketMetricsById(ethersProvider, marketAddress, assetName)
110 | if (metricsAddress === '0x0000000000000000000000000000000000000000') {
111 | const created = await propertyFactoryContract.createAndAuthenticate(
112 | tokenName,
113 | tokenSymbol,
114 | marketAddress,
115 | [assetName, khaosPubSig],
116 | {
117 | metricsFactoryAddress: networkDevContracts.metricsFactory
118 | },
119 | {
120 | fallback: {
121 | from: userAddress,
122 | // value from stake.social createAndAuthenticate
123 | // should this be more dynamic based on network?
124 | gasLimit: 2000000
125 | }
126 | }
127 | )
128 |
129 | await created.waitForAuthentication()
130 |
131 | setIsLoading(false)
132 | return created.property
133 | } else {
134 | setError(`Metrics address ${metricsAddress} already exists for id ${assetName}`)
135 | setIsLoading(false)
136 | }
137 | } catch (error) {
138 | console.error(error)
139 | setError(`Failed to create and authenticate asset`)
140 | setIsLoading(false)
141 | }
142 | },
143 | [ethersProvider]
144 | )
145 |
146 | return { createAndAuthenticate: callback, isLoading, error }
147 | }
148 |
--------------------------------------------------------------------------------
/src/pages/user-positions-list/UserPositionListItem.tsx:
--------------------------------------------------------------------------------
1 | import { Positions } from '@devprotocol/dev-kit/l2'
2 | import { utils } from 'ethers'
3 | import React from 'react'
4 | import { Link } from 'react-router-dom'
5 | import Card from '../../components/Card'
6 | import { crunchAddress } from '../../utils/utils'
7 |
8 | interface UserPositionListItemProps {
9 | position: Positions
10 | }
11 |
12 | const UserPositionListItem: React.FC = ({ position }) => {
13 | const detail = (label: string, value: string) => (
14 |
15 |
{label}
16 |
{value}
17 |
18 | )
19 |
20 | return (
21 |
22 |
23 | {detail('Property', crunchAddress(position.property))}
24 | {detail('Amount', utils.formatEther(position.amount))}
25 | {detail('Price', utils.formatEther(position.price))}
26 | {detail('Cumulative Reward', utils.formatEther(position.cumulativeReward))}
27 | {detail('Pending Reward', utils.formatEther(position.pendingReward))}
28 |
29 |
30 | )
31 | }
32 |
33 | export default UserPositionListItem
34 |
--------------------------------------------------------------------------------
/src/pages/user-positions-list/index.tsx:
--------------------------------------------------------------------------------
1 | import { Positions } from '@devprotocol/dev-kit/l2'
2 | import React, { useEffect, useState } from 'react'
3 | import { useParams } from 'react-router-dom'
4 | import BackButton from '../../components/BackButton'
5 | import Card from '../../components/Card'
6 | import CopyButton from '../../components/CopyButton'
7 | import DPLTitleBar from '../../components/DPLTitleBar'
8 | import { NavTabItem, NavTabs } from '../../components/NavTabs'
9 | import { SectionLoading } from '../../components/Spinner'
10 | import { usePosition } from '../../hooks/usePosition'
11 | import { usePositionsOfOwner } from '../../hooks/usePositionsOfOwner'
12 | import { crunchAddress } from '../../utils/utils'
13 | import UserPositionListItem from './UserPositionListItem'
14 |
15 | interface UserPositionsListPageProps {}
16 |
17 | const UserPositionsListPage: React.FC = () => {
18 | const { userAddress } = useParams()
19 | const { fetchPositionsOfOwner } = usePositionsOfOwner()
20 | const { fetchPosition } = usePosition()
21 | const [userPositions, setUserPositions] = useState([])
22 | const [isLoading, setIsLoading] = useState(false)
23 |
24 | useEffect(() => {
25 | if (!userAddress) {
26 | return
27 | }
28 | ;(async () => {
29 | setIsLoading(true)
30 | setUserPositions([])
31 | const userPositionIds = await fetchPositionsOfOwner(userAddress)
32 | const positionCalls = userPositionIds?.map(async id => fetchPosition(id))
33 | if (!positionCalls) {
34 | return
35 | }
36 | const res = await Promise.all([...positionCalls])
37 | setUserPositions(res.filter((position): position is Positions => !!position))
38 | setIsLoading(false)
39 | })()
40 | }, [userAddress, fetchPositionsOfOwner, fetchPosition])
41 |
42 | return (
43 |
44 |
45 |
46 |
47 |
48 | {userAddress}
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | {!isLoading && (
58 | <>
59 | {userPositions && userPositions.length <= 0 && (
60 |
61 | You have no positions
62 |
63 | )}
64 | {userAddress && userPositions && userPositions.length > 0 && (
65 |
66 | {userPositions.map((position, i) => (
67 |
68 | ))}
69 |
70 | )}
71 | >
72 | )}
73 | {isLoading &&
}
74 |
75 |
76 | )
77 | }
78 |
79 | export default UserPositionsListPage
80 |
--------------------------------------------------------------------------------
/src/pages/user-properties-list/UserTokenListItem.tsx:
--------------------------------------------------------------------------------
1 | import { FunctionComponent, useEffect, useState } from 'react'
2 | import { Market } from '../../const'
3 | import { FaGithub, FaYoutube } from 'react-icons/fa'
4 | import { PropertyContract } from '@devprotocol/dev-kit/l2'
5 | import { AddressContractContainer } from '../../types/AddressContractContainer'
6 | import { Link } from 'react-router-dom'
7 | import { usePropertyDetails } from '../../hooks/usePropertyDetails'
8 | import { crunchAddress, toDisplayAmount } from '../../utils/utils'
9 | import Card from '../../components/Card'
10 | import { SectionLoading } from '../../components/Spinner'
11 |
12 | interface UserTokenListItemProps {
13 | property: AddressContractContainer
14 | userAddress: string
15 | }
16 |
17 | const UserTokenListItem: FunctionComponent = ({ property, userAddress }) => {
18 | const [supply, setSupply] = useState('10,000,000')
19 | const [userHoldAmount, setUserHoldAmount] = useState('0')
20 | const { address, contract } = property
21 |
22 | const { propertyDetails, isLoading, error } = usePropertyDetails(property.address)
23 |
24 | useEffect(() => {
25 | ;(async () => {
26 | setSupply(toDisplayAmount(await contract.totalSupply()))
27 | setUserHoldAmount(toDisplayAmount(await contract.balanceOf(userAddress)))
28 | })()
29 | }, [contract, userAddress])
30 |
31 | return (
32 | <>
33 | {propertyDetails && !isLoading && (
34 |
35 |
36 |
37 |
38 | {propertyDetails.propertyName} ({propertyDetails.propertySymbol})
39 |
40 | {crunchAddress(address)}
41 |
42 |
43 |
44 | {userHoldAmount} / {supply}
45 |
46 |
47 |
48 |
49 | {propertyDetails.market === Market.GITHUB && }
50 | {propertyDetails.market === Market.YOUTUBE && }
51 | {propertyDetails.id}
52 |
53 |
54 |
55 | )}
56 | {isLoading && }
57 | {error && (
58 |
59 | {error}
60 |
61 | )}
62 | >
63 | )
64 | }
65 |
66 | export default UserTokenListItem
67 |
--------------------------------------------------------------------------------
/src/pages/user-properties-list/fetchUserProperties.hook.tsx:
--------------------------------------------------------------------------------
1 | import { createPropertyFactoryContract, PropertyContract } from '@devprotocol/dev-kit/l2'
2 | import { UndefinedOr, whenDefined } from '@devprotocol/util-ts'
3 | import { providers } from 'ethers'
4 | import useSWR from 'swr'
5 | import { EMPTY_USER_TOKEN_PATH } from '../../const'
6 | import { SWRCachePath } from '../../const/cache-path'
7 | import { useProvider } from '../../context/walletContext'
8 | import { AddressContractContainer } from '../../types/AddressContractContainer'
9 | import { getPropertyData, mapProviderToDevContracts } from '../../utils/utils'
10 |
11 | export const getUserPropertyList = async (
12 | provider: providers.JsonRpcProvider,
13 | userAddress?: string
14 | ): Promise[]>> => {
15 | if (!userAddress || userAddress === EMPTY_USER_TOKEN_PATH) {
16 | return []
17 | }
18 | const networkDevContracts = await mapProviderToDevContracts(provider)
19 | if (!networkDevContracts) {
20 | return
21 | }
22 | const propertyFactoryContract = createPropertyFactoryContract(provider)(networkDevContracts.propertyFactory)
23 | const properties = await propertyFactoryContract.getPropertiesOfAuthor(userAddress)
24 | const calls = properties.map(async _address => {
25 | const contract = await getPropertyData(provider, _address)
26 | return {
27 | address: _address,
28 | contract
29 | }
30 | })
31 | const propertyData = await Promise.all(calls)
32 | return propertyData
33 | }
34 |
35 | export const useUserPropertiesList = (userAddress?: string) => {
36 | const { nonConnectedEthersProvider } = useProvider()
37 | const { data, error } = useSWR(
38 | SWRCachePath.getUserPropertyList(userAddress),
39 | () => whenDefined(nonConnectedEthersProvider, client => getUserPropertyList(client, userAddress)),
40 | {
41 | onError: err => {
42 | console.log(err)
43 | },
44 | revalidateOnFocus: false,
45 | focusThrottleInterval: 0
46 | }
47 | )
48 | return { userProperties: data, error }
49 | }
50 |
--------------------------------------------------------------------------------
/src/pages/user-properties-list/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import BackButton from '../../components/BackButton'
3 | import { useParams } from 'react-router-dom'
4 | import UserTokenListItem from './UserTokenListItem'
5 |
6 | import DPLTitleBar from '../../components/DPLTitleBar'
7 | import { EMPTY_USER_TOKEN_PATH } from '../../const'
8 | import { useUserPropertiesList } from './fetchUserProperties.hook'
9 | import { crunchAddress } from '../../utils/utils'
10 | import Card from '../../components/Card'
11 | import { NavTabItem, NavTabs } from '../../components/NavTabs'
12 | import CopyButton from '../../components/CopyButton'
13 |
14 | interface UserPropertiesListPageProps {
15 | // Props
16 | }
17 |
18 | const UserPropertiesListPage: React.FC = () => {
19 | const { userAddress } = useParams()
20 | const { userProperties } = useUserPropertiesList(userAddress)
21 |
22 | return (
23 |
24 |
25 |
26 |
27 | {userAddress}
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | {userProperties && userProperties.length <= 0 && (
38 |
39 | You have no tokens
40 |
41 | )}
42 |
43 |
44 | {userAddress && userAddress !== EMPTY_USER_TOKEN_PATH && userProperties && userProperties.length > 0 && (
45 |
46 | {userProperties.map(property => (
47 |
48 | ))}
49 |
50 | )}
51 |
52 |
53 | )
54 | }
55 |
56 | export default UserPropertiesListPage
57 |
--------------------------------------------------------------------------------
/src/pages/wait-market/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import BackButton from '../../components/BackButton'
3 | import DPLTitleBar from '../../components/DPLTitleBar'
4 |
5 | interface WaitMarketPageProps {}
6 |
7 | const WaitMarketPage: React.FC = () => {
8 | return (
9 | <>
10 |
11 |
12 |
19 | >
20 | )
21 | }
22 |
23 | export default WaitMarketPage
24 |
--------------------------------------------------------------------------------
/src/types/AddressContractContainer.ts:
--------------------------------------------------------------------------------
1 | export interface AddressContractContainer {
2 | address: string
3 | contract: T
4 | }
5 |
--------------------------------------------------------------------------------
/src/types/TokenizeWindowState.ts:
--------------------------------------------------------------------------------
1 | export type TokenizeWindowState = {
2 | isPopup: boolean
3 | }
4 |
--------------------------------------------------------------------------------
/src/utils/utils.test.ts:
--------------------------------------------------------------------------------
1 | import { addresses, marketAddresses } from '@devprotocol/dev-kit'
2 | import { UndefinedOr } from '@devprotocol/util-ts'
3 | import { ethers } from 'ethers'
4 | import { it, describe, expect, beforeEach } from 'vitest'
5 | import { Market } from '../const'
6 | import {
7 | crunchAddress,
8 | getMarketFromString,
9 | getNetworkMarketAddresses,
10 | isValidNetwork,
11 | mapProviderToDevContracts,
12 | marketToReadable,
13 | selectMarketAddressOption
14 | } from './utils'
15 |
16 | describe(`utils`, () => {
17 | it('getMarketFromString', () => {
18 | const market = getMarketFromString('github')
19 | expect(market).to.eq(Market.GITHUB)
20 | const undefinedMarket = getMarketFromString('abc')
21 | expect(undefinedMarket).to.eq(Market.INVALID)
22 | })
23 |
24 | it('marketToReadable', () => {
25 | const readable = marketToReadable(Market.GITHUB)
26 | expect(readable).to.eq('GitHub')
27 | })
28 |
29 | it('should check valid network', () => {
30 | const invalid = isValidNetwork(1)
31 | expect(invalid).to.eq(false)
32 |
33 | const polygon = isValidNetwork(137)
34 | expect(polygon).to.eq(true)
35 | })
36 |
37 | describe('mapProviderToDevContracts', () => {
38 | it('should correctly map network addresses by chain id', async () => {
39 | const chainId = 80001 // arbitrum testnet
40 | const provider = new ethers.providers.InfuraProvider(chainId)
41 | const contracts = await mapProviderToDevContracts(provider)
42 | expect(contracts?.token).to.eq(addresses.polygon.mumbai.token)
43 | })
44 |
45 | it('should reject mapping unsupported network', async () => {
46 | const chainId = 1 // L1 is unsupported on niwa
47 | const provider = new ethers.providers.InfuraProvider(chainId)
48 | await expect(mapProviderToDevContracts(provider)).rejects.toEqual('Invalid network')
49 | })
50 | })
51 |
52 | describe('getNetworkMarketAddresses', () => {
53 | it('should correctly map market addresses by chain id', async () => {
54 | const chainId = 80001 // arbitrum testnet
55 | const provider = new ethers.providers.InfuraProvider(chainId)
56 | const addresses = await getNetworkMarketAddresses(provider)
57 | expect(addresses?.github).to.eq(marketAddresses.polygon.mumbai.github)
58 | })
59 | it('should reject mapping unsupported network', async () => {
60 | const chainId = 1 // L1 is unsupported on niwa
61 | const provider = new ethers.providers.InfuraProvider(chainId)
62 | await expect(getNetworkMarketAddresses(provider)).rejects.toEqual('Invalid network')
63 | })
64 | })
65 |
66 | describe('selectMarketAddressOption', async () => {
67 | let chainId,
68 | provider,
69 | marketOptions: UndefinedOr<{
70 | github: string
71 | youtube: string // arbitrum testnet
72 | discord: string
73 | }>
74 |
75 | beforeEach(async () => {
76 | chainId = 80001 // polygon testnet
77 | provider = new ethers.providers.InfuraProvider(chainId)
78 | marketOptions = await getNetworkMarketAddresses(provider)
79 | })
80 |
81 | it('should correctly select from options', async () => {
82 | const marketAddress = selectMarketAddressOption(Market.GITHUB, marketOptions!)
83 | expect(marketAddress).to.eq(marketOptions?.github)
84 | })
85 | it('should fail with invalid market', () => {
86 | const marketAddress = selectMarketAddressOption(Market.INVALID, marketOptions!)
87 | expect(marketAddress).to.eq(undefined)
88 | })
89 | })
90 |
91 | describe('crunchAddress', () => {
92 | it('should crunch the address', () => {
93 | expect(crunchAddress('0x466fd7d4dBeB049ee42b0DdDcb69D517125E3c94')).to.eq('466f...3c94')
94 | })
95 | it('should return an empty string', () => {
96 | expect(crunchAddress('123')).to.eq('')
97 | })
98 | })
99 | })
100 |
101 | export {}
102 |
--------------------------------------------------------------------------------
/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/vite.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.md' {
2 | // "unknown" would be more detailed depends on how you structure frontmatter
3 | const attributes: Record
4 |
5 | // When "Mode.React" is requested. VFC could take a generic like React.VFC<{ MyComponent: TypeOfMyComponent }>
6 | import React from 'react'
7 | const ReactComponent: React.VFC
8 |
9 | // Modify below per your usage
10 | export { attributes, ReactComponent }
11 | }
12 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | const colors = require('tailwindcss/colors')
2 |
3 | module.exports = {
4 | content: ['./index.html', './src/**/*.{ts,tsx}'],
5 | theme: {
6 | extend: {
7 | colors: {
8 | link: '#00A3FF',
9 | success: 'rgb(34, 197, 94)'
10 | },
11 | gradientColorStops: theme => ({
12 | primary: '#3b82f6',
13 | secondary: '#0891b2'
14 | }),
15 | borderRadius: {},
16 | padding: {},
17 | margin: {},
18 | spacing: {
19 | sm: '1rem',
20 | md: '2rem',
21 | lg: '3rem',
22 | xl: '4rem'
23 | },
24 | fontFamily: {
25 | body: ['Syne', 'sans-serif']
26 | },
27 | fontSize: {},
28 | fontWeight: {},
29 | lineHeight: {},
30 | backgroundImage: {
31 | 'heading-texture': "url('/src/img/HEADING_TEXTURE.png')"
32 | }
33 | }
34 | },
35 | plugins: []
36 | }
37 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ESNext",
4 | "useDefineForClassFields": true,
5 | "lib": ["DOM", "DOM.Iterable", "ESNext"],
6 | "allowJs": false,
7 | "skipLibCheck": true,
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 |
--------------------------------------------------------------------------------
/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "rewrites": [{ "source": "/(.*)", "destination": "/" }]
3 | }
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 | import inject from '@rollup/plugin-inject'
4 | import mdPlugin, { Mode } from 'vite-plugin-markdown'
5 |
6 | const mdOptions = {
7 | mode: [Mode.REACT]
8 | }
9 |
10 | // https://vitejs.dev/config/
11 | export default defineConfig({
12 | plugins: [react(), mdPlugin(mdOptions)],
13 | resolve: {
14 | alias: {
15 | process: 'process/browser',
16 | stream: 'stream-browserify',
17 | zlib: 'browserify-zlib',
18 | util: 'util'
19 | }
20 | },
21 | build: {
22 | target: ['esnext'],
23 | rollupOptions: {
24 | plugins: [inject({ Buffer: ['buffer', 'Buffer'] })]
25 | },
26 | commonjsOptions: {
27 | transformMixedEsModules: true
28 | }
29 | }
30 | })
31 |
--------------------------------------------------------------------------------