├── .babelrc
├── .env.sample
├── .gitignore
├── README.md
├── package.json
├── scripts
├── build
└── start
├── src
├── App.css
├── App.js
├── App.test.js
├── StoredList.js
├── addresses
│ ├── balancePools.json
│ └── mainnet.json
├── assets
│ ├── activity-no-results.png
│ ├── dao-not-found.png
│ ├── default-app-icon.svg
│ ├── eagle.svg
│ ├── favicon-logo.png
│ ├── favicon.png
│ ├── favicon.svg
│ ├── global-preferences-custom-labels.svg
│ ├── global-preferences-help-and-feedback.svg
│ ├── global-preferences-network.svg
│ ├── global-preferences-notifications.svg
│ ├── logo-black.png
│ ├── logo-color.png
│ ├── logo-white.png
│ ├── logo.png
│ ├── logo0.png
│ ├── logo1.png
│ ├── logo2.png
│ ├── logo3.png
│ ├── logo4.png
│ ├── logo_old.png
│ ├── no-results.png
│ ├── transaction-error.svg
│ ├── transaction-pending.svg
│ └── transaction-success.svg
├── components
│ ├── AccountModule
│ │ ├── AccountModule.js
│ │ ├── AccountModuleConnectedScreen.js
│ │ ├── AccountModuleConnectingScreen.js
│ │ ├── AccountModuleErrorScreen.js
│ │ ├── AccountModulePopover.js
│ │ ├── AccountModuleProvidersScreen.js
│ │ ├── ButtonAccount.js
│ │ ├── ButtonConnect.js
│ │ ├── WalletSyncedInfo.js
│ │ ├── assets
│ │ │ ├── connection-error.png
│ │ │ └── loading-ring.svg
│ │ ├── connection-hooks.js
│ │ ├── connection-statuses.js
│ │ └── utils.js
│ ├── Backend
│ │ ├── Backend.js
│ │ ├── NftxEvents.js
│ │ ├── NftxReadPanel.js
│ │ ├── NftxWritePanel.js
│ │ ├── XStoreEvents.js
│ │ └── XStoreReadPanel.js
│ ├── Bounties
│ │ └── Bounties.js
│ ├── CreateD2Panel
│ │ └── CreateD2Panel.js
│ ├── D1FundList
│ │ └── D1FundList.js
│ ├── D1FundView
│ │ ├── D1FundView.js
│ │ ├── D1FundView_Old.js
│ │ ├── FundEvents.js
│ │ └── punkAttr4.json
│ ├── D1Funds
│ │ └── D1Funds.js
│ ├── D2FundView
│ │ └── D2FundView.js
│ ├── FundIcon
│ │ └── FundIcon.js
│ ├── FundView
│ │ └── FundView.js
│ ├── Funds
│ │ └── Funds.js
│ ├── FundsIndex
│ │ └── FundsIndex.js
│ ├── FundsList
│ │ └── FundsList.js
│ ├── GlobalPreferences
│ │ ├── GlobalPreferences.js
│ │ └── Network
│ │ │ └── Network.js
│ ├── HashField
│ │ └── HashField.js
│ ├── Header
│ │ └── Header.js
│ ├── HomeButton
│ │ └── HomeButton.js
│ ├── InnerPanels
│ │ ├── ApproveNftsPanel.js
│ │ ├── CreateErc20Panel.js
│ │ ├── CreateFundPanel.js
│ │ ├── CreateNftPanel.js
│ │ ├── FillBountyPanel.js
│ │ ├── ManageFundPanel.js
│ │ ├── MintD1FundPanel.js
│ │ ├── MintD2FundPanel.js
│ │ ├── MintNftPanel.js
│ │ ├── MintRequestPanel.js
│ │ ├── RedeemD1FundPanel.js
│ │ ├── RedeemD2FundPanel.js
│ │ └── TransferNftPanel.js
│ ├── Landing
│ │ ├── Landing.js
│ │ ├── Suggestions
│ │ │ ├── FavoritesMenu
│ │ │ │ ├── FavoritesMenu.js
│ │ │ │ ├── FavoritesMenuItem.js
│ │ │ │ └── FavoritesMenuItemButton.js
│ │ │ └── Suggestions.js
│ │ └── assets
│ │ │ ├── action-create.png
│ │ │ └── action-open.png
│ ├── MintPanel
│ │ └── MintPanel.js
│ ├── NftFundList
│ │ └── NftFundList.js
│ ├── NftList
│ │ └── NftList.js
│ ├── RedeemPanel
│ │ └── RedeemPanel.js
│ ├── Site
│ │ ├── RoundButton
│ │ │ └── RoundButton.js
│ │ └── Site.js
│ ├── TopBar
│ │ └── TopBar.js
│ ├── Tutorial
│ │ └── Tutorial.js
│ ├── Welcome
│ │ └── Welcome.js
│ └── WelcomeAction
│ │ └── WelcomeAction.js
├── constants.js
├── contexts
│ ├── FavoriteFundsContext.js
│ └── FavoriteNFTsContext.js
├── contracts
│ ├── ERC20.json
│ ├── ERC721.json
│ ├── ERC721Public.json
│ ├── IERC1155.json
│ ├── IERC20.json
│ ├── IERC721.json
│ ├── IERC721Plus.json
│ ├── KittyCore.json
│ ├── NFTX.json
│ ├── NFTXv2.json
│ ├── NFTXv3.json
│ ├── NFTXv6.json
│ ├── NFTXv7.json
│ ├── XBounties.json
│ ├── XStore.json
│ └── XToken.json
├── copy-to-clipboard
│ └── index.js
├── data
│ ├── fundInfo.json
│ └── mainnetD1Funds.json
├── environment.js
├── errors.js
├── ethereum-providers
│ ├── icons
│ │ ├── Cipher.png
│ │ ├── Fortmatic.svg
│ │ ├── Frame.png
│ │ ├── Metamask.png
│ │ ├── Portis.svg
│ │ ├── Status.png
│ │ └── wallet.svg
│ └── index.js
├── hooks.js
├── index.css
├── index.html
├── index.js
├── keycodes.js
├── known-funds
│ ├── images
│ │ └── blankdao.svg
│ └── index.js
├── local-settings.js
├── logo.svg
├── network-config.js
├── prop-types.js
├── reportWebVitals.js
├── routing-utils.js
├── setupTests.js
├── suggested-funds.js
├── symbols.js
├── utils.js
└── web3-utils.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-env",
5 | {
6 | "modules": false,
7 | "useBuiltIns": "entry",
8 | "corejs": 3
9 | }
10 | ],
11 | "@babel/preset-react"
12 | ],
13 | "plugins": ["babel-plugin-styled-components"]
14 | }
--------------------------------------------------------------------------------
/.env.sample:
--------------------------------------------------------------------------------
1 | ALCHEMY_API_KEY=
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # configuration
4 | # (keep `.env.enc` and `.env.sample`, but ignore all other `.env`s)
5 | .env*
6 | !.env.enc
7 | !.env.sample
8 |
9 | # dependencies
10 | node_modules/
11 |
12 | # testing
13 | coverage/
14 |
15 | # production
16 | public/
17 |
18 | # zeit now configuration
19 | .now
20 |
21 | # misc
22 | .DS_Store
23 | .cache
24 |
25 | npm-debug.log*
26 | yarn-debug.log*
27 | yarn-error.log*
28 |
29 | # ignore package-lock files (only use yarn.lock)
30 | package-lock.json
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started
2 |
3 | ### `yarn install`
4 |
5 | ### `yarn start`
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "x-frontend",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@aragon/ui": "^1.7.0",
7 | "@testing-library/jest-dom": "^5.11.4",
8 | "@testing-library/react": "^11.1.0",
9 | "@testing-library/user-event": "^12.1.10",
10 | "axios": "^0.21.1",
11 | "bn.js": "^5.1.3",
12 | "clipboard-polyfill": "^3.0.1",
13 | "dotenv": "^8.2.0",
14 | "lodash.throttle": "^4.1.1",
15 | "lodash.uniqby": "^4.7.0",
16 | "react": "^17.0.1",
17 | "react-dom": "^17.0.1",
18 | "react-loader-spinner": "^3.1.14",
19 | "react-router-dom": "^5.2.0",
20 | "react-scripts": "4.0.0",
21 | "react-spring": "^7.2.10",
22 | "styled-components": "^5.2.1",
23 | "use-wallet": "^0.7.1",
24 | "web-vitals": "^0.2.4",
25 | "web3": "^1.3.0",
26 | "web3-utils": "^1.3.0"
27 | },
28 | "scripts": {
29 | "start": "node scripts/start",
30 | "build": "node scripts/build",
31 | "test": "react-scripts test",
32 | "eject": "react-scripts eject"
33 | },
34 | "eslintConfig": {
35 | "extends": [
36 | "react-app",
37 | "react-app/jest"
38 | ]
39 | },
40 | "browserslist": {
41 | "production": [
42 | ">0.2%",
43 | "not dead",
44 | "not op_mini all"
45 | ],
46 | "development": [
47 | "last 1 chrome version",
48 | "last 1 firefox version",
49 | "last 1 safari version"
50 | ]
51 | },
52 | "devDependencies": {
53 | "@babel/core": "^7.12.3",
54 | "@babel/plugin-proposal-class-properties": "^7.12.1",
55 | "@babel/preset-env": "^7.12.1",
56 | "@babel/preset-react": "^7.12.5",
57 | "babel-eslint": "^10.1.0",
58 | "babel-jest": "^26.6.3",
59 | "babel-plugin-styled-components": "^1.11.1",
60 | "parcel-bundler": "^1.12.4"
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/scripts/build:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | require("dotenv").config();
4 |
5 | const version = require("../package.json").version;
6 | const execute = require("child_process").execSync;
7 | const commitSha = (process.env.NOW_GITHUB_COMMIT_SHA || "").slice(0, 7);
8 |
9 | // First two arguments are node and script name
10 | const buildDir = process.argv[2] || "public";
11 |
12 | execute(`rimraf ${buildDir}`, { stdio: "inherit" });
13 | execute(`copy-aragon-ui-assets -n aragon-ui ./${buildDir}`, {
14 | stdio: "inherit",
15 | });
16 |
17 | // // Commit sha is provided in nightly builds
18 | // process.env.ARAGON_PACKAGE_VERSION = commitSha
19 | // ? `${version}-${commitSha}`
20 | // : version;
21 | execute(
22 | `parcel build src/index.html --out-dir ./${buildDir} --public-url ./ --no-cache`,
23 | { stdio: "inherit" }
24 | );
25 |
--------------------------------------------------------------------------------
/scripts/start:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | require("dotenv").config();
4 |
5 | // const version = require("../package.json").version;
6 | const execute = require("child_process").execSync;
7 |
8 | const clientPort = process.env.NFTX_PORT || process.env.REACT_APP_PORT || 3000;
9 |
10 | execute(`copy-aragon-ui-assets -n aragon-ui ./public`, {
11 | stdio: "inherit",
12 | });
13 |
14 | // process.env.ARAGON_PACKAGE_VERSION = version;
15 | execute(
16 | `parcel serve src/index.html --port ${clientPort} --out-dir ./public --no-cache`,
17 | { stdio: "inherit" }
18 | );
19 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { UseWalletProvider } from "use-wallet";
3 | import { Spring, animated } from "react-spring";
4 | import {
5 | SITE_STATUS_ERROR,
6 | SITE_STATUS_READY,
7 | SITE_STATUS_LOADING,
8 | SITE_STATUS_UNLOADED,
9 | } from "./symbols";
10 | import { getWeb3 } from "./web3-utils";
11 | import { web3Providers } from "./environment";
12 | import { Main, useTheme } from "@aragon/ui";
13 | import { HashRouter as Router, Switch, Route, Link } from "react-router-dom";
14 | import { FavoriteFundsProvider } from "./contexts/FavoriteFundsContext";
15 | import { FavoriteNFTsProvider } from "./contexts/FavoriteNFTsContext";
16 |
17 | // import MainView from "./components/_archived/MainView/MainView";
18 | import Site from "./components/Site/Site";
19 | import GlobalPreferences from "./components/GlobalPreferences/GlobalPreferences";
20 |
21 | const SELECTOR_NETWORKS = [
22 | ["main", "Ethereum Mainnet", "https://client.aragon.org/"],
23 | [
24 | "rinkeby",
25 | "Ethereum Testnet (Rinkeby)",
26 | "https://rinkeby.client.aragon.org/",
27 | ],
28 | ];
29 |
30 | function App() {
31 | const theme = useTheme();
32 | // const web3 = getWeb3(web3Providers.default);
33 |
34 | return (
35 |
40 | {({ opacity, scale }) => (
41 |
47 | `scale3d(${v}, ${v}, 1)`),
50 | }}
51 | >
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | )}
61 |
62 | );
63 | }
64 |
65 | export default function WrapAppWithProvider() {
66 | return (
67 |
68 |
69 | div:first-child > div:first-child > div:first-child {
73 | width: 100vw;
74 | padding-bottom: 0;
75 | }
76 | `}
77 | >
78 |
79 |
80 |
81 |
82 |
83 |
84 | );
85 | }
86 |
87 | const nftxTheme = {
88 | _name: "dark",
89 | _appearance: "dark",
90 | background: "#33284c",
91 | border: "#201143",
92 | overlay: "#33284c",
93 | content: "#FFFFFF",
94 | contentSecondary: "#967cd6",
95 | surface: "#41355e",
96 | surfaceContent: "#FFFFFF",
97 | surfaceContentSecondary: "#967cd6",
98 | surfaceIcon: "#967cd6",
99 | surfaceUnder: "#392d54",
100 | surfaceOpened: "#967cd6",
101 | surfaceSelected: "#3a2e55",
102 | surfaceHighlight: "#4e4071",
103 | surfacePressed: "#4d406e",
104 | surfaceInteractive: "#4e4071",
105 | feedbackSurface: "#5c4c82",
106 | feedbackSurfaceContent: "#242136",
107 | feedbackSurfaceContentSecondary: "#615f7e",
108 | warning: "#ffc010",
109 | warningSurface: "#5e5c60",
110 | warningSurfaceContent: "#ffc010",
111 | info: "#0091ff",
112 | infoSurface: "#414479",
113 | infoSurfaceContent: "#189afb",
114 | help: "#cb83ff",
115 | helpContent: "#FFFFFF",
116 | helpSurface: "#FFFFFF",
117 | helpSurfaceContent: "#242136",
118 | negative: "#ff7768",
119 | negativeContent: "#FFFFFF",
120 | negativeSurface: "#685c72",
121 | negativeSurfaceContent: "#ff6756",
122 | positive: "#2cc696",
123 | positiveContent: "#FFFFFF",
124 | positiveSurface: "#35585e",
125 | positiveSurfaceContent: "#2cc687",
126 | badge: "#524179",
127 | badgeContent: "#ffffff",
128 | badgePressed: "#5c4c82",
129 | tagIdentifier: "#874090",
130 | tagIdentifierContent: "#D1D0FF",
131 | tagNew: "#353f5e",
132 | tagNewContent: "#2da1c9",
133 | tagIndicator: "#524179",
134 | tagIndicatorContent: "#0092ff",
135 | tagActivity: "#0091ff",
136 | tagActivityContent: "#FFFFFF",
137 | hint: "#8266c3",
138 | link: "#0c68ff",
139 | focus: "#0c68ff",
140 | selected: "#0886e5",
141 | selectedContent: "#FFFFFF",
142 | selectedDisabled: "#242136",
143 | disabled: "#4d3f6f",
144 | disabledContent: "#9584bf",
145 | disabledIcon: "#8266c3",
146 | control: "#65578c",
147 | controlBorder: "#392c58",
148 | controlBorderPressed: "#73659a",
149 | controlDisabled: "#4d3f6f",
150 | controlSurface: "#faf9fc",
151 | controlUnder: "#f3f1f7",
152 | accent: "#b4496f",
153 | accentStart: "#f27070",
154 | accentEnd: "#0886e5",
155 | accentContent: "#FFFFFF",
156 | floating: "#1c2539",
157 | floatingContent: "#FFFFFF",
158 | green: "#7fc75a",
159 | yellow: "#F7D858",
160 | red: "#F08658",
161 | blue: "#3E7BF6",
162 | brown: "#876559",
163 | purple: "#7C80F2",
164 | };
165 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import App from './App';
3 |
4 | test('renders learn react link', () => {
5 | render();
6 | const linkElement = screen.getByText(/learn react/i);
7 | expect(linkElement).toBeInTheDocument();
8 | });
9 |
--------------------------------------------------------------------------------
/src/StoredList.js:
--------------------------------------------------------------------------------
1 | import { identity, log } from "./utils";
2 |
3 | class StoredList {
4 | // name: the key used by StoredList to save the list in localStorage.
5 | // preStringify: use this to transform an item of the list before being saved.
6 | // postParse: use this to transform an item of the list after it got loaded.
7 | constructor(name, { preStringify = identity, postParse = identity } = {}) {
8 | this.options = { preStringify, postParse };
9 | this.name = name;
10 | this.items = this.loadItems();
11 | }
12 | loadItems() {
13 | let items = null;
14 | const value = localStorage.getItem(this.name);
15 |
16 | if (value === null) {
17 | return [];
18 | }
19 |
20 | try {
21 | items = JSON.parse(value);
22 | } catch (err) {
23 | log(`StoredList (${this.name}) couldn’t parse the loaded items`, err);
24 | }
25 |
26 | if (!Array.isArray(items)) {
27 | items = null;
28 | log(
29 | `The data loaded by StoredList (${this.name}) is not an array`,
30 | items
31 | );
32 | }
33 |
34 | return items === null ? [] : items.map(this.options.postParse);
35 | }
36 | saveItems() {
37 | localStorage.setItem(
38 | this.name,
39 | JSON.stringify(this.items.map(this.options.preStringify))
40 | );
41 | }
42 | getItems() {
43 | return this.items;
44 | }
45 | update(items = []) {
46 | this.items = items;
47 | this.saveItems();
48 | return items;
49 | }
50 | add(value) {
51 | return this.update([...this.items, value]);
52 | }
53 | remove(index) {
54 | return this.update([
55 | ...this.items.slice(0, index),
56 | ...this.items.slice(index + 1),
57 | ]);
58 | }
59 | }
60 |
61 | export default StoredList;
62 |
--------------------------------------------------------------------------------
/src/addresses/balancePools.json:
--------------------------------------------------------------------------------
1 | {
2 | "0xf63db1719A19F9aDD032f6184E839F491E83f15c": "0x04df4fbb6a003d1db3dd83d6d3b9951455837fff"
3 | }
--------------------------------------------------------------------------------
/src/addresses/mainnet.json:
--------------------------------------------------------------------------------
1 | {
2 | "xStore": "0xBe54738723cea167a76ad5421b50cAa49692E7B7",
3 | "nftxProxy": "0xAf93fCce0548D3124A5fC3045adAf1ddE4e8Bf7e",
4 | "proxyController": "0x947c0bfA2bf3Ae009275f13F548Ba539d38741C2",
5 | "bounties": "0x9C5a36AEf5A7b04b0123b2064BD20bc47183e1DC",
6 | "dao": "0x40d73df4f99bae688ce3c23a01022224fe16c7b2",
7 | "tokenApp": "0x5566b3e5fc300a1b28c214b49a5950c34d00eb33",
8 | "cryptopunks": "0xb7f7f6c52f2e2fdb1963eab30438024864c313f6",
9 | "cryptokitties": "0x06012c8cf97BEaD5deAe237070F9587f8E7A266d",
10 | "axies": "0xF5b0A3eFB8e8E4c201e2A935F110eAaF3FFEcb8d",
11 | "avastars": "0xF3E778F839934fC819cFA1040AabaCeCBA01e049",
12 | "autoglyphs": "0xd4e4078ca3495DE5B1d4dB434BEbc5a986197782",
13 | "joys": "0x6c7B6cc55d4098400aC787C8793205D3E86C37C9",
14 | "nftxToken": "0x87d73e916d7057945c9bcd8cdd94e42a6f47f776"
15 | }
16 |
--------------------------------------------------------------------------------
/src/assets/activity-no-results.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NFTX-project/x-frontend/9f49d8f540820594cd5a2c2c4e2b06ef14b487ea/src/assets/activity-no-results.png
--------------------------------------------------------------------------------
/src/assets/dao-not-found.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NFTX-project/x-frontend/9f49d8f540820594cd5a2c2c4e2b06ef14b487ea/src/assets/dao-not-found.png
--------------------------------------------------------------------------------
/src/assets/default-app-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/eagle.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/favicon-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NFTX-project/x-frontend/9f49d8f540820594cd5a2c2c4e2b06ef14b487ea/src/assets/favicon-logo.png
--------------------------------------------------------------------------------
/src/assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NFTX-project/x-frontend/9f49d8f540820594cd5a2c2c4e2b06ef14b487ea/src/assets/favicon.png
--------------------------------------------------------------------------------
/src/assets/favicon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/global-preferences-custom-labels.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/global-preferences-help-and-feedback.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/global-preferences-notifications.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/logo-black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NFTX-project/x-frontend/9f49d8f540820594cd5a2c2c4e2b06ef14b487ea/src/assets/logo-black.png
--------------------------------------------------------------------------------
/src/assets/logo-color.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NFTX-project/x-frontend/9f49d8f540820594cd5a2c2c4e2b06ef14b487ea/src/assets/logo-color.png
--------------------------------------------------------------------------------
/src/assets/logo-white.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NFTX-project/x-frontend/9f49d8f540820594cd5a2c2c4e2b06ef14b487ea/src/assets/logo-white.png
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NFTX-project/x-frontend/9f49d8f540820594cd5a2c2c4e2b06ef14b487ea/src/assets/logo.png
--------------------------------------------------------------------------------
/src/assets/logo0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NFTX-project/x-frontend/9f49d8f540820594cd5a2c2c4e2b06ef14b487ea/src/assets/logo0.png
--------------------------------------------------------------------------------
/src/assets/logo1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NFTX-project/x-frontend/9f49d8f540820594cd5a2c2c4e2b06ef14b487ea/src/assets/logo1.png
--------------------------------------------------------------------------------
/src/assets/logo2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NFTX-project/x-frontend/9f49d8f540820594cd5a2c2c4e2b06ef14b487ea/src/assets/logo2.png
--------------------------------------------------------------------------------
/src/assets/logo3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NFTX-project/x-frontend/9f49d8f540820594cd5a2c2c4e2b06ef14b487ea/src/assets/logo3.png
--------------------------------------------------------------------------------
/src/assets/logo4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NFTX-project/x-frontend/9f49d8f540820594cd5a2c2c4e2b06ef14b487ea/src/assets/logo4.png
--------------------------------------------------------------------------------
/src/assets/logo_old.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NFTX-project/x-frontend/9f49d8f540820594cd5a2c2c4e2b06ef14b487ea/src/assets/logo_old.png
--------------------------------------------------------------------------------
/src/assets/no-results.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NFTX-project/x-frontend/9f49d8f540820594cd5a2c2c4e2b06ef14b487ea/src/assets/no-results.png
--------------------------------------------------------------------------------
/src/assets/transaction-error.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/transaction-pending.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/transaction-success.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/AccountModule/AccountModuleConnectedScreen.js:
--------------------------------------------------------------------------------
1 | import React, { useCallback } from "react";
2 | import PropTypes from "prop-types";
3 | import styled from "styled-components";
4 | import {
5 | Button,
6 | ButtonBase,
7 | GU,
8 | IconCheck,
9 | IconCopy,
10 | IconCross,
11 | IdentityBadge,
12 | RADIUS,
13 | textStyle,
14 | useTheme,
15 | } from "@aragon/ui";
16 | import { EthereumAddressType, EthereumProviderType } from "../../prop-types";
17 | import { useCopyToClipboard } from "../../copy-to-clipboard";
18 | import { useWallet } from "use-wallet";
19 | import {
20 | useNetworkConnectionData,
21 | useSyncState,
22 | useWalletConnectionDetails,
23 | } from "./connection-hooks";
24 | import WalletSyncedInfo from "./WalletSyncedInfo";
25 |
26 | function AccountModuleConnectedScreen({
27 | account,
28 | clientListening,
29 | clientOnline,
30 | clientSyncDelay,
31 | providerInfo,
32 | walletListening,
33 | walletOnline,
34 | walletSyncDelay,
35 | }) {
36 | const wallet = useWallet();
37 | const theme = useTheme();
38 |
39 | const {
40 | walletNetworkName,
41 | clientNetworkName,
42 | hasNetworkMismatch,
43 | } = useNetworkConnectionData();
44 |
45 | const copyAddress = useCopyToClipboard(account, "Address copied");
46 |
47 | const { header, info, status } = useSyncState(
48 | clientListening,
49 | walletListening,
50 | clientOnline,
51 | clientSyncDelay,
52 | walletSyncDelay
53 | );
54 | const { connectionMessage, connectionColor } = useWalletConnectionDetails(
55 | clientListening,
56 | walletListening,
57 | clientOnline,
58 | walletOnline,
59 | clientSyncDelay,
60 | walletSyncDelay,
61 | walletNetworkName
62 | );
63 |
64 | const handleDisconnect = useCallback(() => {
65 | wallet.deactivate();
66 | }, [wallet]);
67 |
68 | const Icon = connectionColor !== theme.positive ? IconCross : IconCheck;
69 |
70 | const formattedConnectionMessage = connectionMessage.includes("Connected")
71 | ? `Connected to Ethereum ${walletNetworkName} Network`
72 | : connectionMessage;
73 |
74 | return (
75 |
80 |
85 |
90 |
100 | {providerInfo.name}
101 |
102 |
108 |
121 |
127 |
132 |
133 |
134 |
135 |
143 |
144 | {walletNetworkName && (
145 |
150 | {formattedConnectionMessage}
151 |
152 | )}
153 |
154 |
155 | {hasNetworkMismatch ? (
156 |
161 | Please connect to the Ethereum {clientNetworkName} Network.
162 |
163 | ) : (
164 |
165 | )}
166 |
167 |
176 |
177 | );
178 | }
179 |
180 | AccountModuleConnectedScreen.propTypes = {
181 | account: EthereumAddressType,
182 | clientListening: PropTypes.bool,
183 | clientOnline: PropTypes.bool,
184 | clientSyncDelay: PropTypes.number,
185 | providerInfo: EthereumProviderType,
186 | walletListening: PropTypes.bool,
187 | walletOnline: PropTypes.bool,
188 | walletSyncDelay: PropTypes.number,
189 | };
190 |
191 | const FlexWrapper = styled.div`
192 | display: inline-flex;
193 | align-items: center;
194 | `;
195 |
196 | export default AccountModuleConnectedScreen;
197 |
--------------------------------------------------------------------------------
/src/components/AccountModule/AccountModuleConnectingScreen.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import { keyframes } from "styled-components";
4 | import { GU, useTheme, textStyle, Link } from "@aragon/ui";
5 | import {
6 | getProviderFromUseWalletId,
7 | getProviderString,
8 | } from "../../ethereum-providers";
9 |
10 | import loadingRing from "./assets/loading-ring.svg";
11 |
12 | const spin = keyframes`
13 | from {
14 | transform: rotate(0deg);
15 | }
16 | to {
17 | transform: rotate(360deg);
18 | }
19 | `;
20 |
21 | function AccountModuleConnectingScreen({ onCancel, providerId }) {
22 | const theme = useTheme();
23 | const provider = getProviderFromUseWalletId(providerId);
24 | return (
25 |
35 |
45 |
80 |
87 | Connecting to {provider.name}
88 |
89 |
95 | Log into {getProviderString("your Ethereum wallet", provider.id)}. You
96 | may be temporarily redirected to a new screen.
97 |
98 |
99 |
104 | Cancel
105 |
106 |
107 | );
108 | }
109 |
110 | AccountModuleConnectingScreen.propTypes = {
111 | providerId: PropTypes.string,
112 | onCancel: PropTypes.func.isRequired,
113 | };
114 |
115 | export default AccountModuleConnectingScreen;
116 |
--------------------------------------------------------------------------------
/src/components/AccountModule/AccountModuleErrorScreen.js:
--------------------------------------------------------------------------------
1 | import React, { useMemo, useRef } from "react";
2 | import PropTypes from "prop-types";
3 | import { GU, Link, textStyle, useTheme } from "@aragon/ui";
4 | import { UnsupportedChainError } from "use-wallet";
5 | import { network } from "../../environment";
6 |
7 | import connectionError from "./assets/connection-error.png";
8 |
9 | function AccountModuleErrorScreen({ error, onBack }) {
10 | const theme = useTheme();
11 | const elementRef = useRef();
12 |
13 | const [title, secondary] = useMemo(() => {
14 | if (error instanceof UnsupportedChainError) {
15 | return [
16 | "Wrong network",
17 | `Please select the ${network.shortName} network in your wallet and try again.`,
18 | ];
19 | }
20 | return [
21 | "Failed to enable your account",
22 | "You can try another Ethereum wallet.",
23 | ];
24 | }, [error]);
25 |
26 | return (
27 |
38 |
48 |
56 |
63 | {title}
64 |
65 |
71 | {secondary}
72 |
73 |
74 |
79 | OK, try again
80 |
81 |
82 | );
83 | }
84 |
85 | AccountModuleErrorScreen.propTypes = {
86 | onBack: PropTypes.func.isRequired,
87 | error: PropTypes.instanceOf(Error),
88 | };
89 |
90 | export default AccountModuleErrorScreen;
91 |
--------------------------------------------------------------------------------
/src/components/AccountModule/AccountModulePopover.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState, useRef } from "react";
2 | import PropTypes from "prop-types";
3 | import { GU, Popover, springs, textStyle, useTheme } from "@aragon/ui";
4 | import { Spring, Transition, animated } from "react-spring";
5 |
6 | const AnimatedDiv = animated.div;
7 |
8 | function AccountModulePopover({
9 | children,
10 | direction,
11 | heading,
12 | onClose,
13 | onOpen,
14 | opener,
15 | screenData,
16 | screenId,
17 | screenKey,
18 | visible,
19 | }) {
20 | const theme = useTheme();
21 | const [animate, setAnimate] = useState(false);
22 | const [height, setHeight] = useState(30 * GU);
23 | const [measuredHeight, setMeasuredHeight] = useState(true);
24 |
25 | // Prevents to lose the focus on the popover when a screen leaves while an
26 | // element inside is focused (e.g. when clicking on the “disconnect” button).
27 | const popoverFocusElement = useRef();
28 | useEffect(() => {
29 | if (popoverFocusElement.current) {
30 | popoverFocusElement.current.focus();
31 | }
32 | }, [screenId]);
33 |
34 | // Don’t animate the slider until the popover has opened
35 | useEffect(() => {
36 | let timer;
37 |
38 | setAnimate(false);
39 |
40 | if (visible) {
41 | timer = setTimeout(() => {
42 | setAnimate(true);
43 | }, 0);
44 | }
45 |
46 | return () => clearTimeout(timer);
47 | }, [visible]);
48 |
49 | return (
50 |
60 |
67 |
80 | {heading}
81 |
82 |
89 | {({ height }) => (
90 |
102 | {
118 | if (status === "update") {
119 | setMeasuredHeight(false);
120 | }
121 | }}
122 | onStart={(_, status) => {
123 | setMeasuredHeight(true);
124 | }}
125 | >
126 | {(screenData) => ({ opacity, transform }) => (
127 | {
129 | if (elt) {
130 | setHeight(elt.clientHeight);
131 | }
132 | }}
133 | style={{ opacity, transform }}
134 | css={`
135 | position: ${measuredHeight ? "absolute" : "static"};
136 | top: 0;
137 | left: 0;
138 | right: 0;
139 | `}
140 | >
141 | {children(screenData)}
142 |
143 | )}
144 |
145 |
146 | )}
147 |
148 |
149 |
150 | );
151 | }
152 |
153 | AccountModulePopover.propTypes = {
154 | children: PropTypes.func.isRequired,
155 | direction: PropTypes.oneOf([-1, 1]).isRequired,
156 | heading: PropTypes.node.isRequired,
157 | onClose: PropTypes.func.isRequired,
158 | onOpen: PropTypes.func.isRequired,
159 | opener: PropTypes.any,
160 | screenData: PropTypes.object.isRequired,
161 | screenId: PropTypes.string.isRequired,
162 | screenKey: PropTypes.func.isRequired,
163 | visible: PropTypes.bool.isRequired,
164 | };
165 |
166 | export default AccountModulePopover;
167 |
--------------------------------------------------------------------------------
/src/components/AccountModule/AccountModuleProvidersScreen.js:
--------------------------------------------------------------------------------
1 | import React, { useCallback } from "react";
2 | import PropTypes from "prop-types";
3 | import { ButtonBase, GU, Link, RADIUS, useTheme, textStyle } from "@aragon/ui";
4 | import { getProviderFromUseWalletId } from "../../ethereum-providers";
5 | import { providers } from "../../environment";
6 |
7 | const providersInfo = providers.map((provider) => [
8 | provider.id,
9 | getProviderFromUseWalletId(provider.id),
10 | ]);
11 |
12 | function ProviderButton({ id, provider, onActivate }) {
13 | const theme = useTheme();
14 |
15 | const handleClick = useCallback(() => {
16 | onActivate(id);
17 | }, [onActivate, id]);
18 |
19 | return (
20 |
40 |
41 |
47 | {provider.name}
48 |
49 |
50 | );
51 | }
52 | ProviderButton.propTypes = {
53 | id: PropTypes.string.isRequired,
54 | onActivate: PropTypes.func.isRequired,
55 | provider: PropTypes.shape({
56 | image: PropTypes.string.isRequired,
57 | name: PropTypes.string.isRequired,
58 | }).isRequired,
59 | };
60 |
61 | function AccountModuleProvidersScreen({ onActivate }) {
62 | return (
63 |
64 |
73 | {providersInfo.map(([id, provider]) => (
74 |
80 | ))}
81 |
82 |
89 |
90 | Don’t have an Ethereum account?
91 |
92 |
93 |
94 | );
95 | }
96 | AccountModuleProvidersScreen.propTypes = {
97 | onActivate: PropTypes.func.isRequired,
98 | };
99 |
100 | export default AccountModuleProvidersScreen;
101 |
--------------------------------------------------------------------------------
/src/components/AccountModule/ButtonAccount.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import {
4 | ButtonBase,
5 | EthIdenticon,
6 | GU,
7 | IconDown,
8 | RADIUS,
9 | textStyle,
10 | useTheme,
11 | useViewport,
12 | } from "@aragon/ui";
13 | import { shortenAddress } from "../../web3-utils";
14 | import { useWallet } from "use-wallet";
15 |
16 | const ButtonAccount = React.forwardRef(function ButtonAccount(
17 | { connectionColor, connectionMessage, hasNetworkMismatch, label, onClick },
18 | ref
19 | ) {
20 | const theme = useTheme();
21 | const wallet = useWallet();
22 | const { above } = useViewport();
23 | return (
24 |
37 |
45 |
60 | {above("medium") && (
61 |
62 |
68 |
74 | {label ? (
75 |
83 | {label}
84 |
85 | ) : (
86 |
{shortenAddress(wallet.account)}
87 | )}
88 |
89 |
95 | {hasNetworkMismatch ? "Wrong network" : connectionMessage}
96 |
97 |
98 |
99 |
105 |
106 | )}
107 |
108 |
109 | );
110 | });
111 | ButtonAccount.propTypes = {
112 | connectionColor: PropTypes.oneOfType([
113 | PropTypes.string,
114 | PropTypes.instanceOf(String),
115 | ]).isRequired,
116 | connectionMessage: PropTypes.string.isRequired,
117 | hasNetworkMismatch: PropTypes.bool.isRequired,
118 | label: PropTypes.string,
119 | onClick: PropTypes.func.isRequired,
120 | };
121 |
122 | export default ButtonAccount;
123 |
--------------------------------------------------------------------------------
/src/components/AccountModule/ButtonConnect.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import { Button, GU, IconConnect, useViewport } from "@aragon/ui";
4 |
5 | const ButtonConnect = React.forwardRef(function ButtonConnect(
6 | { onClick },
7 | ref
8 | ) {
9 | const { below } = useViewport();
10 |
11 | return (
12 | }
16 | label="Connect account"
17 | onClick={onClick}
18 | css={`
19 | min-width: ${5 * GU}px;
20 | margin: 0 ${2 * GU}px;
21 | `}
22 | />
23 | );
24 | });
25 |
26 | ButtonConnect.propTypes = {
27 | onClick: PropTypes.func.isRequired,
28 | };
29 |
30 | export default ButtonConnect;
31 |
--------------------------------------------------------------------------------
/src/components/AccountModule/WalletSyncedInfo.js:
--------------------------------------------------------------------------------
1 | import React, { useCallback } from "react";
2 | import PropTypes from "prop-types";
3 | import { GU, Link } from "@aragon/ui";
4 | import { network } from "../../environment";
5 | // import { useRouting } from "../../routing";
6 | import {
7 | STATUS_CLIENT_CONNECTION_DROPPED,
8 | STATUS_CONNECTION_OK,
9 | STATUS_MAJOR_NETWORK_SLOWDOWN,
10 | STATUS_NETWORK_SYNC_ISSUES,
11 | STATUS_TOO_LITTLE_ETH,
12 | STATUS_WALLET_CONNECTION_DROPPED,
13 | } from "./connection-statuses";
14 |
15 | function WalletSyncedInfo({ header, info, status }) {
16 | return (
17 |
18 | {header && (
19 |
24 |
30 | {header}
31 |
32 | {info}
33 |
34 | )}
35 | {status !== STATUS_CONNECTION_OK && (
36 |
41 |
42 |
43 | )}
44 |
45 | );
46 | }
47 |
48 | WalletSyncedInfo.propTypes = {
49 | header: PropTypes.string,
50 | info: PropTypes.string,
51 | status: PropTypes.oneOf([
52 | STATUS_CLIENT_CONNECTION_DROPPED,
53 | STATUS_CONNECTION_OK,
54 | STATUS_MAJOR_NETWORK_SLOWDOWN,
55 | STATUS_NETWORK_SYNC_ISSUES,
56 | STATUS_TOO_LITTLE_ETH,
57 | STATUS_WALLET_CONNECTION_DROPPED,
58 | ]),
59 | };
60 |
61 | function ConnectionInfoMessage({ connectionStatus }) {
62 | // const routing = useRouting();
63 |
64 | const handleNetworkSettingsClick = useCallback(
65 | () => {
66 | console.log("TODO: handlNetworkSettingsClick()");
67 | /* routing.update((locator) => ({
68 | ...locator,
69 | preferences: { section: "network" },
70 | })); */
71 | },
72 | [
73 | /* routing */
74 | ]
75 | );
76 |
77 | if (connectionStatus === STATUS_WALLET_CONNECTION_DROPPED) {
78 | return (
79 |
80 | We were unable to fetch network information from your wallet. You may
81 | not be able to send transactions. Please contact your wallet for support
82 | if this issue persists.
83 |
84 | );
85 | }
86 |
87 | if (connectionStatus === STATUS_CLIENT_CONNECTION_DROPPED) {
88 | return (
89 |
90 | We cannot connect to the wallet's Ethereum node. You can change the node
91 | settings in
92 | Network Settings.
93 | You can also refresh the client.
94 |
95 | );
96 | }
97 |
98 | if (connectionStatus === STATUS_NETWORK_SYNC_ISSUES) {
99 | return (
100 |
101 | Your wallet may not accurately reflect the current state of Ethereum's{" "}
102 | {network.name}. Please contact your wallet for support if this issue
103 | persists.
104 |
105 | );
106 | }
107 |
108 | if (connectionStatus === STATUS_MAJOR_NETWORK_SLOWDOWN) {
109 | return (
110 |
111 | The Ethereum {network.name} may be experiencing a global slowdown.
112 | Please avoid signing any transactions until this error is resolved.
113 |
114 | );
115 | }
116 |
117 | if (connectionStatus === STATUS_TOO_LITTLE_ETH) {
118 | return (
119 |
120 | You may not have enough ETH in your account to send any transactions.
121 |
122 | );
123 | }
124 |
125 | return null;
126 | }
127 |
128 | ConnectionInfoMessage.propTypes = {
129 | connectionStatus: PropTypes.oneOf([
130 | STATUS_CLIENT_CONNECTION_DROPPED,
131 | STATUS_CONNECTION_OK,
132 | STATUS_MAJOR_NETWORK_SLOWDOWN,
133 | STATUS_NETWORK_SYNC_ISSUES,
134 | STATUS_TOO_LITTLE_ETH,
135 | STATUS_WALLET_CONNECTION_DROPPED,
136 | ]),
137 | };
138 |
139 | export default WalletSyncedInfo;
140 |
--------------------------------------------------------------------------------
/src/components/AccountModule/assets/connection-error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NFTX-project/x-frontend/9f49d8f540820594cd5a2c2c4e2b06ef14b487ea/src/components/AccountModule/assets/connection-error.png
--------------------------------------------------------------------------------
/src/components/AccountModule/assets/loading-ring.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/AccountModule/connection-statuses.js:
--------------------------------------------------------------------------------
1 | export const STATUS_CONNECTION_ERROR = Symbol("STATUS_CONNECTION_ERROR");
2 | export const STATUS_CONNECTION_HEALTHY = Symbol("STATUS_CONNECTION_HEALTHY");
3 | export const STATUS_CONNECTION_WARNING = Symbol("STATUS_CONNECTION_WARNING");
4 |
5 | export const STATUS_WALLET_CONNECTION_DROPPED = Symbol(
6 | "STATUS_WALLET_CONNECTION_DROPPED"
7 | );
8 | export const STATUS_CLIENT_CONNECTION_DROPPED = Symbol(
9 | "STATUS_CLIENT_CONNECTION_DROPPED"
10 | );
11 | export const STATUS_NETWORK_SYNC_ISSUES = Symbol("STATUS_NETWORK_SYNC_ISSUES");
12 | export const STATUS_MAJOR_NETWORK_SLOWDOWN = Symbol(
13 | "STATUS_MAJOR_NETWORK_SLOWDOWN"
14 | );
15 | export const STATUS_TOO_LITTLE_ETH = Symbol("STATUS_TOO_LITTLE_ETH");
16 | export const STATUS_CONNECTION_OK = Symbol("STATUS_CONNECTION_OK");
17 |
--------------------------------------------------------------------------------
/src/components/AccountModule/utils.js:
--------------------------------------------------------------------------------
1 | import { getNetworkByChainId } from "../../network-config";
2 |
3 | export const MAX_PROVIDER_SYNC_DELAY = 30;
4 | export const MILD_PROVIDER_SYNC_DELAY = 5;
5 | export const OK_PROVIDER_SYNC_DELAY = 3;
6 |
7 | export function normalizeNetworkName(chainId) {
8 | return getNetworkByChainId(chainId).settings.shortName;
9 | }
10 |
--------------------------------------------------------------------------------
/src/components/Backend/Backend.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import PropTypes from "prop-types";
3 | import {
4 | DataView,
5 | ContextMenu,
6 | ContextMenuItem,
7 | Header,
8 | Button,
9 | AddressField,
10 | SidePanel,
11 | IconStarFilled,
12 | IconStar,
13 | IconCircleCheck,
14 | IconCircleMinus,
15 | Info,
16 | } from "@aragon/ui";
17 | import Web3 from "web3";
18 | import { useWallet } from "use-wallet";
19 | import { useFavoriteNFTs } from "../../contexts/FavoriteNFTsContext";
20 | import CreateNftPanel from "../InnerPanels/CreateNftPanel";
21 | import MintNftPanel from "../InnerPanels/MintNftPanel";
22 | import TransferNftPanel from "../InnerPanels/TransferNftPanel";
23 | import NftxReadPanel from "./NftxReadPanel";
24 | import NftxWritePanel from "./NftxWritePanel";
25 | import XStoreReadPanel from "./XStoreReadPanel";
26 | import NftxEvents from "./NftxEvents";
27 | import XStoreEvents from "./XStoreEvents";
28 |
29 | import addresses from "../../addresses/mainnet.json";
30 |
31 | function Backend() {
32 | const [panelTitle, setPanelTitle] = useState("");
33 | const [panelOpened, setPanelOpened] = useState(false);
34 | const [innerPanel, setInnerPanel] = useState();
35 |
36 | const [nftxEvents, setNFTXEvents] = useState([]);
37 |
38 | const handleReadNftx = () => {
39 | setPanelTitle(`NFTX ▸ Read`);
40 | setInnerPanel();
41 | setPanelOpened(true);
42 | };
43 |
44 | const handleWriteNftx = () => {
45 | setPanelTitle(`NFTX ▸ Write`);
46 | setInnerPanel( setPanelOpened(false)} />);
47 | setPanelOpened(true);
48 | };
49 |
50 | const handleReadXStore = () => {
51 | setPanelTitle(`XStore ▸ Read`);
52 | setInnerPanel();
53 | setPanelOpened(true);
54 | };
55 |
56 | const handleWriteXStore = () => {
57 | setPanelTitle(`XStore ▸ Write`);
58 | setInnerPanel(
59 |
64 | There are no callable write functions for XStore
65 |
66 | );
67 | setPanelOpened(true);
68 | };
69 |
70 | return (
71 |
72 |
73 |
83 |
{
97 | return [
98 | {name}
,
99 | ,
100 | {proxy ? : }
,
101 | ];
102 | }}
103 | renderEntryActions={(entry, index) => {
104 | return (
105 |
106 |
111 | Read...
112 |
113 |
118 | Write...
119 |
120 |
121 | );
122 | }}
123 | renderEntryExpansion={(entry, index) => {
124 | return entry.name === "NFTX" ? (
125 |
130 | Implementation address:{" "}
131 |
138 | 0x3A2f04fAa1d39AcB088BecE5C2D6B00E81AFe868
139 |
140 |
141 | ) : null;
142 | }}
143 | />
144 |
145 |
setPanelOpened(false)}
149 | >
150 | {innerPanel}
151 |
152 |
158 |
165 |
166 |
167 |
173 |
180 |
181 |
182 |
183 | );
184 | }
185 |
186 | export const NftType = PropTypes.shape({
187 | name: PropTypes.string,
188 | supply: PropTypes.string,
189 | address: PropTypes.string,
190 | });
191 |
192 | Backend.propTypes = {
193 | title: PropTypes.string,
194 | entries: PropTypes.arrayOf(NftType),
195 | handleMint: PropTypes.func,
196 | };
197 |
198 | export default Backend;
199 |
--------------------------------------------------------------------------------
/src/components/Backend/NftxEvents.js:
--------------------------------------------------------------------------------
1 | import React, { useRef, useState, useEffect } from "react";
2 | import { DataView } from "@aragon/ui";
3 | import Web3 from "web3";
4 | import { useWallet } from "use-wallet";
5 | import HashField from "../HashField/HashField";
6 | import Nftx from "../../contracts/NFTX.json";
7 | import addresses from "../../addresses/mainnet.json";
8 |
9 | function NftxEvents() {
10 | const { account } = useWallet();
11 | const injected = window.ethereum;
12 | const provider =
13 | injected && injected.chainId === "0x1"
14 | ? injected
15 | : `wss://eth-mainnet.ws.alchemyapi.io/v2/${process.env.ALCHEMY_API_KEY}`;
16 |
17 | const { current: web3 } = useRef(new Web3(provider));
18 | const nftx = new web3.eth.Contract(Nftx.abi, addresses.nftxProxy);
19 |
20 | const [events, setEvents] = useState([]);
21 | // const [eventTimes, setEventTimes] = useState([]);
22 |
23 | useEffect(() => {
24 | nftx
25 | .getPastEvents("allEvents", { fromBlock: 7664346, toBlock: "latest" })
26 | .then((result) => {
27 | const _events = result.reverse().slice(0, Math.min(25, result.length));
28 | setEvents(_events);
29 | });
30 | }, []);
31 |
32 | return (
33 |
34 |
{
40 | return [
41 | {event}
,
42 | ,
43 | {blockNumber}
,
44 | ];
45 | }}
46 | />
47 |
48 | );
49 | }
50 |
51 | export default NftxEvents;
52 |
--------------------------------------------------------------------------------
/src/components/Backend/NftxReadPanel.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef } from "react";
2 | import {
3 | DropDown,
4 | TextInput,
5 | Button,
6 | AddressField,
7 | IconCheck,
8 | } from "@aragon/ui";
9 | import Web3 from "web3";
10 | import { useWallet } from "use-wallet";
11 | import Nftx from "../../contracts/NFTX.json";
12 | import Loader from "react-loader-spinner";
13 | import HashField from "../HashField/HashField";
14 | import { useFavoriteNFTs } from "../../contexts/FavoriteNFTsContext";
15 | import addresses from "../../addresses/mainnet.json";
16 |
17 | function ManageFundPanel() {
18 | const { account } = useWallet();
19 |
20 | // const { addFavorite } = useFavoriteNFTs();
21 |
22 | const { current: web3 } = useRef(new Web3(window.ethereum));
23 |
24 | const [vaultId, setVaultId] = useState("");
25 | const [nftId, setNftId] = useState("");
26 | const [isEligible, setIsEligible] = useState("");
27 | const [vaultId2, setVaultId2] = useState("");
28 | const [vaultSize, setVaultSize] = useState("");
29 | const [owner, setOwner] = useState("");
30 | const [store, setStore] = useState("");
31 |
32 | // const [tokenId, setTokenId] = useState("");
33 | // const [recipient, setRecipient] = useState("");
34 |
35 | // const [txStatus, setTxStatus] = useState(null);
36 | const [txHash, setTxHash] = useState(null);
37 | const [txReceipt, setTxReceipt] = useState(null);
38 | const [txError, setTxError] = useState(null);
39 |
40 | const nftx = new web3.eth.Contract(Nftx.abi, addresses.nftxProxy);
41 |
42 | const getIsEligible = () => {
43 | nftx.methods
44 | .isEligible(vaultId, nftId)
45 | .call({ from: account })
46 | .then((retVal) => setIsEligible(retVal));
47 | };
48 |
49 | const getVaultSize = () => {
50 | nftx.methods
51 | .vaultSize(vaultId2)
52 | .call({ from: account })
53 | .then((retVal) => setVaultSize(retVal));
54 | };
55 |
56 | const getOwner = () => {
57 | nftx.methods
58 | .owner()
59 | .call({ from: account })
60 | .then((retVal) => setOwner(retVal));
61 | };
62 |
63 | const getStore = () => {
64 | nftx.methods
65 | .store()
66 | .call({ from: account })
67 | .then((retVal) => setStore(retVal));
68 | };
69 |
70 | return (
71 | div {
74 | margin-top: 25px;
75 | margin-bottom: 10px;
76 | }
77 | `}
78 | >
79 |
80 | setVaultId(event.target.value)}
83 | placeholder="vaultId (e.g. 6)"
84 | wide={true}
85 | css={`
86 | margin-bottom: 10px;
87 | `}
88 | />
89 | setNftId(event.target.value)}
92 | placeholder="nftId (e.g. 42)"
93 | wide={true}
94 | css={`
95 | margin-bottom: 10px;
96 | `}
97 | />
98 |
108 | event.target.value === "" && setIsEligible("")}
111 | placeholder="isEligible (bool)"
112 | wide={true}
113 | css={`
114 | margin-bottom: 10px;
115 | cursor: not-allowed;
116 | `}
117 | />
118 |
119 |
120 | setVaultId2(event.target.value)}
123 | placeholder="vaultId (e.g. 6)"
124 | wide={true}
125 | css={`
126 | margin-bottom: 10px;
127 | `}
128 | />
129 |
139 | event.target.value === "" && setVaultSize("")}
142 | placeholder="vaultSize (uint256)"
143 | wide={true}
144 | css={`
145 | margin-bottom: 10px;
146 | cursor: not-allowed;
147 | `}
148 | />
149 |
150 |
151 |
160 | event.target.value === "" && setOwner("")}
163 | placeholder="owner (address)"
164 | wide={true}
165 | css={`
166 | margin-bottom: 10px;
167 | cursor: not-allowed;
168 | `}
169 | />
170 |
171 |
172 |
181 | event.target.value === "" && setStore("")}
184 | placeholder="store (address)"
185 | wide={true}
186 | css={`
187 | margin-bottom: 10px;
188 | cursor: not-allowed;
189 | `}
190 | />
191 |
192 |
193 | );
194 | }
195 |
196 | export default ManageFundPanel;
197 |
--------------------------------------------------------------------------------
/src/components/Backend/NftxWritePanel.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef } from "react";
2 | import {
3 | DropDown,
4 | TextInput,
5 | Button,
6 | AddressField,
7 | IconCheck,
8 | } from "@aragon/ui";
9 | import Web3 from "web3";
10 | import { useWallet } from "use-wallet";
11 | // import Nftx from "../../contracts/NFTX.json";
12 | import NftxV7 from "../../contracts/NFTXv7.json";
13 | // import NftxV2 from "../../contracts/NFTXv2.json";
14 | import Loader from "react-loader-spinner";
15 | import HashField from "../HashField/HashField";
16 | import { useFavoriteNFTs } from "../../contexts/FavoriteNFTsContext";
17 | import addresses from "../../addresses/mainnet.json";
18 |
19 | function ManageFundPanel({ closePanel }) {
20 | const { account } = useWallet();
21 | const injected = window.ethereum;
22 | const provider =
23 | injected && (injected.chainId === "0x1" || injected.isFrame)
24 | ? injected
25 | : `wss://eth-mainnet.ws.alchemyapi.io/v2/${process.env.ALCHEMY_API_KEY}`;
26 |
27 | const { current: web3 } = useRef(new Web3(provider));
28 |
29 | const nftx = new web3.eth.Contract(NftxV7.abi, addresses.nftxProxy);
30 |
31 | const [funcParams, setFuncParams] = useState(JSON.parse("[[]]"));
32 |
33 | const [txHash, setTxHash] = useState(null);
34 | const [txReceipt, setTxReceipt] = useState(null);
35 | const [txError, setTxError] = useState(null);
36 |
37 | const getIsEligible = () => {};
38 |
39 | if (!txHash) {
40 | return (
41 | div {
44 | margin-top: 25px;
45 | margin-bottom: 40px;
46 | }
47 | `}
48 | >
49 | {NftxV7.abi
50 | .filter(
51 | (item) =>
52 | item.type === "function" && !item.stateMutability.includes("view")
53 | )
54 | .map((func, i) => (
55 |
56 | {func.inputs.map((input, _i) => (
57 | {
61 | const newFuncParams = JSON.parse(
62 | JSON.stringify(funcParams)
63 | );
64 | if (!newFuncParams[i]) {
65 | newFuncParams[i] = [];
66 | }
67 | newFuncParams[i][_i] = event.target.value;
68 | setFuncParams(newFuncParams);
69 | }}
70 | placeholder={`${input.name} (${input.type})`}
71 | wide={true}
72 | css={`
73 | margin-bottom: 10px;
74 | `}
75 | />
76 | ))}
77 |
107 | ))}
108 |
109 | );
110 | } else if (txHash && !txReceipt) {
111 | return (
112 |
113 |
119 | Transaction in progress...
120 |
121 |
122 |
132 |
133 | );
134 | } else if (txError) {
135 | return (
136 |
137 |
143 | Error occured. Check console.
144 |
145 |
146 | );
147 | } else {
148 | return (
149 |
150 |
156 | Transaction was successful
157 |
163 |
164 |
165 |
166 | );
167 | }
168 | }
169 |
170 | export default ManageFundPanel;
171 |
--------------------------------------------------------------------------------
/src/components/Backend/XStoreEvents.js:
--------------------------------------------------------------------------------
1 | import React, { useRef, useState, useEffect } from "react";
2 | import { DataView } from "@aragon/ui";
3 | import Web3 from "web3";
4 | import { useWallet } from "use-wallet";
5 | import HashField from "../HashField/HashField";
6 | import XStore from "../../contracts/XStore.json";
7 | import addresses from "../../addresses/mainnet.json";
8 |
9 | function XStoreEvents() {
10 | const { account } = useWallet();
11 | const injected = window.ethereum;
12 | const provider =
13 | injected && injected.chainId === "0x1"
14 | ? injected
15 | : `wss://eth-mainnet.ws.alchemyapi.io/v2/${process.env.ALCHEMY_API_KEY}`;
16 |
17 | const { current: web3 } = useRef(new Web3(provider));
18 | const xStore = new web3.eth.Contract(XStore.abi, addresses.xStore);
19 |
20 | const [events, setEvents] = useState([]);
21 | // const [eventTimes, setEventTimes] = useState([]);
22 |
23 | useEffect(() => {
24 | xStore
25 | .getPastEvents("allEvents", { fromBlock: 7664346, toBlock: "latest" })
26 | .then((result) => {
27 | if (result.length > 25) {
28 | const _events = result
29 | .slice(result.length - 25, result.length)
30 | .reverse();
31 | setEvents(_events);
32 | } else {
33 | const _events = result
34 | .reverse()
35 | .slice(0, Math.min(25, result.length));
36 | setEvents(_events);
37 | }
38 | });
39 | }, []);
40 |
41 | return (
42 |
43 |
{
49 | return [
50 | {event}
,
51 | ,
52 | {blockNumber}
,
53 | ];
54 | }}
55 | />
56 |
57 | );
58 | }
59 |
60 | export default XStoreEvents;
61 |
--------------------------------------------------------------------------------
/src/components/Backend/XStoreReadPanel.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef } from "react";
2 | import {
3 | DropDown,
4 | TextInput,
5 | Button,
6 | AddressField,
7 | IconCheck,
8 | } from "@aragon/ui";
9 | import Web3 from "web3";
10 | import { useWallet } from "use-wallet";
11 | import XStore from "../../contracts/XStore.json";
12 | import Loader from "react-loader-spinner";
13 | import HashField from "../HashField/HashField";
14 | import { useFavoriteNFTs } from "../../contexts/FavoriteNFTsContext";
15 | import addresses from "../../addresses/mainnet.json";
16 |
17 | function XStoreReadPanel() {
18 | const { account } = useWallet();
19 |
20 | // const { addFavorite } = useFavoriteNFTs();
21 |
22 | const { current: web3 } = useRef(new Web3(window.ethereum));
23 |
24 | const xStore = new web3.eth.Contract(XStore.abi, addresses.xStore);
25 |
26 | const [funcParams, setFuncParams] = useState(JSON.parse("[[]]"));
27 | const [returnVals, setReturnVals] = useState(JSON.parse("[[]]"));
28 |
29 | return (
30 | div {
33 | margin-top: 25px;
34 | margin-bottom: 40px;
35 | }
36 | `}
37 | >
38 | {XStore.abi
39 | .filter(
40 | (item) =>
41 | item.type === "function" && item.stateMutability.includes("view")
42 | )
43 | .map((func, i) => (
44 |
45 | {func.inputs.map((input, _i) => (
46 | {
53 | const newFuncParams = JSON.parse(JSON.stringify(funcParams));
54 | if (!newFuncParams[i]) {
55 | newFuncParams[i] = [];
56 | }
57 | newFuncParams[i][_i] = event.target.value;
58 | setFuncParams(newFuncParams);
59 | }}
60 | placeholder={`${input.name} (${input.type})`}
61 | wide={true}
62 | />
63 | ))}
64 |
105 | ))}
106 |
107 | );
108 | }
109 |
110 | export default XStoreReadPanel;
111 |
--------------------------------------------------------------------------------
/src/components/CreateD2Panel/CreateD2Panel.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import {
3 | TextInput,
4 | Button,
5 | Header,
6 | textStyle,
7 | DropDown,
8 | Info,
9 | } from "@aragon/ui";
10 |
11 | function CreateD2Panel() {
12 | const [value, setValue] = useState("");
13 |
14 | return (
15 |
20 | This feature is coming soon.
21 |
22 | );
23 | }
24 |
25 | export default CreateD2Panel;
26 |
--------------------------------------------------------------------------------
/src/components/D1FundList/D1FundList.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef, useEffect } from "react";
2 | import { Link, useHistory } from "react-router-dom";
3 | import { useWallet } from "use-wallet";
4 | import PropTypes from "prop-types";
5 | import {
6 | DataView,
7 | ContextMenu,
8 | ContextMenuItem,
9 | Header,
10 | Button,
11 | AddressField,
12 | SidePanel,
13 | IconStarFilled,
14 | IconStar,
15 | IconCircleCheck,
16 | IconCircleMinus,
17 | FloatIndicator,
18 | Info,
19 | } from "@aragon/ui";
20 | import { useFavoriteFunds } from "../../contexts/FavoriteFundsContext";
21 |
22 | import CreateErc20Panel from "../InnerPanels/CreateErc20Panel";
23 | import CreateFundPanel from "../InnerPanels/CreateFundPanel";
24 |
25 | import Web3 from "web3";
26 |
27 | import FundsList from "../FundsList/FundsList";
28 |
29 | function D1FundList({ fundsData, balances }) {
30 | const {
31 | isVaultIdFavorited,
32 | removeFavoriteByVaultId,
33 | addFavorite,
34 | } = useFavoriteFunds();
35 | const history = useHistory();
36 | const { account } = useWallet();
37 | const injected = window.ethereum;
38 | const provider =
39 | injected && injected.chainId === "0x1"
40 | ? injected
41 | : `wss://eth-mainnet.ws.alchemyapi.io/v2/${process.env.ALCHEMY_API_KEY}`;
42 |
43 | const { current: web3 } = useRef(new Web3(provider));
44 |
45 | const [panelTitle, setPanelTitle] = useState("");
46 | const [panelOpened, setPanelOpened] = useState(false);
47 | const [innerPanel, setInnerPanel] = useState();
48 |
49 | const handleClickCreate = () => {
50 | setPanelTitle("Create a D1 Fund (Step 1/2)");
51 | setInnerPanel(
52 | {
54 | setPanelOpened(false);
55 | setTimeout(() => {
56 | setPanelTitle("Create a D1 Fund (Step 2/2)");
57 | setInnerPanel(
58 | {
61 | addFavorite({
62 | ticker: tokenSymbol,
63 | address: tokenAddress,
64 | vaultId: vaultId,
65 | });
66 | setPanelOpened(false);
67 | setTimeout(() => {
68 | window.location.hash = `/fund/${vaultId}`;
69 | }, 400);
70 | }}
71 | />
72 | );
73 | setPanelOpened(true);
74 | }, 500);
75 | }}
76 | />
77 | );
78 | setPanelOpened(true);
79 | };
80 |
81 | return (
82 |
87 |
91 | }
92 | />
93 |
96 | !elem.isD2Vault &&
97 | (elem.verified || isVaultIdFavorited(elem.vaultId))
98 | )}
99 | balances={balances}
100 | />
101 | setPanelOpened(false)}
105 | >
106 | {innerPanel}
107 |
108 |
109 | );
110 | }
111 |
112 | export default D1FundList;
113 |
--------------------------------------------------------------------------------
/src/components/D1FundView/FundEvents.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { DataView } from "@aragon/ui";
3 |
4 | function FundEvents({ events }) {
5 | return (
6 | [, , ]}
10 | />
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/src/components/D1Funds/D1Funds.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { SidePanel } from "@aragon/ui";
3 | import Funds from "../Funds/Funds";
4 | import MintPanel from "../MintPanel/MintPanel";
5 | import RedeemPanel from "../RedeemPanel/RedeemPanel";
6 |
7 | function D1Funds() {
8 | const [panelTitle, setPanelTitle] = useState("");
9 | const [panelOpened, setPanelOpened] = useState(false);
10 | const [innerPanel, setInnerPanel] = useState();
11 |
12 | const handleMintClick = (vaultId, ticker) => {
13 | setPanelTitle(`Mint ${ticker}`);
14 | setPanelOpened(true);
15 | setInnerPanel();
16 | };
17 |
18 | const handleRedeemClick = (vaultId, ticker) => {
19 | setPanelTitle(`Redeem ${ticker}`);
20 | setPanelOpened(true);
21 | setInnerPanel();
22 | };
23 |
24 | return (
25 |
26 |
37 |
38 |
49 |
50 |
56 |
65 |
66 |
76 |
82 | setPanelOpened(false)}
86 | >
87 | {innerPanel}
88 |
89 |
90 | );
91 | }
92 |
93 | export default D1Funds;
94 |
--------------------------------------------------------------------------------
/src/components/FundIcon/FundIcon.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import { GU, EthIdenticon } from "@aragon/ui";
4 | import { network } from "../../environment";
5 | import { getKnownFunds } from "../../known-funds";
6 | import { EthereumAddressType } from "../../prop-types";
7 |
8 | function FundIcon({ fundAddress, size }) {
9 | const knownOrg = getKnownFunds(network.type, fundAddress);
10 | const knownOrgImage = knownOrg && knownOrg.image;
11 |
12 | return (
13 |
25 | {knownOrgImage ? (
26 |

33 | ) : (
34 |
40 | )}
41 |
42 | );
43 | }
44 | FundIcon.propTypes = {
45 | fundAddress: EthereumAddressType.isRequired,
46 | size: PropTypes.number,
47 | };
48 | FundIcon.defaultProps = {
49 | size: 3 * GU,
50 | };
51 |
52 | export default FundIcon;
53 |
--------------------------------------------------------------------------------
/src/components/FundView/FundView.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from "react";
2 | import { useWallet } from "use-wallet";
3 | import { useLocation, Link } from "react-router-dom";
4 | import Web3 from "web3";
5 | import XStore from "../../contracts/XStore.json";
6 | import addresses from "../../addresses/mainnet.json";
7 | // import D1FundViewOld from "../D1FundView/D1FundView_Old";
8 | import D1FundView from "../D1FundView/D1FundView";
9 | import D2FundView from "../D2FundView/D2FundView";
10 | // import D2FundView from '../D2FundView/D2FundView';
11 | import { Button, DataView, textStyle } from "@aragon/ui";
12 |
13 | function FundView({ fundsData, balances }) {
14 | console.log("balances1b", balances);
15 | const { account } = useWallet();
16 | const injected = window.ethereum;
17 | const provider =
18 | injected && injected.chainId === "0x1"
19 | ? injected
20 | : `wss://eth-mainnet.ws.alchemyapi.io/v2/${process.env.ALCHEMY_API_KEY}`;
21 | const { current: web3 } = useRef(new Web3(provider));
22 |
23 | const xStore = new web3.eth.Contract(XStore.abi, addresses.xStore);
24 |
25 | const [vaultId, setVaultId] = useState(null);
26 | const [invalidVid, setInvalidVid] = useState(false);
27 | const [degree, setDegree] = useState(null);
28 | const location = useLocation();
29 |
30 | useEffect(() => {
31 | if (location) {
32 | const _vaultId = location.pathname.split("/")[2];
33 | if (isNaN(parseInt(_vaultId))) {
34 | setInvalidVid(true);
35 | }
36 | setVaultId(_vaultId);
37 | }
38 | }, [location]);
39 |
40 | useEffect(() => {
41 | if (xStore && vaultId) {
42 | xStore.methods
43 | .isD2Vault(vaultId)
44 | .call({ from: account })
45 | .then((retVal) => {
46 | setDegree(retVal ? 2 : 1);
47 | });
48 | }
49 | }, [xStore, vaultId, account]);
50 |
51 | if (invalidVid) {
52 | return Invalid Fund ID
;
53 | } else if (degree === 1) {
54 | return ;
55 | } else if (degree === 2) {
56 | return ;
57 | } else {
58 | return (
59 |
64 |
72 |
79 |
85 |
98 | Funds
99 |
100 |
{" "}
101 |
107 |
113 | ›
114 | {" "}
115 |
120 | Fund #{vaultId}
121 |
122 |
123 |
124 |
125 |
126 |
div {
129 | padding-bottom: 20px;
130 | }
131 | `}
132 | >
133 |
}
138 | />
139 |
140 |
141 | );
142 | }
143 | }
144 |
145 | export default FundView;
146 |
--------------------------------------------------------------------------------
/src/components/Funds/Funds.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import { DataView, ContextMenu, ContextMenuItem, Header } from "@aragon/ui";
4 |
5 | function Funds({ title, entries, handleMint, handleRedeem }) {
6 | return (
7 |
8 | {title &&
}
9 |
{
13 | return [
14 | {ticker}
,
15 | N/A
,
16 | {supply}
,
17 | N/A
,
18 | N/A
,
19 | ];
20 | }}
21 | renderEntryActions={(entry, index) => {
22 | return (
23 |
24 | handleMint(entry.vaultId, entry.ticker)}
26 | >
27 | Mint
28 |
29 | handleRedeem(entry.vaultId, entry.ticker)}
31 | >
32 | Redeem
33 |
34 |
35 | );
36 | }}
37 | />
38 |
39 | );
40 | }
41 |
42 | export const FundType = PropTypes.shape({
43 | ticker: PropTypes.string,
44 | supply: PropTypes.string,
45 | vaultId: PropTypes.string,
46 | });
47 |
48 | Funds.propTypes = {
49 | title: PropTypes.string,
50 | entries: PropTypes.arrayOf(FundType),
51 | handleMint: PropTypes.func,
52 | handleRedeem: PropTypes.func,
53 | };
54 |
55 | export default Funds;
56 |
--------------------------------------------------------------------------------
/src/components/FundsIndex/FundsIndex.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef, useEffect } from "react";
2 | import { Link, useHistory } from "react-router-dom";
3 | import { useWallet } from "use-wallet";
4 | import PropTypes from "prop-types";
5 | import {
6 | DataView,
7 | ContextMenu,
8 | ContextMenuItem,
9 | Header,
10 | Button,
11 | AddressField,
12 | SidePanel,
13 | IconStarFilled,
14 | IconStar,
15 | IconCircleCheck,
16 | IconCircleMinus,
17 | FloatIndicator,
18 | Info,
19 | DropDown,
20 | } from "@aragon/ui";
21 | import { useFavoriteFunds } from "../../contexts/FavoriteFundsContext";
22 |
23 | import CreateErc20Panel from "../InnerPanels/CreateErc20Panel";
24 | import CreateFundPanel from "../InnerPanels/CreateFundPanel";
25 |
26 | import Web3 from "web3";
27 |
28 | import FundsList from "../FundsList/FundsList";
29 |
30 | function FundsIndex({ fundsData, balances, getSelection, setSelection }) {
31 | const {
32 | isVaultIdFavorited,
33 | removeFavoriteByVaultId,
34 | addFavorite,
35 | } = useFavoriteFunds();
36 | const history = useHistory();
37 | const { account } = useWallet();
38 | const injected = window.ethereum;
39 | const provider =
40 | injected && injected.chainId === "0x1"
41 | ? injected
42 | : `wss://eth-mainnet.ws.alchemyapi.io/v2/${process.env.ALCHEMY_API_KEY}`;
43 |
44 | const { current: web3 } = useRef(new Web3(provider));
45 |
46 | const featuredVaultIds = [16];
47 |
48 | const getVisibleFundsData = () => {
49 | if (!fundsData) return null;
50 | const _visibleFunds = fundsData
51 | .filter((elem) => {
52 | let visible1;
53 | let visible2;
54 | if (getSelection(0) === 0) {
55 | visible1 = featuredVaultIds.includes(elem.vaultId);
56 | } else if (getSelection(0) === 1) {
57 | visible1 = !featuredVaultIds.includes(elem.vaultId);
58 | } else {
59 | visible1 = true;
60 | }
61 | if (getSelection(1) === 0) {
62 | visible2 = !elem.isD2Vault;
63 | } else if (getSelection(1) === 1) {
64 | visible2 = elem.isD2Vault;
65 | } else {
66 | visible2 = true;
67 | }
68 | return visible1 && visible2;
69 | })
70 | .filter(
71 | (elem) =>
72 | /* (elem.verified && elem.isFinalized) || */
73 | elem.verified || isVaultIdFavorited(elem.vaultId)
74 | );
75 | return _visibleFunds;
76 | };
77 |
78 | return (
79 |
84 |
94 | setSelection(0, newIndex)}
98 | />
99 | setSelection(1, newIndex)}
103 | css={`
104 | margin-left: 15px;
105 | `}
106 | />
107 |
108 | }
109 | />
110 |
111 |
112 | );
113 | }
114 |
115 | export default FundsIndex;
116 |
--------------------------------------------------------------------------------
/src/components/HashField/HashField.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { AddressField } from "@aragon/ui";
3 |
4 | function HashField({ hash }) {
5 | return (
6 | div > div:first-child {
10 | }
11 | `}
12 | >
13 |
24 | #
25 |
26 |
27 |
28 |
29 | );
30 | }
31 |
32 | export default HashField;
33 |
--------------------------------------------------------------------------------
/src/components/Header/Header.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import { textStyle, useTheme, GU } from "@aragon/ui";
4 |
5 | function Header({
6 | title,
7 | subtitle,
8 | calltoaction,
9 | topSpacing = 10 * GU,
10 | bottomSpacing = 7 * GU,
11 | }) {
12 | const theme = useTheme();
13 | return (
14 |
20 |
28 | {title}
29 |
30 | {subtitle && (
31 |
37 | {subtitle}
38 |
39 | )}
40 | {calltoaction && (
41 |
50 | )}
51 |
52 | );
53 | }
54 |
55 | Header.propTypes = {
56 | title: PropTypes.node.isRequired,
57 | subtitle: PropTypes.node,
58 | calltoaction: PropTypes.node,
59 | topSpacing: PropTypes.number.isRequired,
60 | bottomSpacing: PropTypes.number.isRequired,
61 | };
62 |
63 | Header.defaultProps = {
64 | topSpacing: 10 * GU,
65 | bottomSpacing: 7 * GU,
66 | };
67 |
68 | export default Header;
69 |
--------------------------------------------------------------------------------
/src/components/HomeButton/HomeButton.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import { Button, IconHome, GU, RADIUS, useTheme } from "@aragon/ui";
4 |
5 | function HomeButton({ onClick, ...props }) {
6 | const theme = useTheme();
7 | return (
8 |
21 | }
24 | label="Settings"
25 | size="medium"
26 | onClick={onClick}
27 | disabled={false}
28 | />
29 |
30 | );
31 | }
32 |
33 | HomeButton.propTypes = {
34 | onClick: PropTypes.func,
35 | };
36 |
37 | HomeButton.defaultProps = {
38 | onClick: () => {
39 | window.location.hash = "/";
40 | },
41 | };
42 |
43 | export default HomeButton;
44 |
--------------------------------------------------------------------------------
/src/components/InnerPanels/ApproveNftsPanel.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef, useEffect } from "react";
2 | import {
3 | DropDown,
4 | TextInput,
5 | Button,
6 | AddressField,
7 | IconCheck,
8 | } from "@aragon/ui";
9 | import Web3 from "web3";
10 | import { useWallet } from "use-wallet";
11 | import XStore from "../../contracts/XStore.json";
12 | import IErc721 from "../../contracts/IERC721.json";
13 | import Loader from "react-loader-spinner";
14 | import HashField from "../HashField/HashField";
15 | import { useFavoriteNFTs } from "../../contexts/FavoriteNFTsContext";
16 | import addresses from "../../addresses/mainnet.json";
17 |
18 | function ApproveNftsPanel({ vaultId, ticker, closePanel }) {
19 | const { account } = useWallet();
20 | const injected = window.ethereum;
21 | const provider =
22 | injected && injected.chainId === "0x1"
23 | ? injected
24 | : `wss://eth-mainnet.ws.alchemyapi.io/v2/${process.env.ALCHEMY_API_KEY}`;
25 |
26 | const { current: web3 } = useRef(new Web3(provider));
27 |
28 | const [tokenId, setTokenId] = useState("");
29 |
30 | const [txStatus, setTxStatus] = useState(null);
31 | const [txHash, setTxHash] = useState(null);
32 | const [txReceipt, setTxReceipt] = useState(null);
33 | const [txError, setTxError] = useState(null);
34 |
35 | const [nftAddress, setNftAddress] = useState("");
36 |
37 | const xStore = new web3.eth.Contract(XStore.abi, addresses.xStore);
38 |
39 | useEffect(() => {
40 | xStore.methods
41 | .nftAddress(vaultId)
42 | .call({ from: account })
43 | .then((retVal) => {
44 | setNftAddress(retVal);
45 | });
46 | }, []);
47 |
48 | const handleApproveAll = () => {
49 | const nft = new web3.eth.Contract(IErc721.abi, nftAddress);
50 | nft.methods
51 | .setApprovalForAll(addresses.nftxProxy, true)
52 | .send(
53 | {
54 | from: account,
55 | },
56 | (error, txHash) => {}
57 | )
58 | .on("error", (error) => setTxError(error))
59 | .on("transactionHash", (txHash) => setTxHash(txHash))
60 | .on("receipt", (receipt) => {
61 | setTxReceipt(receipt);
62 | });
63 | };
64 |
65 | const handleApproveIndividual = () => {
66 | const nft = new web3.eth.Contract(IErc721.abi, nftAddress);
67 | nft.methods
68 | .approve(addresses.nftxProxy, tokenId)
69 | .send(
70 | {
71 | from: account,
72 | },
73 | (error, txHash) => {}
74 | )
75 | .on("error", (error) => setTxError(error))
76 | .on("transactionHash", (txHash) => setTxHash(txHash))
77 | .on("receipt", (receipt) => {
78 | setTxReceipt(receipt);
79 | });
80 | };
81 |
82 | const handleViewNFT = () => {
83 | closePanel();
84 | };
85 |
86 | if (!txHash) {
87 | return (
88 |
93 | setTokenId(event.target.value)}
96 | placeholder="Token ID (e.g. 42)"
97 | wide={true}
98 | css={`
99 | margin-bottom: 10px;
100 | `}
101 | />
102 |
103 |
109 |
110 |
119 |
120 | );
121 | } else if (txHash && !txReceipt) {
122 | return (
123 |
124 |
130 | Transaction in progress...
131 |
132 |
133 |
143 |
144 | );
145 | } else {
146 | return (
147 |
148 |
154 | Fund minted succesfully
155 |
161 |
162 |
163 |
164 | );
165 | }
166 | }
167 |
168 | export default ApproveNftsPanel;
169 |
--------------------------------------------------------------------------------
/src/components/InnerPanels/CreateErc20Panel.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef } from "react";
2 | import {
3 | DropDown,
4 | TextInput,
5 | Button,
6 | AddressField,
7 | IconCheck,
8 | } from "@aragon/ui";
9 | import Web3 from "web3";
10 | import { useWallet } from "use-wallet";
11 | import erc20 from "../../contracts/XToken.json";
12 | import Loader from "react-loader-spinner";
13 | import HashField from "../HashField/HashField";
14 | import addresses from "../../addresses/mainnet.json";
15 |
16 | function CreateErc20Panel({ onContinue }) {
17 | const { account } = useWallet();
18 |
19 | const { current: web3 } = useRef(new Web3(window.ethereum));
20 |
21 | const [name, setName] = useState("");
22 | const [symbol, setSymbol] = useState("");
23 |
24 | const [txStatus, setTxStatus] = useState(null);
25 | const [txHash, setTxHash] = useState(null);
26 | const [txReceipt, setTxReceipt] = useState(null);
27 | const [txError, setTxError] = useState(null);
28 |
29 | const handleDeploy = () => {
30 | const tokenContract = new web3.eth.Contract(erc20.abi);
31 | tokenContract
32 | .deploy({
33 | data: erc20.bytecode,
34 | arguments: [name, symbol, addresses.nftxProxy],
35 | })
36 | .send(
37 | {
38 | from: account,
39 | },
40 | (error, txHash) => {}
41 | )
42 | .on("error", (error) => setTxError(error))
43 | .on("transactionHash", (txHash) => setTxHash(txHash))
44 | .on("receipt", (receipt) => {
45 | setTxReceipt(receipt);
46 | });
47 | };
48 |
49 | if (!txHash) {
50 | return (
51 |
52 |
58 | Deploy an ERC20 fund token
59 |
60 |
setName(event.target.value)}
63 | placeholder="Name (e.g. Punk-Basic)"
64 | wide={true}
65 | css={`
66 | margin-bottom: 10px;
67 | `}
68 | />
69 | setSymbol(event.target.value)}
72 | placeholder="Symbol (e.g. PUNK-BASIC)"
73 | wide={true}
74 | css={`
75 | margin-bottom: 15px;
76 | `}
77 | />
78 |
84 |
85 | );
86 | } else if (txHash && !txReceipt) {
87 | return (
88 |
89 |
95 | Transaction in progress...
96 |
97 |
98 |
108 |
109 | );
110 | } else if (txError) {
111 | return (
112 |
113 |
119 | Error occured. Check console.
120 |
121 |
122 | );
123 | } else {
124 | return (
125 |
126 |
132 | Contract deployed succesfully
133 |
139 |
140 |
146 | );
147 | }
148 | }
149 |
150 | export default CreateErc20Panel;
151 |
--------------------------------------------------------------------------------
/src/components/InnerPanels/CreateFundPanel.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef } from "react";
2 | import {
3 | DropDown,
4 | TextInput,
5 | Button,
6 | AddressField,
7 | IconCheck,
8 | } from "@aragon/ui";
9 | import Web3 from "web3";
10 | import { useWallet } from "use-wallet";
11 | import Nftx from "../../contracts/NFTX.json";
12 | import Loader from "react-loader-spinner";
13 | import HashField from "../HashField/HashField";
14 | import addresses from "../../addresses/mainnet.json";
15 |
16 | function CreateFundPanel({ tokenAddress, onContinue }) {
17 | const { account } = useWallet();
18 |
19 | const { current: web3 } = useRef(new Web3(window.ethereum));
20 |
21 | const [nftAddress, setNftAddress] = useState("");
22 |
23 | const [txHash, setTxHash] = useState(null);
24 | const [txReceipt, setTxReceipt] = useState(null);
25 | const [txError, setTxError] = useState(null);
26 |
27 | const handleCreate = () => {
28 | const nftx = new web3.eth.Contract(Nftx.abi, addresses.nftxProxy);
29 | // window.nftx = nftx;
30 | nftx.methods
31 | .createVault(tokenAddress, nftAddress, false)
32 | .send(
33 | {
34 | from: account,
35 | },
36 | (error, txHash) => {}
37 | )
38 | .on("error", (error) => setTxError(error))
39 | .on("transactionHash", (txHash) => setTxHash(txHash))
40 | .on("receipt", (receipt) => {
41 | setTxReceipt(receipt);
42 | });
43 | };
44 |
45 | if (!txHash) {
46 | return (
47 |
52 | setNftAddress(event.target.value)}
55 | placeholder="NFT contract address (e.g. 0x0bf7...D63a)"
56 | wide={true}
57 | css={`
58 | margin-bottom: 15px;
59 | `}
60 | />
61 |
62 |
68 |
69 | );
70 | } else if (txHash && !txReceipt) {
71 | return (
72 |
73 |
79 | Transaction in progress...
80 |
81 |
82 |
92 |
93 | );
94 | } else if (txError) {
95 | return (
96 |
97 |
103 | Error occured. Check console.
104 |
105 |
106 | );
107 | } else {
108 | return (
109 |
110 |
116 | Fund created succesfully
117 |
123 |
124 |
132 | );
133 | }
134 | }
135 |
136 | export default CreateFundPanel;
137 |
--------------------------------------------------------------------------------
/src/components/InnerPanels/CreateNftPanel.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef } from "react";
2 | import {
3 | DropDown,
4 | TextInput,
5 | Button,
6 | AddressField,
7 | IconCheck,
8 | } from "@aragon/ui";
9 | import Web3 from "web3";
10 | import { useWallet } from "use-wallet";
11 | import erc721 from "../../contracts/ERC721Public.json";
12 | import Loader from "react-loader-spinner";
13 | import HashField from "../HashField/HashField";
14 | import { useFavoriteNFTs } from "../../contexts/FavoriteNFTsContext";
15 |
16 | function CreateNftPanel({ onContinue }) {
17 | const { account } = useWallet();
18 |
19 | // const { addFavorite } = useFavoriteNFTs();
20 |
21 | const { current: web3 } = useRef(new Web3(window.ethereum));
22 |
23 | const [name, setName] = useState("");
24 | const [symbol, setSymbol] = useState("");
25 | const [minTokenId, setMinTokenId] = useState("");
26 | const [maxTokenId, setMaxTokenId] = useState("");
27 |
28 | const [txStatus, setTxStatus] = useState(null);
29 | const [txHash, setTxHash] = useState(null);
30 | const [txReceipt, setTxReceipt] = useState(null);
31 | const [txError, setTxError] = useState(null);
32 |
33 | const handleDeploy = () => {
34 | const nftContract = new web3.eth.Contract(erc721.abi);
35 | nftContract
36 | .deploy({
37 | data: erc721.bytecode,
38 | arguments: [name, symbol, minTokenId, maxTokenId],
39 | })
40 | .send(
41 | {
42 | from: account,
43 | },
44 | (error, txHash) => {}
45 | )
46 | .on("error", (error) => setTxError(error))
47 | .on("transactionHash", (txHash) => setTxHash(txHash))
48 | .on("receipt", (receipt) => {
49 | setTxReceipt(receipt);
50 | });
51 | };
52 |
53 | const handleViewNFT = () => {
54 | // addFavorite({ name: name, address: txReceipt.contractAddress });
55 | onContinue(name, txReceipt.contractAddress);
56 | };
57 |
58 | if (!txHash) {
59 | return (
60 |
61 | setName(event.target.value)}
64 | placeholder="Name (e.g. CryptoGems)"
65 | wide={true}
66 | css={`
67 | margin-top: 20px;
68 | margin-bottom: 10px;
69 | `}
70 | />
71 | setSymbol(event.target.value)}
74 | placeholder="Symbol (e.g. GEMS)"
75 | wide={true}
76 | css={`
77 | margin-bottom: 10px;
78 | `}
79 | />
80 | setMinTokenId(event.target.value)}
83 | placeholder="Minimum token ID (e.g. 0)"
84 | wide={true}
85 | css={`
86 | margin-bottom: 10px;
87 | `}
88 | />
89 | setMaxTokenId(event.target.value)}
92 | placeholder="Maximum token ID (e.g. 9999)"
93 | wide={true}
94 | css={`
95 | margin-bottom: 20px;
96 | `}
97 | />
98 |
104 |
105 | );
106 | } else if (txHash && !txReceipt) {
107 | return (
108 |
109 |
115 | Transaction in progress...
116 |
117 |
118 |
128 |
129 | );
130 | } else if (txError) {
131 | return (
132 |
133 |
139 | Error occured. Check console.
140 |
141 |
142 | );
143 | } else {
144 | return (
145 |
146 |
152 | Contract deployed succesfully
153 |
159 |
160 |
161 |
162 | );
163 | }
164 | }
165 |
166 | export default CreateNftPanel;
167 |
--------------------------------------------------------------------------------
/src/components/InnerPanels/MintNftPanel.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef } from "react";
2 | import {
3 | DropDown,
4 | TextInput,
5 | Button,
6 | AddressField,
7 | IconCheck,
8 | } from "@aragon/ui";
9 | import Web3 from "web3";
10 | import { useWallet } from "use-wallet";
11 | import erc721Pub from "../../contracts/ERC721Public.json";
12 | import Loader from "react-loader-spinner";
13 | import HashField from "../HashField/HashField";
14 | import { useFavoriteNFTs } from "../../contexts/FavoriteNFTsContext";
15 |
16 | function MintNftPanel({ contractAddress, onContinue }) {
17 | const { account } = useWallet();
18 |
19 | const { addFavorite } = useFavoriteNFTs();
20 |
21 | const { current: web3 } = useRef(new Web3(window.ethereum));
22 |
23 | const [tokenId, setTokenId] = useState("");
24 | const [recipient, setRecipient] = useState("");
25 |
26 | const [txHash, setTxHash] = useState(null);
27 | const [txReceipt, setTxReceipt] = useState(null);
28 | const [txError, setTxError] = useState(null);
29 |
30 | const handleMint = (tokenId, recipient) => {
31 | const nftContract = new web3.eth.Contract(erc721Pub.abi, contractAddress);
32 | nftContract.methods
33 | .mint(tokenId, recipient)
34 | .send(
35 | {
36 | from: account,
37 | },
38 | (error, txHash) => {}
39 | )
40 | .on("error", (error) => setTxError(error))
41 | .on("transactionHash", (txHash) => setTxHash(txHash))
42 | .on("receipt", (receipt) => {
43 | setTxReceipt(receipt);
44 | console.log(receipt);
45 | });
46 | };
47 |
48 | const handleViewNFT = () => {
49 | onContinue();
50 | };
51 |
52 | if (!txHash) {
53 | return (
54 |
59 | setTokenId(event.target.value)}
62 | placeholder="Token ID (e.g. 42)"
63 | wide={true}
64 | css={`
65 | margin-bottom: 10px;
66 | `}
67 | />
68 | setRecipient(event.target.value)}
71 | placeholder="Recipient (e.g. 0x0bf7...D63a)"
72 | wide={true}
73 | css={`
74 | margin-bottom: 20px;
75 | `}
76 | />
77 |
78 |
85 | );
86 | } else if (txHash && !txReceipt) {
87 | return (
88 |
89 |
95 | Transaction in progress...
96 |
97 |
98 |
108 |
109 | );
110 | } else if (txError) {
111 | return (
112 |
113 |
119 | Error occured. Check console.
120 |
121 |
122 | );
123 | } else {
124 | return (
125 |
126 |
132 | NFT minted succesfully
133 |
139 |
140 |
141 |
142 | );
143 | }
144 | }
145 |
146 | export default MintNftPanel;
147 |
--------------------------------------------------------------------------------
/src/components/InnerPanels/TransferNftPanel.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef } from "react";
2 | import {
3 | DropDown,
4 | TextInput,
5 | Button,
6 | AddressField,
7 | IconCheck,
8 | } from "@aragon/ui";
9 | import Web3 from "web3";
10 | import { useWallet } from "use-wallet";
11 | import erc721Pub from "../../contracts/ERC721Public.json";
12 | import Loader from "react-loader-spinner";
13 | import HashField from "../HashField/HashField";
14 | import { useFavoriteNFTs } from "../../contexts/FavoriteNFTsContext";
15 |
16 | function TransferNftPanel({ contractAddress, onContinue }) {
17 | const { account } = useWallet();
18 |
19 | const { addFavorite } = useFavoriteNFTs();
20 |
21 | const { current: web3 } = useRef(new Web3(window.ethereum));
22 |
23 | const [sender, setSender] = useState(account);
24 | const [tokenId, setTokenId] = useState("");
25 | const [recipient, setRecipient] = useState("");
26 |
27 | const [txStatus, setTxStatus] = useState(null);
28 | const [txHash, setTxHash] = useState(null);
29 | const [txReceipt, setTxReceipt] = useState(null);
30 | const [txError, setTxError] = useState(null);
31 |
32 | const handleTransfer = () => {
33 | const nftContract = new web3.eth.Contract(erc721Pub.abi, contractAddress);
34 | nftContract.methods
35 | .transferFrom(sender, recipient, tokenId)
36 | .send(
37 | {
38 | from: account,
39 | },
40 | (error, txHash) => {}
41 | )
42 | .on("error", (error) => setTxError(error))
43 | .on("transactionHash", (txHash) => setTxHash(txHash))
44 | .on("receipt", (receipt) => {
45 | setTxReceipt(receipt);
46 | console.log(receipt);
47 | });
48 | };
49 |
50 | if (!txHash) {
51 | return (
52 |
57 | setSender(event.target.value)}
60 | placeholder="Sender (e.g. 0x0bf7...D63)"
61 | wide={true}
62 | css={`
63 | margin-bottom: 10px;
64 | `}
65 | />
66 | setTokenId(event.target.value)}
69 | placeholder="Token ID (e.g. 42)"
70 | wide={true}
71 | css={`
72 | margin-bottom: 10px;
73 | `}
74 | />
75 | setRecipient(event.target.value)}
78 | placeholder="Recipient (e.g. 0x0bf7...D63)"
79 | wide={true}
80 | css={`
81 | margin-bottom: 20px;
82 | `}
83 | />
84 |
90 |
91 | );
92 | } else if (txHash && !txReceipt) {
93 | return (
94 |
95 |
101 | Transaction in progress...
102 |
103 |
104 |
114 |
115 | );
116 | } else if (txError) {
117 | return (
118 |
119 |
125 | Error occured. Check console.
126 |
127 |
128 | );
129 | } else {
130 | return (
131 |
132 |
138 | NFT transferred successfully
139 |
145 |
146 |
147 |
148 | );
149 | }
150 | }
151 |
152 | export default TransferNftPanel;
153 |
--------------------------------------------------------------------------------
/src/components/Landing/Landing.js:
--------------------------------------------------------------------------------
1 | import React, { useMemo, useState, useCallback } from "react";
2 | import { Split, DropDown, SidePanel, Info } from "@aragon/ui";
3 | import { network } from "../../environment";
4 | import WelcomeAction from "../WelcomeAction/WelcomeAction";
5 | import Suggestions from "./Suggestions/Suggestions";
6 | import { useSuggestedFunds } from "../../suggested-funds";
7 | // import CreateD1PanelA from "../CreateD1Panel/CreateD1PanelA";
8 | import CreateD2Panel from "../CreateD2Panel/CreateD2Panel";
9 | import CreateErc20Panel from "../InnerPanels/CreateErc20Panel";
10 | import CreateFundPanel from "../InnerPanels/CreateFundPanel";
11 | import { useFavoriteFunds } from "../../contexts/FavoriteFundsContext";
12 |
13 | import actionCreate from "./assets/action-create.png";
14 | import actionOpen from "./assets/action-open.png";
15 |
16 | function Landing({ selectorNetworks }) {
17 | const selectorNetworksSorted = useMemo(() => {
18 | return selectorNetworks
19 | .map(([type, name, url]) => ({ type, name, url }))
20 | .sort((a, b) => {
21 | if (b.type === network.type) return 1;
22 | if (a.type === network.type) return -1;
23 | return 0;
24 | });
25 | }, [selectorNetworks]);
26 |
27 | const [createError, setCreateError] = useState([null]);
28 |
29 | const suggestedFunds = useSuggestedFunds();
30 |
31 | const [panelTitle, setPanelTitle] = useState("");
32 | const [panelOpened, setPanelOpened] = useState(false);
33 | const [innerPanel, setInnerPanel] = useState();
34 |
35 | const {
36 | favoriteFunds,
37 | removeFavoriteByAddress,
38 | addFavorite,
39 | } = useFavoriteFunds();
40 |
41 | const handleCreate = useCallback(
42 | (degree) => {
43 | /* // reset the creation state
44 | saveTemplateState({}) */
45 | /* const requirementsError = validateCreationRequirements(
46 | account,
47 | balance,
48 | isContractAccount
49 | )
50 | setRequirementsError(requirementsError)
51 |
52 | // Account not connected
53 | if (requirementsError[0] === 'no-account') {
54 | setConnectIntent('create')
55 | setConnectModalOpened(true)
56 | return
57 | }
58 |
59 | // No error, we can go to create straight away
60 | if (requirementsError[0] === null) {
61 | goToCreate()
62 | } */
63 | setPanelTitle(
64 | degree === 1 ? "Create a D1 Fund (Step 1/2)" : "Create a D2 Fund"
65 | );
66 | setInnerPanel(
67 | degree === 1 ? (
68 | {
70 | setPanelOpened(false);
71 | setTimeout(() => {
72 | setPanelTitle("Create a D1 Fund (Step 2/2)");
73 | setInnerPanel(
74 | {
77 | addFavorite({
78 | ticker: tokenSymbol,
79 | address: tokenAddress,
80 | vaultId: vaultId,
81 | });
82 | setPanelOpened(false);
83 | setTimeout(() => {
84 | if (window.location.hash !== "/") {
85 | window.location.hash = "/";
86 | }
87 | }, 400);
88 | }}
89 | />
90 | );
91 | setPanelOpened(true);
92 | }, 500);
93 | }}
94 | />
95 | ) : (
96 |
97 | )
98 | );
99 | setPanelOpened(true);
100 | },
101 | [
102 | /* account, balance, goToCreate, isContractAccount */
103 | ]
104 | );
105 |
106 | return (
107 |
112 |
115 | network.name)}
117 | placeholder={selectorNetworksSorted[0].name}
118 | onChange={() => console.log("TODO")}
119 | disabled={false}
120 | wide
121 | />
122 | handleCreate(1)}
127 | hasError={
128 | createError[0] !== null && createError[0] !== "no-account"
129 | }
130 | />
131 | handleCreate(2)}
136 | hasError={
137 | createError[0] !== null && createError[0] !== "no-account"
138 | }
139 | />
140 |
141 | }
142 | secondary={}
143 | />
144 | setPanelOpened(false)}
148 | >
149 | {innerPanel}
150 |
151 |
152 | );
153 | }
154 |
155 | export default Landing;
156 |
--------------------------------------------------------------------------------
/src/components/Landing/Suggestions/FavoritesMenu/FavoritesMenu.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import { useTheme } from "@aragon/ui";
4 | import FavoritesMenuItem from "./FavoritesMenuItem";
5 |
6 | function FavoritesMenu({ items, onActivate, onFavoriteUpdate, disabled }) {
7 | const theme = useTheme();
8 | return (
9 |
16 | {items.map((item) => (
17 | -
25 |
36 |
37 | ))}
38 |
39 | );
40 | }
41 |
42 | FavoritesMenu.propTypes = {
43 | items: PropTypes.arrayOf(
44 | PropTypes.shape({
45 | favorited: PropTypes.bool,
46 | vaultId: PropTypes.number,
47 | image: PropTypes.node,
48 | secondary: PropTypes.node,
49 | }).isRequired
50 | ),
51 |
52 | // when the favorited status of an item changes
53 | onFavoriteUpdate: PropTypes.func.isRequired,
54 |
55 | // when the item itself gets clicked
56 | onActivate: PropTypes.func.isRequired,
57 |
58 | disabled: PropTypes.bool,
59 | };
60 |
61 | export default FavoritesMenu;
62 |
--------------------------------------------------------------------------------
/src/components/Landing/Suggestions/FavoritesMenu/FavoritesMenuItem.js:
--------------------------------------------------------------------------------
1 | import React, { useCallback } from "react";
2 | import PropTypes from "prop-types";
3 | import { GU, IconStar, IconStarFilled, textStyle, useTheme } from "@aragon/ui";
4 | import FavoritesMenuItemButton from "./FavoritesMenuItemButton";
5 |
6 | function FavoritesMenuItem({
7 | favorited,
8 | vaultId,
9 | address,
10 | ticker,
11 | image,
12 | secondary,
13 | onActivate,
14 | onFavoriteUpdate,
15 | disabled,
16 | }) {
17 | const theme = useTheme();
18 |
19 | const handleActivationClick = useCallback(() => {
20 | if (!disabled) {
21 | onActivate(vaultId);
22 | }
23 | }, [vaultId, onActivate]);
24 |
25 | const handleFavoriteClick = useCallback(() => {
26 | onFavoriteUpdate(vaultId, address, ticker, !favorited);
27 | }, [onFavoriteUpdate, vaultId, favorited]);
28 |
29 | return (
30 |
36 |
46 |
53 |
60 | {image}
61 |
62 |
71 |
79 | {ticker}
80 |
81 | {secondary && (
82 |
88 | {secondary}
89 |
90 | )}
91 |
92 |
93 |
94 |
95 | {favorited ? : }
96 |
97 |
98 | );
99 | }
100 |
101 | // FavoritesMenuItem.propTypes = {
102 | // favorited: PropTypes.bool,
103 | // vaultId: PropTypes.string.isRequired,
104 | // image: PropTypes.node,
105 | // name: PropTypes.string.isRequired,
106 | // onActivate: PropTypes.func.isRequired,
107 | // onFavoriteUpdate: PropTypes.func.isRequired,
108 | // secondary: PropTypes.string,
109 | // disabled: PropTypes.bool,
110 | // };
111 |
112 | export default FavoritesMenuItem;
113 |
--------------------------------------------------------------------------------
/src/components/Landing/Suggestions/FavoritesMenu/FavoritesMenuItemButton.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { ButtonBase, GU, useTheme, textStyle } from "@aragon/ui";
3 |
4 | function FavoritesMenuItemButton({ disabled, ...props }) {
5 | const theme = useTheme();
6 | return (
7 |
24 | );
25 | }
26 |
27 | export default FavoritesMenuItemButton;
28 |
--------------------------------------------------------------------------------
/src/components/Landing/Suggestions/Suggestions.js:
--------------------------------------------------------------------------------
1 | import React, { useCallback } from "react";
2 | import PropTypes from "prop-types";
3 | import { Box } from "@aragon/ui";
4 | import FavoritesMenu from "./FavoritesMenu/FavoritesMenu";
5 | import FundIcon from "../../FundIcon/FundIcon";
6 | import { useFavoriteFunds } from "../../../contexts/FavoriteFundsContext";
7 | import { network } from "../../../environment";
8 | import { getKnownFunds } from "../../../known-funds";
9 | import { addressesEqual } from "../../../web3-utils";
10 |
11 | function Suggestions({ suggestedFunds }) {
12 | const {
13 | isAddressFavorited,
14 | removeFavoriteByVaultId,
15 | addFavorite,
16 | } = useFavoriteFunds();
17 |
18 | const updateFavorite = useCallback(
19 | (vaultId, address, ticker, favorite) => {
20 | if (favorite) {
21 | addFavorite({ vaultId, address, ticker });
22 | } else {
23 | removeFavoriteByVaultId(vaultId);
24 | }
25 | },
26 | [addFavorite, removeFavoriteByVaultId, suggestedFunds]
27 | );
28 |
29 | const goToFund = (vaultId) => {
30 | window.location.hash = `/fund/${vaultId}`;
31 | };
32 |
33 | const openOrg = useCallback(
34 | (address) => {
35 | const org = suggestedFunds.find((org) =>
36 | addressesEqual(org.address, address)
37 | );
38 | window.location.hash = `/${(org && org.name) || address}`;
39 | },
40 | [suggestedFunds]
41 | );
42 |
43 | if (suggestedFunds.length === 0) {
44 | return null;
45 | }
46 |
47 | return (
48 |
49 | {
51 | const knownOrg = getKnownFunds(network.type, fund.address);
52 | return {
53 | favorited: isAddressFavorited(fund.address),
54 | vaultId: fund.vaultId,
55 | image: ,
56 | name: knownOrg ? knownOrg.ticker : fund.name || fund.address,
57 | secondary: knownOrg ? knownOrg.template : "",
58 | ticker: fund.ticker,
59 | address: fund.address,
60 | };
61 | })}
62 | onActivate={goToFund}
63 | onFavoriteUpdate={updateFavorite}
64 | disabled={false}
65 | />
66 |
67 | );
68 | }
69 |
70 | Suggestions.propTypes = {
71 | suggestedFunds: PropTypes.array.isRequired,
72 | };
73 |
74 | export default Suggestions;
75 |
--------------------------------------------------------------------------------
/src/components/Landing/assets/action-create.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NFTX-project/x-frontend/9f49d8f540820594cd5a2c2c4e2b06ef14b487ea/src/components/Landing/assets/action-create.png
--------------------------------------------------------------------------------
/src/components/Landing/assets/action-open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NFTX-project/x-frontend/9f49d8f540820594cd5a2c2c4e2b06ef14b487ea/src/components/Landing/assets/action-open.png
--------------------------------------------------------------------------------
/src/components/MintPanel/MintPanel.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { TextInput, Button } from "@aragon/ui";
3 |
4 | function MintPanel({ ticker }) {
5 | const [values, setValues] = useState([""]);
6 | const getAmount = () =>
7 | values.reduce((acc, val, i) => {
8 | if (isNaN(parseInt(val))) {
9 | return acc;
10 | }
11 | if (values.slice(0, i).includes(val)) {
12 | return acc;
13 | }
14 | return acc + 1;
15 | }, 0);
16 | return (
17 |
22 | {values.map((v, i) => (
23 |
29 | {
32 | const newValues = JSON.parse(JSON.stringify(values));
33 | newValues[i] = event.target.value;
34 | setValues(newValues);
35 | }}
36 | placeholder="NFT tokenID"
37 | />
38 | {i === values.length - 1 ? (
39 |
63 | ))}
64 |
69 |
70 | );
71 | }
72 |
73 | export default MintPanel;
74 |
--------------------------------------------------------------------------------
/src/components/NftList/NftList.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import PropTypes from "prop-types";
3 | import {
4 | DataView,
5 | ContextMenu,
6 | ContextMenuItem,
7 | Header,
8 | Button,
9 | AddressField,
10 | SidePanel,
11 | IconStarFilled,
12 | IconStar,
13 | } from "@aragon/ui";
14 | import { useFavoriteNFTs } from "../../contexts/FavoriteNFTsContext";
15 | import CreateNftPanel from "../InnerPanels/CreateNftPanel";
16 | import MintNftPanel from "../InnerPanels/MintNftPanel";
17 | import TransferNftPanel from "../InnerPanels/TransferNftPanel";
18 |
19 | function NftList() {
20 | const [panelTitle, setPanelTitle] = useState("");
21 | const [panelOpened, setPanelOpened] = useState(false);
22 | const [innerPanel, setInnerPanel] = useState();
23 |
24 | const [tableEntries, setTableEntries] = useState([]);
25 |
26 | const {
27 | favoriteNFTs,
28 | isAddressFavorited,
29 | removeFavoriteByAddress,
30 | addFavorite,
31 | } = useFavoriteNFTs();
32 |
33 | const entries = [
34 | {
35 | name: "CryptoPunks",
36 | address: "0xcC495748Df37dCfb0C1041a6FDfA257D350aFD60",
37 | },
38 | {
39 | name: "Autoglyphs",
40 | address: "0x476895959C3E2a775a433B0DFA7744Ec9726b6bc",
41 | },
42 | {
43 | name: "CryptoKitties",
44 | address: "0xc860383974FB0D4b1FCD988024c3632C23b506f7",
45 | },
46 | { name: "Axie", address: "0xCf4CeD73Cdc2725cbE8DCd837CE2F46716D5b6D3" },
47 | { name: "Avastar", address: "0xD8f9c5A7d228b99e7307FA37872A339359ED111B" },
48 | { name: "JOYWORLD", address: "0x50BBddbF12A97B2BfbCC7B728Eb08Dc40d123FAd" },
49 | ];
50 |
51 | useEffect(() => {
52 | fetchTableEntries();
53 | }, []);
54 |
55 | const fetchTableEntries = () => {
56 | setTableEntries(
57 | favoriteNFTs.concat(
58 | entries.filter(
59 | (e) => !favoriteNFTs.find((f) => f.address === e.address)
60 | )
61 | )
62 | );
63 | };
64 |
65 | const handleClickCreate = () => {
66 | setPanelTitle("Create NFT");
67 | setInnerPanel(
68 | {
70 | window.location.reload();
71 | }}
72 | />
73 | );
74 | setPanelOpened(true);
75 | };
76 |
77 | const handleMint = (address, name) => {
78 | setPanelTitle(`${name} ▸ Mint`);
79 | setInnerPanel(
80 | {
83 | fetchTableEntries();
84 | setPanelOpened(false);
85 | }}
86 | />
87 | );
88 | setPanelOpened(true);
89 | };
90 |
91 | const handleTransfer = (address, name) => {
92 | setPanelTitle(`${name} ▸ Transfer`);
93 | setInnerPanel(
94 | {
97 | fetchTableEntries();
98 | setPanelOpened(false);
99 | }}
100 | />
101 | );
102 | setPanelOpened(true);
103 | };
104 |
105 | return (
106 |
107 |
}
110 | />
111 |
{
116 | return [
117 | {name}
,
118 | ,
119 | svg {
122 | }
123 | cursor: pointer;
124 | padding: 5px;
125 | `}
126 | onClick={() =>
127 | isAddressFavorited(address)
128 | ? removeFavoriteByAddress(address)
129 | : addFavorite({ name, address })
130 | }
131 | >
132 | {isAddressFavorited(address) ? : }
133 |
,
134 | ];
135 | }}
136 | renderEntryActions={(entry, index) => {
137 | return (
138 |
139 | handleMint(entry.address, entry.name)}
141 | >
142 | Mint
143 |
144 | handleTransfer(entry.address, entry.name)}
146 | >
147 | Transfer
148 |
149 |
150 | );
151 | }}
152 | />
153 | setPanelOpened(false)}
157 | >
158 | {innerPanel}
159 |
160 |
161 | );
162 | }
163 |
164 | export const NftType = PropTypes.shape({
165 | name: PropTypes.string,
166 | supply: PropTypes.string,
167 | address: PropTypes.string,
168 | });
169 |
170 | NftList.propTypes = {
171 | title: PropTypes.string,
172 | entries: PropTypes.arrayOf(NftType),
173 | handleMint: PropTypes.func,
174 | };
175 |
176 | export default NftList;
177 |
--------------------------------------------------------------------------------
/src/components/RedeemPanel/RedeemPanel.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { TextInput, Button } from "@aragon/ui";
3 |
4 | function RedeemPanel({ ticker }) {
5 | const [value, setValue] = useState("");
6 |
7 | return (
8 |
13 |
18 | setValue(event.target.value)}
21 | placeholder="NFT tokenID"
22 | />
23 | console.log("clicked")}
29 | />
30 |
31 |
38 |
39 | );
40 | }
41 |
42 | export default RedeemPanel;
43 |
--------------------------------------------------------------------------------
/src/components/Site/RoundButton/RoundButton.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useLocation, Link } from "react-router-dom";
3 | import { useTheme } from "@aragon/ui";
4 |
5 | function RoundButton({ text, link }) {
6 | const location = useLocation();
7 |
8 | const theme = useTheme();
9 | const button = (
10 |
31 |
36 | {" "}
37 | {text}
38 |
39 |
40 | );
41 | if (link) {
42 | return (
43 |
51 | {button}
52 |
53 | );
54 | } else {
55 | return button;
56 | }
57 | }
58 |
59 | export default RoundButton;
60 |
--------------------------------------------------------------------------------
/src/components/TopBar/TopBar.js:
--------------------------------------------------------------------------------
1 | import React, { useCallback } from "react";
2 | import PropTypes from "prop-types";
3 | import { Button, GU, IconSettings, useTheme } from "@aragon/ui";
4 | import AccountModule from "../AccountModule/AccountModule";
5 | import HomeButton from "../HomeButton/HomeButton";
6 |
7 | function TopBar({ status, solid }) {
8 | const theme = useTheme();
9 |
10 | const handleSettingsClick = useCallback(() => {
11 | let path = "/";
12 | if (status === "open") {
13 | path = "/open";
14 | }
15 | if (status === "create") {
16 | path = "/create";
17 | }
18 | window.location.hash = path + "?preferences=/network";
19 | }, [status]);
20 |
21 | return (
22 |
23 |
35 |
44 |
45 |
52 |
53 |
63 |
73 |
}
76 | label="Settings"
77 | size="medium"
78 | onClick={handleSettingsClick}
79 | disabled={true}
80 | />
81 |
82 |
83 |
84 | );
85 | }
86 |
87 | TopBar.propTypes = {
88 | solid: PropTypes.bool,
89 | };
90 |
91 | export default TopBar;
92 |
--------------------------------------------------------------------------------
/src/components/Tutorial/Tutorial.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Header } from "@aragon/ui";
3 |
4 | function Tutorial() {
5 | return ;
6 | }
7 |
8 | export default Tutorial;
9 |
--------------------------------------------------------------------------------
/src/components/Welcome/Welcome.js:
--------------------------------------------------------------------------------
1 | import React, { useCallback, useMemo } from "react";
2 | import PropTypes from "prop-types";
3 | import { Link, DropDown, GU, Layout, Split, useTheme } from "@aragon/ui";
4 | import Header from "../Header/Header";
5 |
6 | const Welcome = React.memo(function Welcome() {
7 | const theme = useTheme();
8 |
9 | return (
10 | header {
17 | margin-top: 10px;
18 | }
19 | `}
20 | >
21 |
26 |
27 | );
28 | });
29 |
30 | export default Welcome;
31 |
--------------------------------------------------------------------------------
/src/components/WelcomeAction/WelcomeAction.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import { Card, GU, textStyle, useTheme } from "@aragon/ui";
4 |
5 | function WelcomeAction({
6 | hasError,
7 | illustration,
8 | onActivate,
9 | subtitle,
10 | calltoaction,
11 | title,
12 | }) {
13 | const theme = useTheme();
14 | return (
15 |
24 |
31 |
37 |

46 |
47 |
48 |
54 | {title}
55 |
56 | {subtitle && (
57 |
63 | {subtitle}
64 |
65 | )}
66 |
67 | {calltoaction && (
68 |
74 | {calltoaction}
75 | Try it out!
76 |
77 | )}
78 |
79 |
80 |
81 | );
82 | }
83 |
84 | WelcomeAction.propTypes = {
85 | hasError: PropTypes.bool,
86 | illustration: PropTypes.node.isRequired,
87 | onActivate: PropTypes.func.isRequired,
88 | subtitle: PropTypes.node,
89 | calltoaction: PropTypes.node,
90 | title: PropTypes.node.isRequired,
91 | };
92 |
93 | export default WelcomeAction;
94 |
--------------------------------------------------------------------------------
/src/constants.js:
--------------------------------------------------------------------------------
1 | export const POLL_DELAY_CONNECTIVITY = 2000;
2 |
--------------------------------------------------------------------------------
/src/contexts/FavoriteFundsContext.js:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import uniqby from "lodash.uniqby";
3 | import PropTypes from "prop-types";
4 | import { network } from "../environment";
5 | import StoredList from "../StoredList";
6 | import { addressesEqual } from "../web3-utils";
7 |
8 | const FavoriteFundsContext = React.createContext();
9 |
10 | const storedList = new StoredList(`favorite-funds:${network.type}`);
11 |
12 | const filterFavoritesFunds = (funds) =>
13 | uniqby(
14 | funds
15 | .filter((fund) => fund && fund.vaultId)
16 | .map((fund) => ({
17 | ticker: fund.ticker,
18 | address: fund.address,
19 | vaultId: fund.vaultId,
20 | })),
21 | (fund) => fund.vaultId
22 | );
23 |
24 | class FavoriteFundsProvider extends React.Component {
25 | static propTypes = {
26 | children: PropTypes.node,
27 | };
28 |
29 | state = {
30 | favoriteFunds: filterFavoritesFunds(storedList.loadItems()),
31 | };
32 |
33 | add = (fund) => {
34 | this.setState({
35 | favoriteFunds: storedList.add(fund),
36 | });
37 | };
38 |
39 | remove = (index) => {
40 | this.setState({
41 | favoriteFunds: storedList.remove(index),
42 | });
43 | };
44 |
45 | isAddressFavorited = (address) => {
46 | return (
47 | this.state.favoriteFunds.findIndex((fund) =>
48 | addressesEqual(fund.address, address)
49 | ) > -1
50 | );
51 | };
52 |
53 | isVaultIdFavorited = (vaultId) => {
54 | return this.state.favoriteFunds.find((fund) => fund.vaultId === vaultId);
55 | };
56 |
57 | addFavorite = ({ vaultId, address, ticker }) => {
58 | console.log("addFavorite", vaultId, address, ticker);
59 | const fundIndex = this.state.favoriteFunds.findIndex(
60 | ({ _vaultId }) => vaultId === _vaultId
61 | );
62 | if (fundIndex >= 0) {
63 | console.log("..already favorite");
64 | } else {
65 | console.log("not fav yet, all good..");
66 | }
67 |
68 | console.log("adding new favorite...", {
69 | ticker: ticker,
70 | address: address,
71 | vaultId: vaultId,
72 | });
73 | if (fundIndex === -1) {
74 | this.setState({
75 | favoriteFunds: storedList.add({
76 | ticker: ticker,
77 | address: address,
78 | vaultId: vaultId,
79 | }),
80 | });
81 | }
82 | };
83 |
84 | removeFavoriteByVaultId = (_vaultId) => {
85 | console.log("removeFavoriteByVaultId(..)", _vaultId);
86 | console.log("_vaultId", _vaultId);
87 | console.log("this.state.favoriteFunds", this.state.favoriteFunds);
88 | const fundIndex = this.state.favoriteFunds.findIndex(
89 | ({ vaultId }) => vaultId === _vaultId
90 | );
91 | console.log("fundIndex", fundIndex);
92 | if (fundIndex > -1) {
93 | const favs = storedList.remove(fundIndex);
94 | this.setState({
95 | favoriteFunds: favs,
96 | });
97 | }
98 | };
99 |
100 | updateFavoriteFunds = (favoriteFunds) => {
101 | this.setState({
102 | favoriteFunds: storedList.update(favoriteFunds),
103 | });
104 | };
105 |
106 | render() {
107 | const { children } = this.props;
108 | const { favoriteFunds } = this.state;
109 | return (
110 |
120 | {children}
121 |
122 | );
123 | }
124 | }
125 |
126 | function useFavoriteFunds() {
127 | return useContext(FavoriteFundsContext);
128 | }
129 |
130 | const FavoriteFundsConsumer = FavoriteFundsContext.Consumer;
131 |
132 | export { FavoriteFundsProvider, FavoriteFundsConsumer, useFavoriteFunds };
133 |
--------------------------------------------------------------------------------
/src/contexts/FavoriteNFTsContext.js:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import uniqby from "lodash.uniqby";
3 | import PropTypes from "prop-types";
4 | import { network } from "../environment";
5 | import StoredList from "../StoredList";
6 | import { addressesEqual } from "../web3-utils";
7 |
8 | const FavoriteNFTsContext = React.createContext();
9 |
10 | const storedList = new StoredList(`favorite-nfts:${network.type}`);
11 |
12 | const filterFavoritesNFTs = (nfts) =>
13 | uniqby(
14 | nfts
15 | .filter((nft) => nft && nft.address)
16 | .map((nft) => ({
17 | name: nft.name || "",
18 | address: nft.address,
19 | })),
20 | (nft) => nft.address.toLowerCase()
21 | );
22 |
23 | class FavoriteNFTsProvider extends React.Component {
24 | static propTypes = {
25 | children: PropTypes.node,
26 | };
27 |
28 | state = {
29 | favoriteNFTs: filterFavoritesNFTs(storedList.loadItems()),
30 | };
31 |
32 | add = (nft) => {
33 | this.setState({
34 | favoriteNFTs: storedList.add(nft),
35 | });
36 | };
37 |
38 | remove = (index) => {
39 | this.setState({
40 | favoriteNFTs: storedList.remove(index),
41 | });
42 | };
43 |
44 | isAddressFavorited = (address) => {
45 | return (
46 | this.state.favoriteNFTs.findIndex((nft) =>
47 | addressesEqual(nft.address, address)
48 | ) > -1
49 | );
50 | };
51 |
52 | addFavorite = (nft) => {
53 | const nftIndex = this.state.favoriteNFTs.findIndex(({ address }) =>
54 | addressesEqual(address, nft.address)
55 | );
56 | if (nftIndex === -1) {
57 | this.setState({
58 | favoriteNFTs: storedList.add({
59 | name: nft.name,
60 | address: nft.address,
61 | }),
62 | });
63 | }
64 | };
65 |
66 | removeFavoriteByAddress = (addr) => {
67 | const nftIndex = this.state.favoriteNFTs.findIndex(({ address }) =>
68 | addressesEqual(address, addr)
69 | );
70 | if (nftIndex > -1) {
71 | const favs = storedList.remove(nftIndex);
72 | this.setState({
73 | favoriteNFTs: favs,
74 | });
75 | }
76 | };
77 |
78 | updateFavoriteNFTs = (favoriteNFTs) => {
79 | this.setState({
80 | favoriteNFTs: storedList.update(favoriteNFTs),
81 | });
82 | };
83 |
84 | render() {
85 | const { children } = this.props;
86 | const { favoriteNFTs } = this.state;
87 | return (
88 |
97 | {children}
98 |
99 | );
100 | }
101 | }
102 |
103 | function useFavoriteNFTs() {
104 | return useContext(FavoriteNFTsContext);
105 | }
106 |
107 | const FavoriteNFTsConsumer = FavoriteNFTsContext.Consumer;
108 |
109 | export { FavoriteNFTsProvider, FavoriteNFTsConsumer, useFavoriteNFTs };
110 |
--------------------------------------------------------------------------------
/src/contracts/IERC20.json:
--------------------------------------------------------------------------------
1 | {
2 | "contractName": "IERC20",
3 | "abi": [
4 | {
5 | "anonymous": false,
6 | "inputs": [
7 | {
8 | "indexed": true,
9 | "internalType": "address",
10 | "name": "owner",
11 | "type": "address"
12 | },
13 | {
14 | "indexed": true,
15 | "internalType": "address",
16 | "name": "spender",
17 | "type": "address"
18 | },
19 | {
20 | "indexed": false,
21 | "internalType": "uint256",
22 | "name": "value",
23 | "type": "uint256"
24 | }
25 | ],
26 | "name": "Approval",
27 | "type": "event"
28 | },
29 | {
30 | "anonymous": false,
31 | "inputs": [
32 | {
33 | "indexed": true,
34 | "internalType": "address",
35 | "name": "from",
36 | "type": "address"
37 | },
38 | {
39 | "indexed": true,
40 | "internalType": "address",
41 | "name": "to",
42 | "type": "address"
43 | },
44 | {
45 | "indexed": false,
46 | "internalType": "uint256",
47 | "name": "value",
48 | "type": "uint256"
49 | }
50 | ],
51 | "name": "Transfer",
52 | "type": "event"
53 | },
54 | {
55 | "inputs": [
56 | {
57 | "internalType": "address",
58 | "name": "owner",
59 | "type": "address"
60 | },
61 | {
62 | "internalType": "address",
63 | "name": "spender",
64 | "type": "address"
65 | }
66 | ],
67 | "name": "allowance",
68 | "outputs": [
69 | {
70 | "internalType": "uint256",
71 | "name": "",
72 | "type": "uint256"
73 | }
74 | ],
75 | "stateMutability": "view",
76 | "type": "function"
77 | },
78 | {
79 | "inputs": [
80 | {
81 | "internalType": "address",
82 | "name": "spender",
83 | "type": "address"
84 | },
85 | {
86 | "internalType": "uint256",
87 | "name": "amount",
88 | "type": "uint256"
89 | }
90 | ],
91 | "name": "approve",
92 | "outputs": [
93 | {
94 | "internalType": "bool",
95 | "name": "",
96 | "type": "bool"
97 | }
98 | ],
99 | "stateMutability": "nonpayable",
100 | "type": "function"
101 | },
102 | {
103 | "inputs": [
104 | {
105 | "internalType": "address",
106 | "name": "account",
107 | "type": "address"
108 | }
109 | ],
110 | "name": "balanceOf",
111 | "outputs": [
112 | {
113 | "internalType": "uint256",
114 | "name": "",
115 | "type": "uint256"
116 | }
117 | ],
118 | "stateMutability": "view",
119 | "type": "function"
120 | },
121 | {
122 | "inputs": [],
123 | "name": "totalSupply",
124 | "outputs": [
125 | {
126 | "internalType": "uint256",
127 | "name": "",
128 | "type": "uint256"
129 | }
130 | ],
131 | "stateMutability": "view",
132 | "type": "function"
133 | },
134 | {
135 | "inputs": [
136 | {
137 | "internalType": "address",
138 | "name": "recipient",
139 | "type": "address"
140 | },
141 | {
142 | "internalType": "uint256",
143 | "name": "amount",
144 | "type": "uint256"
145 | }
146 | ],
147 | "name": "transfer",
148 | "outputs": [
149 | {
150 | "internalType": "bool",
151 | "name": "",
152 | "type": "bool"
153 | }
154 | ],
155 | "stateMutability": "nonpayable",
156 | "type": "function"
157 | },
158 | {
159 | "inputs": [
160 | {
161 | "internalType": "address",
162 | "name": "sender",
163 | "type": "address"
164 | },
165 | {
166 | "internalType": "address",
167 | "name": "recipient",
168 | "type": "address"
169 | },
170 | {
171 | "internalType": "uint256",
172 | "name": "amount",
173 | "type": "uint256"
174 | }
175 | ],
176 | "name": "transferFrom",
177 | "outputs": [
178 | {
179 | "internalType": "bool",
180 | "name": "",
181 | "type": "bool"
182 | }
183 | ],
184 | "stateMutability": "nonpayable",
185 | "type": "function"
186 | }
187 | ],
188 | "bytecode": "0x",
189 | "deployedBytecode": "0x",
190 | "linkReferences": {},
191 | "deployedLinkReferences": {}
192 | }
193 |
--------------------------------------------------------------------------------
/src/copy-to-clipboard/index.js:
--------------------------------------------------------------------------------
1 | import { useToast } from "@aragon/ui";
2 | import * as clipboard from "clipboard-polyfill";
3 |
4 | export function useCopyToClipboard(payload, toastText) {
5 | const toast = useToast();
6 | return () => {
7 | clipboard.writeText(payload);
8 | toastText && toast(toastText);
9 | };
10 | }
11 |
--------------------------------------------------------------------------------
/src/data/fundInfo.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "vaultId": 0,
4 | "verified": true,
5 | "amm": true
6 | },
7 | {
8 | "vaultId": 1,
9 | "verified": true,
10 | "amm": true
11 | },
12 | {
13 | "vaultId": 2,
14 | "verified": true,
15 | "amm": true
16 | },
17 | {
18 | "vaultId": 3,
19 | "verified": true,
20 | "amm": true
21 | },
22 | {
23 | "vaultId": 4,
24 | "verified": true,
25 | "amm": false
26 | },
27 | {
28 | "vaultId": 5,
29 | "verified": true,
30 | "amm": false
31 | },
32 | {
33 | "vaultId": 6,
34 | "verified": true,
35 | "amm": false
36 | },
37 | {
38 | "vaultId": 7,
39 | "verified": true,
40 | "amm": true
41 | },
42 | {
43 | "vaultId": 8,
44 | "verified": true,
45 | "amm": true
46 | },
47 | //{
48 | // "vaultId": 9,
49 | // "verified": true,
50 | // "amm": false
51 | // },
52 | {
53 | "vaultId": 10,
54 | "verified": true,
55 | "amm": false
56 | },
57 | {
58 | "vaultId": 11,
59 | "verified": true,
60 | "amm": false
61 | },
62 | {
63 | "vaultId": 12,
64 | "verified": true,
65 | "amm": false
66 | },
67 | {
68 | "vaultId": 13,
69 | "verified": true,
70 | "amm": false
71 | },
72 | {
73 | "vaultId": 14,
74 | "verified": true,
75 | "amm": false
76 | },
77 | {
78 | "vaultId": 15,
79 | "verified": true,
80 | "amm": true
81 | },
82 | {
83 | "vaultId": 16,
84 | "verified": true,
85 | "amm": true
86 | },
87 | {
88 | "vaultId": 18,
89 | "verified": true,
90 | "amm": false
91 | },
92 | {
93 | "vaultId": 19,
94 | "verified": true,
95 | "amm": false
96 | },
97 | {
98 | "vaultId": 20,
99 | "verified": true,
100 | "amm": true
101 | },
102 | {
103 | "vaultId": 21,
104 | "verified": true,
105 | "amm": true
106 | },
107 | {
108 | "vaultId": 22,
109 | "verified": true,
110 | "amm": true
111 | },
112 | {
113 | "vaultId": 27,
114 | "verified": true,
115 | "amm": true
116 | },
117 | {
118 | "vaultId": 28,
119 | "verified": true,
120 | "amm": true
121 | },
122 | {
123 | "vaultId": 29,
124 | "verified": true,
125 | "amm": true
126 | },
127 | {
128 | "vaultId": 30,
129 | "verified": true,
130 | "amm": true
131 | },
132 | {
133 | "vaultId": 31,
134 | "verified": true,
135 | "amm": true
136 | },
137 | {
138 | "vaultId": 32,
139 | "verified": true,
140 | "amm": true
141 | },
142 | {
143 | "vaultId": 33,
144 | "verified": true,
145 | "amm": true
146 | },
147 | {
148 | "vaultId": 35,
149 | "verified": true,
150 | "amm": true
151 | },
152 | {
153 | "vaultId": 37,
154 | "verified": true,
155 | "amm": true
156 | },
157 | {
158 | "vaultId": 40,
159 | "verified": true,
160 | "amm": true
161 | },
162 | {
163 | "vaultId": 41,
164 | "verified": true,
165 | "amm": false
166 | },
167 | //{
168 | // "vaultId": 42,
169 | // "verified": true,
170 | // "amm": false
171 | //},
172 | {
173 | "vaultId": 44,
174 | "verified": true,
175 | "amm": false
176 | },
177 | {
178 | "vaultId": 49,
179 | "verified": true,
180 | "amm": false
181 | }
182 | ,
183 | {
184 | "vaultId": 47,
185 | "verified": true,
186 | "amm": false
187 | }
188 | ,
189 | {
190 | "vaultId": 50,
191 | "verified": true,
192 | "amm": false
193 | },
194 | {
195 | "vaultId": 51,
196 | "verified": true,
197 | "amm": false
198 | },
199 | {
200 | "vaultId": 45,
201 | "verified": true,
202 | "amm": false
203 | },
204 | {
205 | "vaultId": 53,
206 | "verified": true,
207 | "amm": true
208 | },
209 | {
210 | "vaultId": 52,
211 | "verified": true,
212 | "amm": true
213 | },
214 | {
215 | "vaultId": 58,
216 | "verified": true,
217 | "amm": false
218 | },
219 | {
220 | "vaultId": 61,
221 | "verified": true,
222 | "amm": false
223 | },
224 | {
225 | "vaultId": 63,
226 | "verified": true,
227 | "amm": true
228 | },
229 | {
230 | "vaultId": 64,
231 | "verified": true,
232 | "amm": true
233 | },
234 | {
235 | "vaultId": 67,
236 | "verified": true,
237 | "amm": false
238 | },
239 | {
240 | "vaultId": 68,
241 | "verified": true,
242 | "amm": false
243 | },
244 | {
245 | "vaultId": 70,
246 | "verified": true,
247 | "amm": false
248 | },
249 | {
250 | "vaultId": 72,
251 | "verified": true,
252 | "amm": false
253 | },
254 | {
255 | "vaultId": 74,
256 | "verified": true,
257 | "amm": true
258 | }
259 | ]
260 |
--------------------------------------------------------------------------------
/src/data/mainnetD1Funds.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "ticker": "PUNK-BASIC",
4 | "address": "0x69BbE2FA02b4D90A944fF328663667DC32786385",
5 | "vaultId": 0
6 | },
7 | {
8 | "ticker": "PUNK-ATTR-4",
9 | "address": "0x49706a576bb823cdE3180C930F9947d59e2deD4D",
10 | "vaultId": 1
11 | },
12 | {
13 | "ticker": "PUNK-ATTR-5",
14 | "address": "0xAB9c92A9337A1494C6D545E48187Fa37144403c8",
15 | "vaultId": 2
16 | },
17 | {
18 | "ticker": "PUNK-ZOMBIE",
19 | "address": "0xF18ade29a225fAa555e475eE01F9Eb66eb4a3a74",
20 | "vaultId": 3
21 | },
22 | {
23 | "ticker": "GLYPH",
24 | "address": "0xc8AA432112814B9CAB53811D4340Ed45482CB2b5",
25 | "vaultId": 13
26 | },
27 | {
28 | "ticker": "JOY",
29 | "address": "0x4acC9c89F47f5330b2f4F412ef157E3016333f58",
30 | "vaultId": 14
31 | }
32 | ]
--------------------------------------------------------------------------------
/src/environment.js:
--------------------------------------------------------------------------------
1 | import Web3 from "web3";
2 | import { getDefaultEthNode, getEthNetworkType, getIpfsGateway } from "./local-settings";
3 | import { getNetworkConfig } from "./network-config";
4 |
5 | const networkType = getEthNetworkType();
6 |
7 | const networkConfig = getNetworkConfig(networkType);
8 | export const network = networkConfig.settings;
9 | export const providers = networkConfig.providers;
10 |
11 | export const defaultEthNode =
12 | getDefaultEthNode() || networkConfig.nodes.defaultEth;
13 |
14 | export const web3Providers = {
15 | default: new Web3.providers.WebsocketProvider(defaultEthNode),
16 | };
17 |
18 | export const ipfsDefaultConf = {
19 | gateway: getIpfsGateway(),
20 | }
--------------------------------------------------------------------------------
/src/errors.js:
--------------------------------------------------------------------------------
1 | export const extendError = (name, { defaultMessage }) =>
2 | class extends Error {
3 | name = name
4 | constructor(message = defaultMessage) {
5 | super(message)
6 | }
7 | }
8 |
9 | // export const InvalidAddress = extendError('InvalidAddress', {
10 | // defaultMessage: 'The address is invalid',
11 | // })
12 | export const InvalidNetworkType = extendError('InvalidNetworkType', {
13 | defaultMessage: 'The network type is invalid',
14 | })
15 | export const InvalidURI = extendError('InvalidURI', {
16 | defaultMessage: 'The URI is invalid',
17 | })
18 | export const NoConnection = extendError('NoConnection', {
19 | defaultMessage: 'There is no connection',
20 | })
21 | // export class DAONotFound extends Error {
22 | // name = 'DAONotFound'
23 | // constructor(dao) {
24 | // super('The ENS address of this dao could not be resolved')
25 | // this.dao = dao
26 | // }
27 | // }
28 |
--------------------------------------------------------------------------------
/src/ethereum-providers/icons/Cipher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NFTX-project/x-frontend/9f49d8f540820594cd5a2c2c4e2b06ef14b487ea/src/ethereum-providers/icons/Cipher.png
--------------------------------------------------------------------------------
/src/ethereum-providers/icons/Fortmatic.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/ethereum-providers/icons/Frame.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NFTX-project/x-frontend/9f49d8f540820594cd5a2c2c4e2b06ef14b487ea/src/ethereum-providers/icons/Frame.png
--------------------------------------------------------------------------------
/src/ethereum-providers/icons/Metamask.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NFTX-project/x-frontend/9f49d8f540820594cd5a2c2c4e2b06ef14b487ea/src/ethereum-providers/icons/Metamask.png
--------------------------------------------------------------------------------
/src/ethereum-providers/icons/Portis.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/ethereum-providers/icons/Status.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/NFTX-project/x-frontend/9f49d8f540820594cd5a2c2c4e2b06ef14b487ea/src/ethereum-providers/icons/Status.png
--------------------------------------------------------------------------------
/src/ethereum-providers/icons/wallet.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/ethereum-providers/index.js:
--------------------------------------------------------------------------------
1 | import { isElectron } from '../utils'
2 |
3 | import frame from './icons/Frame.png'
4 | import cipher from './icons/Cipher.png'
5 | import metamask from './icons/Metamask.png'
6 | import status from './icons/Status.png'
7 | import wallet from './icons/wallet.svg'
8 | import fortmatic from './icons/Fortmatic.svg'
9 | import portis from './icons/Portis.svg'
10 |
11 | // See the corresponding prop type, EthereumProviderType, in prop-types.js.
12 | const PROVIDERS = new Map(
13 | [
14 | {
15 | id: 'frame',
16 | name: 'Frame',
17 | type: 'Desktop',
18 | image: frame,
19 | strings: {
20 | 'your Ethereum wallet': 'Frame',
21 | },
22 | },
23 | {
24 | id: 'metamask',
25 | name: 'Metamask',
26 | type: 'Desktop',
27 | image: metamask,
28 | strings: {
29 | 'your Ethereum wallet': 'Metamask',
30 | },
31 | },
32 | {
33 | id: 'status',
34 | name: 'Status',
35 | type: 'Mobile',
36 | image: status,
37 | strings: {
38 | 'your Ethereum wallet': 'Status',
39 | },
40 | },
41 | {
42 | id: 'cipher',
43 | name: 'Cipher',
44 | type: 'Mobile',
45 | image: cipher,
46 | strings: {
47 | 'your Ethereum wallet': 'Cipher',
48 | },
49 | },
50 | {
51 | id: 'fortmatic',
52 | name: 'Fortmatic',
53 | type: 'Any',
54 | image: fortmatic,
55 | strings: {
56 | 'your Ethereum wallet': 'Fortmatic',
57 | },
58 | },
59 | {
60 | id: 'portis',
61 | name: 'Portis',
62 | type: 'Any',
63 | image: portis,
64 | strings: {
65 | 'your Ethereum wallet': 'Portis',
66 | },
67 | },
68 | {
69 | id: 'unknown',
70 | name: 'Unknown',
71 | type: 'Desktop',
72 | image: wallet,
73 | strings: {
74 | 'your Ethereum wallet': 'your wallet',
75 | },
76 | },
77 | ].map(provider => [provider.id, provider])
78 | )
79 |
80 | // Get a providers object for a given ID.
81 | function getProvider(providerId) {
82 | return PROVIDERS.get(providerId)
83 | }
84 |
85 | // Get a string that depends on the current Ethereum provider.
86 | // The default string is used as an identifier (à la gettext).
87 | function getProviderString(string, providerId = 'unknown') {
88 | const provider = getProvider(providerId)
89 | return (provider && provider.strings[string]) || string
90 | }
91 |
92 | // Get an identifier for the provider, if it can be detected.
93 | function identifyProvider(provider) {
94 | if (provider && isElectron()) {
95 | return 'frame'
96 | }
97 | if (provider && provider.isMetaMask) {
98 | return 'metamask'
99 | }
100 | return 'unknown'
101 | }
102 |
103 | // Get a provider from its useWallet() identifier.
104 | function getProviderFromUseWalletId(id) {
105 | if (id === 'provided') {
106 | return (
107 | getProvider(identifyProvider(window.ethereum)) || getProvider('unknown')
108 | )
109 | }
110 | return getProvider(id) || getProvider('unknown')
111 | }
112 |
113 | export {
114 | getProvider,
115 | identifyProvider,
116 | getProviderString,
117 | getProviderFromUseWalletId,
118 | }
119 | export default PROVIDERS
120 |
--------------------------------------------------------------------------------
/src/hooks.js:
--------------------------------------------------------------------------------
1 | import { useCallback, useEffect } from "react";
2 | import keycodes from "./keycodes";
3 |
4 | export function useEsc(cb) {
5 | const handlekeyDown = useCallback(
6 | (e) => {
7 | if (e.keyCode === keycodes.esc) {
8 | cb();
9 | }
10 | },
11 | [cb]
12 | );
13 | useEffect(() => {
14 | window.addEventListener("keydown", handlekeyDown);
15 | return () => window.removeEventListener("keydown", handlekeyDown);
16 | }, [handlekeyDown]);
17 | }
18 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
12 | monospace;
13 | }
14 |
15 | a {
16 | outline: 0;
17 | }
18 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
11 |
12 | NFTX — enabling NFT Index Funds
13 |
15 |
25 |
26 |
27 |
34 |
35 |
36 |
37 |
40 |
41 |
42 | Our devs are hard at work building a new and improved app experience. Stay tuned! 🦧 🕳️
43 |
44 |
45 |
46 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import "regenerator-runtime/runtime";
2 |
3 | import React from "react";
4 | import ReactDOM from "react-dom";
5 | import "./index.css";
6 | import App from "./App";
7 | import reportWebVitals from "./reportWebVitals";
8 |
9 | import "react-loader-spinner/dist/loader/css/react-spinner-loader.css";
10 |
11 | ReactDOM.render(
12 |
13 |
14 | ,
15 | document.getElementById("root")
16 | );
17 |
18 | // If you want to start measuring performance in your app, pass a function
19 | // to log results (for example: reportWebVitals(console.log))
20 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
21 | reportWebVitals();
22 |
--------------------------------------------------------------------------------
/src/keycodes.js:
--------------------------------------------------------------------------------
1 | export default {
2 | enter: 13,
3 | esc: 27,
4 | up: 38,
5 | left: 37,
6 | down: 40,
7 | right: 39,
8 | }
9 |
--------------------------------------------------------------------------------
/src/known-funds/images/blankdao.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/known-funds/index.js:
--------------------------------------------------------------------------------
1 | import blankDaoImage from "./images/blankdao.svg";
2 | import mainnetFunds from "../data/mainnetD1Funds.json";
3 |
4 | const TEMPLATE_D1 = "D1 Fund";
5 |
6 | export const KnownFunds = {
7 | main: new Map(
8 | mainnetFunds
9 | .map((entry) => {
10 | entry.name = entry.ticker;
11 | entry.recommended = true;
12 | entry.template = TEMPLATE_D1;
13 | return entry;
14 | })
15 | .map((fund) => [fund.address.toLowerCase(), fund])
16 | ),
17 | };
18 |
19 | // Get the organizations that might appear in the suggestions,
20 | // using the format `{ address, name }` where name is the ENS domain.
21 | export const getRecommendedFunds = (networkType, max = -1) => {
22 | if (!KnownFunds[networkType]) {
23 | return [];
24 | }
25 |
26 | const recommended = [];
27 | for (const [address, fund] of KnownFunds[networkType]) {
28 | if (fund.recommended) {
29 | recommended.push({ address, ticker: fund.ticker, vaultId: fund.vaultId });
30 | if (recommended.length === max) {
31 | break;
32 | }
33 | }
34 | }
35 |
36 | return recommended;
37 | };
38 |
39 | export const getKnownFunds = (networkType, address) => {
40 | if (!KnownFunds[networkType]) return null;
41 | return KnownFunds[networkType].get(address) || null;
42 | };
43 |
--------------------------------------------------------------------------------
/src/local-settings.js:
--------------------------------------------------------------------------------
1 | // List of configurable settings
2 | const APP_LOCATOR = "APP_LOCATOR";
3 | const CLIENT_THEME = "THEME";
4 | const DEFAULT_ETH_NODE = "DEFAULT_ETH_NODE";
5 | const ENS_REGISTRY_ADDRESS = "ENS_REGISTRY_ADDRESS";
6 | const ETH_NETWORK_TYPE = "ETH_NETWORK_TYPE";
7 | const ETH_SUBSCRIPTION_EVENT_DELAY = "ETH_SUBSCRIPTION_EVENT_DELAY";
8 | const IPFS_GATEWAY = "IPFS_GATEWAY";
9 | const LOCAL_CHAIN_ID = "LOCAL_CHAIN_ID";
10 | const PACKAGE_VERSION = "PACKAGE_VERSION";
11 | const SELECTED_CURRENCY = "SELECTED_CURRENCY";
12 | const SENTRY_DSN = "SENTRY_DSN";
13 | const PORTIS_DAPP_ID = "PORTIS_DAPP_ID";
14 | const FORTMATIC_API_KEY = "FORTMATIC_API_KEY";
15 |
16 | // Parcel requires env vars to be declared statically.
17 | const CONFIGURATION_VARS = [
18 | [
19 | APP_LOCATOR,
20 | process.env.NFTX_APP_LOCATOR,
21 | process.env.REACT_APP_ASSET_BRIDGE,
22 | ],
23 | [
24 | DEFAULT_ETH_NODE,
25 | process.env.NFTX_DEFAULT_ETH_NODE,
26 | process.env.REACT_APP_DEFAULT_ETH_NODE,
27 | ],
28 | [
29 | ENS_REGISTRY_ADDRESS,
30 | process.env.NFTX_ENS_REGISTRY_ADDRESS,
31 | process.env.REACT_APP_ENS_REGISTRY_ADDRESS,
32 | ],
33 | [
34 | ETH_NETWORK_TYPE,
35 | process.env.NFTX_ETH_NETWORK_TYPE,
36 | process.env.REACT_APP_ETH_NETWORK_TYPE,
37 | ],
38 | [
39 | ETH_SUBSCRIPTION_EVENT_DELAY,
40 | process.env.NFTX_ETH_SUBSCRIPTION_EVENT_DELAY,
41 | process.env.REACT_APP_ETH_SUBSCRIPTION_EVENT_DELAY,
42 | ],
43 | [
44 | IPFS_GATEWAY,
45 | process.env.NFTX_IPFS_GATEWAY,
46 | process.env.REACT_APP_IPFS_GATEWAY,
47 | ],
48 | [
49 | SELECTED_CURRENCY,
50 | process.env.NFTX_SELECTED_CURRENCY,
51 | process.env.REACT_APP_SELECTED_CURRENCY,
52 | ],
53 | [SENTRY_DSN, process.env.NFTX_SENTRY_DSN, process.env.REACT_APP_SENTRY_DSN],
54 | [
55 | PACKAGE_VERSION,
56 | process.env.NFTX_PACKAGE_VERSION,
57 | process.env.REACT_APP_PACKAGE_VERSION,
58 | ],
59 | [CLIENT_THEME, process.env.NFTX_CLIENT_THEME],
60 | [LOCAL_CHAIN_ID, process.env.LOCAL_CHAIN_ID],
61 | [FORTMATIC_API_KEY, process.env.NFTX_FORTMATIC_API_KEY],
62 | [PORTIS_DAPP_ID, process.env.NFTX_PORTIS_DAPP_ID],
63 | ].reduce(
64 | (acc, [option, envValue, envValueCompat]) => ({
65 | ...acc,
66 | [option]: {
67 | storageKey: `${option}_KEY`,
68 | envValue: envValue || envValueCompat || null,
69 | },
70 | }),
71 | {}
72 | );
73 |
74 | // Get a setting from localStorage
75 | function getLocalStorageSetting(confKey) {
76 | return window.localStorage.getItem(CONFIGURATION_VARS[confKey].storageKey);
77 | }
78 |
79 | // Get a setting from the env vars
80 | function getEnvSetting(confKey) {
81 | return CONFIGURATION_VARS[confKey].envValue;
82 | }
83 |
84 | // Get a local setting: from the local storage if available, or the env vars.
85 | function getLocalSetting(confKey) {
86 | return getLocalStorageSetting(confKey) || getEnvSetting(confKey);
87 | }
88 |
89 | export function getLocalChainId() {
90 | // Default to 1337 as used by most local development environments.
91 | return getLocalSetting(LOCAL_CHAIN_ID) || 1337;
92 | }
93 |
94 | export function getDefaultEthNode() {
95 | // Let the network configuration handle node defaults
96 | return getLocalSetting(DEFAULT_ETH_NODE) || "";
97 | }
98 |
99 | export function getEnsRegistryAddress() {
100 | // Let the network configuration handle contract address defaults
101 | return getLocalSetting(ENS_REGISTRY_ADDRESS) || "";
102 | }
103 |
104 | export function getEthNetworkType() {
105 | return getLocalSetting(ETH_NETWORK_TYPE) || "main";
106 | }
107 |
108 | export function getIpfsGateway() {
109 | return (
110 | getLocalSetting(IPFS_GATEWAY) || "https://ipfs.eth.aragon.network/ipfs"
111 | );
112 | }
113 |
114 | export function getPortisDappId() {
115 | return getLocalSetting(PORTIS_DAPP_ID) || "";
116 | }
117 |
118 | export function getFortmaticApiKey() {
119 | return getLocalSetting(FORTMATIC_API_KEY) || "";
120 | }
121 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/network-config.js:
--------------------------------------------------------------------------------
1 | import {
2 | getLocalChainId,
3 | getEnsRegistryAddress,
4 | getFortmaticApiKey,
5 | getPortisDappId,
6 | } from "./local-settings";
7 |
8 | const localEnsRegistryAddress = getEnsRegistryAddress();
9 | const fortmaticApiKey = getFortmaticApiKey();
10 | const portisDappId = getPortisDappId();
11 |
12 | export const networkConfigs = {
13 | main: {
14 | addresses: {
15 | ensRegistry:
16 | localEnsRegistryAddress || "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e",
17 | },
18 | nodes: {
19 | defaultEth: `wss://eth-mainnet.ws.alchemyapi.io/v2/${process.env.ALCHEMY_API_KEY}`,
20 | },
21 | settings: {
22 | chainId: 1,
23 | name: "Mainnet",
24 | shortName: "Mainnet",
25 | type: "main", // as returned by web3.eth.net.getNetworkType()
26 | live: true,
27 | },
28 | providers: [
29 | { id: "provided" },
30 | { id: "frame" },
31 | fortmaticApiKey ? { id: "fortmatic", conf: fortmaticApiKey } : null,
32 | portisDappId ? { id: "portis", conf: portisDappId } : null,
33 | ].filter((p) => p),
34 | },
35 | rinkeby: {
36 | addresses: {
37 | ensRegistry:
38 | localEnsRegistryAddress || "0x98df287b6c145399aaa709692c8d308357bc085d",
39 | },
40 | nodes: {
41 | defaultEth: "wss://rinkeby.eth.aragon.network/ws",
42 | },
43 | settings: {
44 | chainId: 4,
45 | name: "Rinkeby testnet",
46 | shortName: "Rinkeby",
47 | type: "rinkeby", // as returned by web3.eth.net.getNetworkType()
48 | live: true,
49 | },
50 | // providers: ['injected', 'frame'],
51 | providers: [
52 | { id: "provided" },
53 | { id: "frame" },
54 | fortmaticApiKey ? { id: "fortmatic", conf: fortmaticApiKey } : null,
55 | portisDappId ? { id: "portis", conf: portisDappId } : null,
56 | ].filter((p) => p),
57 | },
58 | ropsten: {
59 | addresses: {
60 | ensRegistry:
61 | localEnsRegistryAddress || "0x6afe2cacee211ea9179992f89dc61ff25c61e923",
62 | },
63 | nodes: {
64 | defaultEth: "wss://ropsten.eth.aragon.network/ws",
65 | },
66 | settings: {
67 | chainId: 3,
68 | name: "Ropsten testnet",
69 | shortName: "Ropsten",
70 | type: "ropsten", // as returned by web3.eth.net.getNetworkType()
71 | live: true,
72 | },
73 | providers: [{ id: "provided" }, { id: "frame" }],
74 | },
75 | local: {
76 | addresses: {
77 | ensRegistry: localEnsRegistryAddress,
78 | },
79 | nodes: {
80 | defaultEth: "ws://localhost:8545",
81 | },
82 | settings: {
83 | // Local development environments by convention use
84 | // a chainId of value 1337, but for the sake of configuration
85 | // we expose a way to change this value.
86 | chainId: Number(getLocalChainId()),
87 | name: "local testnet",
88 | shortName: "Local",
89 | type: "private",
90 | live: false,
91 | },
92 | providers: [{ id: "provided" }, { id: "frame" }],
93 | },
94 | // xDai is an experimental chain in the Aragon Client. It's possible
95 | // and expected that a few things will break.
96 | xdai: {
97 | addresses: {
98 | ensRegistry:
99 | localEnsRegistryAddress || "0xaafca6b0c89521752e559650206d7c925fd0e530",
100 | },
101 | nodes: {
102 | defaultEth: "wss://xdai.poanetwork.dev/wss",
103 | },
104 | settings: {
105 | chainId: 100,
106 | name: "xDai",
107 | shortName: "xdai",
108 | type: "private",
109 | live: true,
110 | },
111 | providers: [
112 | { id: "provided" },
113 | { id: "frame" },
114 | portisDappId ? { id: "portis", conf: portisDappId } : null,
115 | ].filter((p) => p),
116 | },
117 | unknown: {
118 | addresses: {
119 | ensRegistry: localEnsRegistryAddress,
120 | },
121 | nodes: {
122 | defaultEth: "ws://localhost:8545",
123 | },
124 | settings: {
125 | name: `Unknown network`,
126 | shortName: "Unknown",
127 | type: "unknown",
128 | live: false,
129 | },
130 | providers: [{ id: "provided" }, { id: "frame" }],
131 | },
132 | };
133 |
134 | export function getNetworkConfig(type) {
135 | return (
136 | networkConfigs[type] || {
137 | ...networkConfigs.unknown,
138 | settings: {
139 | ...networkConfigs.unknown.settings,
140 | name: `Unsupported network (${type})`,
141 | },
142 | }
143 | );
144 | }
145 |
146 | export function getNetworkByChainId(chainId = -1) {
147 | chainId = Number(chainId);
148 | return (
149 | Object.values(networkConfigs).find(
150 | (network) => network.settings.chainId === chainId
151 | ) || networkConfigs.unknown
152 | );
153 | }
154 |
155 | export function sanitizeNetworkType(networkType) {
156 | if (networkType === "private") {
157 | return "localhost";
158 | } else if (networkType === "main") {
159 | return "mainnet";
160 | }
161 | return networkType;
162 | }
163 |
--------------------------------------------------------------------------------
/src/prop-types.js:
--------------------------------------------------------------------------------
1 | import PropTypes from "prop-types";
2 |
3 | import {
4 | SITE_STATUS_ERROR,
5 | SITE_STATUS_READY,
6 | SITE_STATUS_LOADING,
7 | SITE_STATUS_UNLOADED,
8 | } from "./symbols";
9 | import { isAddress } from "web3-utils";
10 |
11 | const validatorCreator = (nonRequiredFunction) => {
12 | const validator = nonRequiredFunction;
13 |
14 | validator.isRequired = (props, propName, componentName) => {
15 | const value = props[propName];
16 |
17 | if (value === null || value === undefined || value === "") {
18 | return new Error(
19 | `Property ${propName} is required on ${componentName}, but ${value} was given.`
20 | );
21 | }
22 |
23 | return nonRequiredFunction(props, propName, componentName);
24 | };
25 |
26 | return validator;
27 | };
28 |
29 | const ethereumAddressValidator = (props, propName, componentName) => {
30 | const value = props[propName];
31 |
32 | if (value === null || value === undefined || value === "") {
33 | return null;
34 | }
35 |
36 | if (!isAddress(value)) {
37 | const valueType = typeof value;
38 | let nonAddress = null;
39 |
40 | if (valueType !== "object") {
41 | nonAddress = value.toString();
42 | }
43 |
44 | return new Error(
45 | `Invalid prop ${propName} supplied to ${componentName}. The provided value is not a valid ethereum address.${
46 | nonAddress && ` You provided "${nonAddress}"`
47 | }`
48 | );
49 | }
50 | };
51 |
52 | export const EthereumAddressType = validatorCreator(ethereumAddressValidator);
53 |
54 | export const SiteStatusType = PropTypes.oneOf([
55 | SITE_STATUS_ERROR,
56 | SITE_STATUS_READY,
57 | SITE_STATUS_LOADING,
58 | SITE_STATUS_UNLOADED,
59 | ]);
60 |
61 | export const NetworkItemType = PropTypes.shape({
62 | name: PropTypes.string,
63 | number: PropTypes.number,
64 | });
65 |
66 | // see ethereum-providers/
67 | export const EthereumProviderType = PropTypes.shape({
68 | id: PropTypes.string.isRequired,
69 | name: PropTypes.string.isRequired,
70 | type: PropTypes.string.isRequired,
71 | image: PropTypes.string.isRequired,
72 | strings: PropTypes.object.isRequired,
73 | });
74 |
--------------------------------------------------------------------------------
/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/src/routing-utils.js:
--------------------------------------------------------------------------------
1 | // // Preferences
2 | // export function parsePreferences(search = "") {
3 | // const searchParams = new URLSearchParams(search);
4 | // const path = searchParams.get("preferences") || "";
5 | // // Ignore labels if search does not contain a preferences path
6 | // const labels = searchParams.has("preferences")
7 | // ? searchParams.get("labels")
8 | // : "";
9 |
10 | // const [, section = "", subsection = ""] = path.split("/");
11 |
12 | // const data = {};
13 |
14 | // if (labels) {
15 | // data.labels = labels;
16 | // }
17 |
18 | // return { section, subsection, data };
19 | // }
20 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/src/suggested-funds.js:
--------------------------------------------------------------------------------
1 | import { useMemo } from "react";
2 | import { useFavoriteFunds } from "./contexts/FavoriteFundsContext";
3 | import { getRecommendedFunds } from "./known-funds";
4 | import { network } from "./environment";
5 |
6 | const RECOMMENDED_FUNDS = getRecommendedFunds(network.type);
7 |
8 | export function useSuggestedFunds(maxSuggestions = 6) {
9 | const { favoriteFunds } = useFavoriteFunds();
10 |
11 | const suggestedFunds = useMemo(() => {
12 | const funds = new Map(
13 | [...favoriteFunds].map((fund) => [fund.vaultId, fund])
14 | );
15 |
16 | // Keep filling with recommended orgs until we reach the max
17 | RECOMMENDED_FUNDS.forEach((fund) => {
18 | const { vaultId } = fund;
19 | if (funds.size < maxSuggestions && !funds.has(vaultId)) {
20 | funds.set(vaultId, fund);
21 | }
22 | });
23 | return [...funds.values()].sort((fund, fund2) => {
24 | const { vaultId } = fund;
25 | const { vaultId: vaultId2 } = fund2;
26 | return vaultId > vaultId2 ? 1 : -1;
27 | });
28 | }, [favoriteFunds, maxSuggestions]);
29 |
30 | return suggestedFunds;
31 | }
32 |
--------------------------------------------------------------------------------
/src/symbols.js:
--------------------------------------------------------------------------------
1 | export const SITE_STATUS_LOADING = Symbol("SITE_STATUS_LOADING");
2 | // export const TX_STATUS_ERROR = Symbol("TX_STATUS_ERROR");
3 | // export const TX_STATUS_PENDING = Symbol("TX_STATUS_PENDING");
4 | // export const TX_STATUS_SUCCESS = Symbol("TX_STATUS_SUCCESS");
5 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | import { POLL_DELAY_CONNECTIVITY } from "./constants";
2 | import { getWeb3 } from "./web3-utils";
3 |
4 | export const pollConnectivity = pollEvery((providers = [], onConnectivity) => {
5 | let lastFound = null;
6 | return {
7 | request: async () => {
8 | try {
9 | await Promise.all(
10 | providers.map((p) => getWeb3(p).eth.net.getNetworkType())
11 | );
12 | return true;
13 | } catch (err) {
14 | return false;
15 | }
16 | },
17 | onResult: (connected) => {
18 | if (connected !== lastFound) {
19 | lastFound = connected;
20 | onConnectivity(connected);
21 | }
22 | },
23 | };
24 | // web.eth.net.isListening()
25 | }, POLL_DELAY_CONNECTIVITY);
26 |
27 | export function pollEvery(fn, delay) {
28 | let timer = -1;
29 | let stop = false;
30 | const poll = async (request, onResult) => {
31 | let result;
32 | try {
33 | result = await request();
34 | } catch (err) {
35 | log("Polling failed for fn:", fn);
36 | log("Error:", err);
37 | // Stop polling and let requester handle
38 | throw err;
39 | }
40 |
41 | if (!stop) {
42 | onResult(result);
43 | timer = setTimeout(poll.bind(null, request, onResult), delay);
44 | }
45 | };
46 | return (...params) => {
47 | const { request, onResult } = fn(...params);
48 | poll(request, onResult);
49 | return () => {
50 | stop = true;
51 | clearTimeout(timer);
52 | };
53 | };
54 | }
55 |
56 | export function isElectron() {
57 | // See https://github.com/electron/electron/issues/2288
58 | return (
59 | typeof navigator === "object" &&
60 | typeof navigator.userAgent === "string" &&
61 | navigator.userAgent.indexOf("Electron") >= 0
62 | );
63 | }
64 |
65 | // return the first argument (named after lodash)
66 | export const identity = (arg) => arg;
67 |
68 | export function log(...params) {
69 | if (
70 | process.env.NODE_ENV !== "production" &&
71 | process.env.NODE_ENV !== "test"
72 | ) {
73 | console.log(...params);
74 | }
75 | }
76 |
77 | export const iOS =
78 | /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
79 |
80 | export const isSafari = /Version\/[\d.]+.*Safari/.test(navigator.userAgent);
81 |
--------------------------------------------------------------------------------
/src/web3-utils.js:
--------------------------------------------------------------------------------
1 | import Web3 from "web3";
2 | import { InvalidNetworkType, InvalidURI, NoConnection } from "./errors";
3 |
4 | // Cache web3 instances used in the app
5 | const web3Cache = new WeakMap();
6 |
7 | // Filter the value we get from getBalance() before passing it to BN.js.
8 | // This is because passing some values to BN.js can lead to an infinite loop
9 | // when .toString() is called. Returns "-1" when the value is invalid.
10 | //
11 | // See https://github.com/indutny/bn.js/issues/186
12 | export function filterBalanceValue(value) {
13 | if (value === null) {
14 | return "-1";
15 | }
16 | if (typeof value === "object") {
17 | value = String(value);
18 | }
19 | if (typeof value === "string") {
20 | return /^[0-9]+$/.test(value) ? value : "-1";
21 | }
22 | return "-1";
23 | }
24 |
25 | /**
26 | * Check address equality without checksums
27 | * @param {string} first First address
28 | * @param {string} second Second address
29 | * @returns {boolean} Address equality
30 | */
31 | export function addressesEqual(first, second) {
32 | first = first && first.toLowerCase();
33 | second = second && second.toLowerCase();
34 | return first === second;
35 | }
36 |
37 | const websocketRegex = /^wss?:\/\/.+/;
38 |
39 | /**
40 | * Check if the ETH node at the given URI is compatible for the current environment
41 | * @param {string} uri URI of the ETH node.
42 | * @param {string} expectedNetworkType The expected network type of the ETH node.
43 | * @returns {Promise} Resolves if the ETH node is compatible, otherwise throws:
44 | * - InvalidURI: URI given is not compatible (e.g. must be WebSockets)
45 | * - InvalidNetworkType: ETH node connected to wrong network
46 | * - NoConnection: Couldn't connect to URI
47 | */
48 | export async function checkValidEthNode(uri, expectedNetworkType) {
49 | // Must be websocket connection
50 | if (!websocketRegex.test(uri)) {
51 | throw new InvalidURI("The URI must use the WebSocket protocol");
52 | }
53 |
54 | try {
55 | const web3 = new Web3(uri);
56 | const connectedNetworkType = await web3.eth.net.getNetworkType();
57 | if (web3.currentProvider.disconnect) {
58 | web3.currentProvider.disconnect();
59 | } else {
60 | // Older versions of web3's providers didn't expose a generic interface for disconnecting
61 | web3.currentProvider.connection.close();
62 | }
63 |
64 | if (connectedNetworkType !== expectedNetworkType) {
65 | throw new InvalidNetworkType();
66 | }
67 | } catch (err) {
68 | if (err instanceof InvalidNetworkType) {
69 | throw err;
70 | }
71 | throw new NoConnection();
72 | }
73 |
74 | return true;
75 | }
76 |
77 | /**
78 | * Get cached web3 instance
79 | * @param {Web3.Provider} provider Web3 provider
80 | * @returns {Web3} The web3 instance
81 | */
82 | export function getWeb3(provider) {
83 | if (web3Cache.has(provider)) {
84 | return web3Cache.get(provider);
85 | }
86 | const web3 = new Web3(provider);
87 | web3Cache.set(provider, web3);
88 | return web3;
89 | }
90 |
91 | export async function getLatestBlockTimestamp(web3) {
92 | const { timestamp } = (await web3.eth.getBlock("latest")) || {};
93 | if (!timestamp) {
94 | throw new Error("Could not fetch the latest block timestamp");
95 | }
96 | return new Date(timestamp * 1000);
97 | }
98 |
99 | /**
100 | * Shorten an Ethereum address. `charsLength` allows to change the number of
101 | * characters on both sides of the ellipsis.
102 | *
103 | * Examples:
104 | * shortenAddress('0x19731977931271') // 0x1973…1271
105 | * shortenAddress('0x19731977931271', 2) // 0x19…71
106 | * shortenAddress('0x197319') // 0x197319 (already short enough)
107 | *
108 | * @param {string} address The address to shorten
109 | * @param {number} [charsLength=4] The number of characters to change on both sides of the ellipsis
110 | * @returns {string} The shortened address
111 | */
112 | export function shortenAddress(address, charsLength = 4) {
113 | const prefixLength = 2; // "0x"
114 | if (!address) {
115 | return "";
116 | }
117 | if (address.length < charsLength * 2 + prefixLength) {
118 | return address;
119 | }
120 | return (
121 | address.slice(0, charsLength + prefixLength) +
122 | "…" +
123 | address.slice(-charsLength)
124 | );
125 | }
126 |
--------------------------------------------------------------------------------