├── .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 |
52 |
68 |
79 |
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 |
46 | 47 |
59 |
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 |
119 |
120 | setVaultId2(event.target.value)} 123 | placeholder="vaultId (e.g. 6)" 124 | wide={true} 125 | css={` 126 | margin-bottom: 10px; 127 | `} 128 | /> 129 |
150 |
151 |
171 |
172 |
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 |
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 |
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 |
47 | {calltoaction} 48 | Try it out! 49 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
31 |
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 |
71 | 72 |
73 |
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 | 2 | 3 | 4 | 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 | 2 | 3 | 4 | 5 | 6 | 7 | 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 | --------------------------------------------------------------------------------