├── .gitignore ├── .prettierrc ├── Readme.md ├── react-crowdfunding ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt ├── src │ ├── App.css │ ├── App.test.tsx │ ├── App.tsx │ ├── assets │ │ ├── img │ │ │ ├── elrond-symbol.svg │ │ │ ├── elrond.svg │ │ │ └── heart.svg │ │ └── sass │ │ │ └── theme.scss │ ├── components │ │ ├── Denominate │ │ │ ├── formatters.tsx │ │ │ └── index.tsx │ │ ├── Layout │ │ │ ├── Footer │ │ │ │ └── index.jsx │ │ │ ├── Navbar │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ ├── Login │ │ │ ├── Ledger.tsx │ │ │ └── Wallet.tsx │ │ ├── PageNotFound │ │ │ └── index.tsx │ │ ├── PageState │ │ │ └── index.tsx │ │ └── PageTitle │ │ │ └── index.tsx │ ├── context │ │ ├── index.tsx │ │ ├── reducer.tsx │ │ └── state.tsx │ ├── contracts │ │ ├── Crowdfund.tsx │ │ ├── abis.js │ │ ├── addresses.js │ │ └── index.js │ ├── index.css │ ├── index.tsx │ ├── logo.svg │ ├── pages │ │ ├── Dashboard │ │ │ └── index.tsx │ │ └── Home │ │ │ └── index.tsx │ ├── react-app-env.d.ts │ ├── routes.ts │ ├── serviceWorker.ts │ ├── setupTests.ts │ └── storage │ │ └── session.tsx ├── tsconfig.json └── yarn.lock └── react-delegationdashboard ├── .env.example ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── preview.png ├── public ├── .htaccess ├── activation.svg ├── bls_c.wasm ├── contract.svg ├── delegation.svg ├── favicon.ico ├── index.html ├── leaf-solid.svg ├── manifest.json ├── nodes.svg ├── robots.txt ├── service.svg └── user.svg ├── src ├── App.test.tsx ├── App.tsx ├── assets │ ├── images │ │ ├── background.jpg │ │ ├── contract.svg │ │ ├── dashboard.png │ │ ├── delegation.svg │ │ ├── leaf-solid.svg │ │ ├── ledger-nano.svg │ │ ├── logo.svg │ │ ├── nodes.svg │ │ ├── owner.png │ │ └── service.svg │ └── styles │ │ ├── _components.scss │ │ ├── _elements.scss │ │ ├── _main.scss │ │ ├── _pages.scss │ │ ├── _utilities.scss │ │ ├── _variables.scss │ │ ├── components │ │ ├── cards.scss │ │ ├── footer.scss │ │ ├── header.scss │ │ ├── layout.scss │ │ ├── navbar.scss │ │ └── statcard.scss │ │ ├── pages │ │ ├── home.scss │ │ └── owner.scss │ │ └── theme.scss ├── components │ ├── ConfirmTransactionModal │ │ └── index.tsx │ ├── ContinueAndCloseButtons │ │ └── index.tsx │ ├── Denominate │ │ ├── formatters.tsx │ │ └── index.tsx │ ├── DropzonePem │ │ ├── PemUpload.tsx │ │ ├── Request.tsx │ │ ├── RequestVariablesModal.tsx │ │ └── index.tsx │ ├── Layout │ │ ├── APRCalculation.ts │ │ ├── Footer │ │ │ └── index.jsx │ │ ├── Navbar │ │ │ └── index.tsx │ │ └── index.tsx │ ├── ModalActionButton │ │ └── index.tsx │ ├── Overview │ │ ├── Cards │ │ │ ├── AutomaticActivationAction │ │ │ │ └── index.tsx │ │ │ ├── ReDelegateCapActivationAction │ │ │ │ └── index.tsx │ │ │ ├── SetPercentageFeeAction │ │ │ │ ├── SetPercentageFeeModal.tsx │ │ │ │ └── index.tsx │ │ │ ├── UpdateDelegationCapAction │ │ │ │ ├── DelegationCapModal.tsx │ │ │ │ └── index.tsx │ │ │ └── index.tsx │ │ ├── Header │ │ │ ├── SetAgencyMetaDataModal.tsx │ │ │ └── index.tsx │ │ ├── OwnerActionModal.tsx │ │ └── index.tsx │ ├── PageNotFound │ │ └── index.tsx │ ├── PageTitle │ │ └── index.tsx │ ├── StatCard │ │ └── index.tsx │ ├── State │ │ └── index.tsx │ └── TransactionStatus │ │ ├── StatusTxDetails.tsx │ │ ├── index.tsx │ │ └── txStatus.tsx ├── config.devnet.ts ├── config.mainnet.ts ├── config.testnet.ts ├── context │ ├── index.tsx │ ├── reducer.tsx │ └── state.tsx ├── contracts │ ├── ContractViews.ts │ ├── Delegation.tsx │ └── index.js ├── helpers │ ├── contractDataDefinitions.ts │ ├── decodePem.tsx │ ├── entireBalance.ts │ ├── index.ts │ ├── ledgerErrorCodes.ts │ ├── nominate.ts │ ├── types.ts │ ├── useDelegation.ts │ └── useInterval.ts ├── index.css ├── index.tsx ├── pages │ ├── Dashboard │ │ ├── Actions │ │ │ ├── ClaimRewardsAction │ │ │ │ ├── ClaimRewardsModal.tsx │ │ │ │ └── index.tsx │ │ │ ├── DelegateAction │ │ │ │ ├── DelegateModal.tsx │ │ │ │ └── index.tsx │ │ │ └── UndelegateAction │ │ │ │ ├── UndelegateModal.tsx │ │ │ │ └── index.tsx │ │ ├── Delegation │ │ │ └── index.tsx │ │ ├── PendingUndelegated │ │ │ ├── UndelegatedValueRow.tsx │ │ │ ├── UndelegatedValueType.ts │ │ │ └── index.tsx │ │ └── index.tsx │ ├── Home │ │ ├── Login │ │ │ ├── Wallet.tsx │ │ │ └── WalletConnect.tsx │ │ └── index.tsx │ ├── Ledger │ │ ├── AddressRow.tsx │ │ ├── AddressTable.tsx │ │ ├── ConfirmedAddress.tsx │ │ ├── LedgerConnect.tsx │ │ └── index.tsx │ ├── Owner │ │ ├── Nodes │ │ │ ├── AddNodeAction.tsx │ │ │ ├── NodeRow.tsx │ │ │ ├── helpers │ │ │ │ ├── index.ts │ │ │ │ ├── keysFunctions.ts │ │ │ │ ├── nodeType.ts │ │ │ │ ├── nodeTypes.ts │ │ │ │ └── stakeHooks.ts │ │ │ └── index.tsx │ │ └── index.tsx │ └── WalletConnect │ │ └── index.tsx ├── react-app-env.d.ts ├── routes.ts ├── serviceWorker.ts ├── setupTests.ts └── storage │ └── session.tsx └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # IDE 4 | .idea/ 5 | 6 | # dependencies 7 | /node_modules 8 | /.pnp 9 | .pnp.js 10 | 11 | # testing 12 | /coverage 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | .eslintcache 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | react-delegationdashboard/src/config.ts 26 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "printWidth": 100, 4 | "semi": true, 5 | "singleQuote": true, 6 | "jsxSingleQuote": false 7 | } 8 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Elrond dApp boilerplate examples 2 | 3 | This repo contains subfolders, each named after the framework in which it is built and the contract used as an example (-) 4 | 5 | To install one of the examples, either use this repo and follow the readme instructions in each project, or use the Elrond IDE when this feature will be available (link below). 6 | 7 | | Project | Framework | URL | Contract Tutorial 8 | |----------|:-------------:|:-------------:|:-------------:| 9 | | react-crowdfunding | React | [react-crowdfunding](https://github.com/ElrondNetwork/dapp-boilerplate.elrond.com/tree/master/react-crowdfunding) | [Crowdfunding Contract](https://docs.elrond.com/developers/tutorials/crowdfunding-p1/) 10 | | react-delegationdashboard | React | [react-delegationdashboard](https://github.com/ElrondNetwork/dapp-boilerplate.elrond.com/tree/master/react-delegationdashboard) | [Delegation Cheatsheet](https://docs.google.com/document/d/15wXAUIHBQmKefFSg5uY_MnKlgrjZ4zHhKCIOTWukfzE/edit) 11 | 12 | 13 | For more Smart Contract tutorials, [the documentation](https://docs.elrond.com/), plus [Elrond IDE](https://marketplace.visualstudio.com/items?itemName=Elrond.vscode-elrond-ide) - which is a frontend for erdpy - should be a good start. 14 | 15 | 16 | ## Developers 17 | 18 | The [Elrond Team](https://elrond.com/team/). 19 | 20 | ## Contribute 21 | 22 | One can contribute by creating *pull requests*, or by opening *issues* for discovered bugs or desired features. 23 | -------------------------------------------------------------------------------- /react-crowdfunding/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | .idea/ 4 | 5 | # dependencies 6 | /node_modules 7 | /.pnp 8 | .pnp.js 9 | 10 | # testing 11 | /coverage 12 | 13 | # production 14 | /build 15 | 16 | # misc 17 | .DS_Store 18 | .env.local 19 | .env.development.local 20 | .env.test.local 21 | .env.production.local 22 | 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | -------------------------------------------------------------------------------- /react-crowdfunding/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `yarn start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `yarn test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `yarn build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `yarn eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | -------------------------------------------------------------------------------- /react-crowdfunding/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dapp-boilerplate", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@elrondnetwork/erdjs": "^1.1.8", 7 | "@fortawesome/fontawesome-svg-core": "^1.2.32", 8 | "@fortawesome/free-solid-svg-icons": "^5.15.1", 9 | "@fortawesome/react-fontawesome": "^0.1.12", 10 | "@testing-library/jest-dom": "^4.2.4", 11 | "@testing-library/react": "^9.3.2", 12 | "@testing-library/user-event": "^7.1.2", 13 | "@types/jest": "^24.0.0", 14 | "@types/node": "^12.0.0", 15 | "@types/react": "^16.9.0", 16 | "@types/react-dom": "^16.9.0", 17 | "@types/react-router-dom": "^5.1.6", 18 | "bootstrap": "^4.5.3", 19 | "moment": "^2.29.1", 20 | "node-sass": "^4.0.0", 21 | "react": "^16.13.1", 22 | "react-bootstrap": "^1.4.0", 23 | "react-dom": "^16.13.1", 24 | "react-router-dom": "^5.2.0", 25 | "react-scripts": "^4.0.3", 26 | "reinspect": "^1.1.0", 27 | "typescript": "^3.8.3" 28 | }, 29 | "scripts": { 30 | "start": "HTTPS=true react-scripts start", 31 | "build": "react-scripts build", 32 | "test": "react-scripts test", 33 | "eject": "react-scripts eject" 34 | }, 35 | "eslintConfig": { 36 | "extends": "react-app" 37 | }, 38 | "browserslist": { 39 | "production": [ 40 | ">0.2%", 41 | "not dead", 42 | "not op_mini all" 43 | ], 44 | "development": [ 45 | "last 1 chrome version", 46 | "last 1 firefox version", 47 | "last 1 safari version" 48 | ] 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /react-crowdfunding/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multiversx/mx-deprecated-starter-dapp/eb50639bdf87941689a102371e8ce823f8e08003/react-crowdfunding/public/favicon.ico -------------------------------------------------------------------------------- /react-crowdfunding/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /react-crowdfunding/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multiversx/mx-deprecated-starter-dapp/eb50639bdf87941689a102371e8ce823f8e08003/react-crowdfunding/public/logo192.png -------------------------------------------------------------------------------- /react-crowdfunding/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multiversx/mx-deprecated-starter-dapp/eb50639bdf87941689a102371e8ce823f8e08003/react-crowdfunding/public/logo512.png -------------------------------------------------------------------------------- /react-crowdfunding/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /react-crowdfunding/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /react-crowdfunding/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 | -------------------------------------------------------------------------------- /react-crowdfunding/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | const { getByText } = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /react-crowdfunding/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; 3 | import Layout from './components/Layout'; 4 | import routes from './routes'; 5 | import { ContextProvider } from './context'; 6 | import './App.css'; 7 | 8 | function App() { 9 | return ( 10 | 11 | 12 | 13 | 14 | {routes.map((route, i) => ( 15 | 21 | ))} 22 | 23 | 24 | 25 | 26 | ); 27 | } 28 | 29 | export default App; 30 | -------------------------------------------------------------------------------- /react-crowdfunding/src/assets/img/elrond-symbol.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /react-crowdfunding/src/assets/img/heart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | heart 5 | Created with Sketch. 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /react-crowdfunding/src/components/Denominate/formatters.tsx: -------------------------------------------------------------------------------- 1 | function format( 2 | big: string, 3 | denomination: number, 4 | decimals: number, 5 | showLastNonZeroDecimal: boolean, 6 | addCommas: boolean 7 | ) { 8 | showLastNonZeroDecimal = 9 | typeof showLastNonZeroDecimal !== 'undefined' ? showLastNonZeroDecimal : false; 10 | let array = big.toString().split(''); 11 | if (denomination !== 0) { 12 | // make sure we have enough characters 13 | while (array.length < denomination + 1) { 14 | array.unshift('0'); 15 | } 16 | // add our dot 17 | array.splice(array.length - denomination, 0, '.'); 18 | // make sure there are enough decimals after the dot 19 | while (array.length - array.indexOf('.') <= decimals) { 20 | array.push('0'); 21 | } 22 | 23 | if (showLastNonZeroDecimal) { 24 | let nonZeroDigitIndex = 0; 25 | for (let i = array.length - 1; i > 0; i--) { 26 | if (array[i] !== '0') { 27 | nonZeroDigitIndex = i + 1; 28 | break; 29 | } 30 | } 31 | const decimalsIndex = array.indexOf('.') + decimals + 1; 32 | const sliceIndex = Math.max(decimalsIndex, nonZeroDigitIndex); 33 | array = array.slice(0, sliceIndex); 34 | } else { 35 | // trim unnecessary characters after the dot 36 | array = array.slice(0, array.indexOf('.') + decimals + 1); 37 | } 38 | } 39 | if (addCommas) { 40 | // add comas every 3 characters 41 | array = array.reverse(); 42 | const reference = denomination ? array.length - array.indexOf('.') - 1 : array.length; 43 | const count = Math.floor(reference / 3); 44 | for (let i = 1; i <= count; i++) { 45 | const position = array.indexOf('.') + 3 * i + i; 46 | if (position !== array.length) { 47 | array.splice(position, 0, ','); 48 | } 49 | } 50 | array = array.reverse(); 51 | } 52 | 53 | const allDecimalsZero = array 54 | .slice(array.indexOf('.') + 1) 55 | .every(digit => digit.toString() === '0'); 56 | 57 | const string = array.join(''); 58 | 59 | if (allDecimalsZero) { 60 | return string.split('.')[0]; 61 | } 62 | 63 | return decimals === 0 ? string.split('.').join('') : string; 64 | } 65 | 66 | interface DenominateType { 67 | input: string; 68 | denomination: number; 69 | decimals: number; 70 | showLastNonZeroDecimal: boolean; 71 | addCommas?: boolean; 72 | } 73 | 74 | export default function denominate({ 75 | input, 76 | denomination, 77 | decimals, 78 | showLastNonZeroDecimal = false, 79 | addCommas = true, 80 | }: DenominateType): string { 81 | if (input === '...') { 82 | return input; 83 | } 84 | if (input === '' || input === '0' || input === undefined) { 85 | input = '0'; 86 | } 87 | return format(input, denomination, decimals, showLastNonZeroDecimal, addCommas); 88 | } 89 | -------------------------------------------------------------------------------- /react-crowdfunding/src/components/Denominate/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useContext } from '../../context'; 3 | import denominate from './formatters'; 4 | 5 | interface DenominateType { 6 | value: string; 7 | showLastNonZeroDecimal?: boolean; 8 | showErd?: boolean; 9 | decimals?: number; 10 | } 11 | 12 | const Denominate = ({ 13 | value, 14 | showLastNonZeroDecimal = false, 15 | showErd = true, 16 | decimals, 17 | }: DenominateType) => { 18 | const { denomination, decimals: configDecimals } = useContext(); 19 | 20 | decimals = decimals !== undefined ? decimals : configDecimals; 21 | 22 | const denominatedValue = denominate({ 23 | input: value, 24 | denomination, 25 | decimals, 26 | showLastNonZeroDecimal, 27 | }); 28 | 29 | const valueParts = denominatedValue.split('.'); 30 | const hasNoDecimals = valueParts.length === 1; 31 | const isNotZero = denominatedValue !== '0'; 32 | 33 | if (decimals > 0 && hasNoDecimals && isNotZero) { 34 | let zeros = ''; 35 | 36 | for (let i = 1; i <= decimals; i++) { 37 | zeros = zeros + '0'; 38 | } 39 | 40 | valueParts.push(zeros); 41 | } 42 | 43 | return ( 44 | 45 | {valueParts[0]} 46 | {valueParts.length > 1 && .{valueParts[1]}} 47 | {showErd &&  eGLD} 48 | 49 | ); 50 | }; 51 | 52 | export default Denominate; 53 | -------------------------------------------------------------------------------- /react-crowdfunding/src/components/Layout/Footer/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ReactComponent as HeartIcon } from '../../../assets/img/heart.svg'; 3 | 4 | const Footer = () => { 5 | return ( 6 | 19 | ); 20 | }; 21 | 22 | export default Footer; 23 | -------------------------------------------------------------------------------- /react-crowdfunding/src/components/Layout/Navbar/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Navbar as BsNavbar, NavItem, Nav } from 'react-bootstrap'; 3 | import { ReactComponent as ElrondLogo } from '../../../assets/img/elrond.svg'; 4 | import {useContext, useDispatch} from "../../../context"; 5 | 6 | const Navbar = () => { 7 | const {loggedIn} = useContext(); 8 | const dispatch = useDispatch(); 9 | 10 | const logOut = () => { 11 | dispatch({type: "logout"}); 12 | }; 13 | 14 | return ( 15 | 16 |
17 | 18 | 19 | Dapp 20 | 21 | 22 | 29 |
30 |
31 | ); 32 | }; 33 | 34 | export default Navbar; 35 | -------------------------------------------------------------------------------- /react-crowdfunding/src/components/Layout/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Footer from './Footer'; 3 | import Navbar from './Navbar'; 4 | 5 | const Layout = ({children}: { children: React.ReactNode }) => { 6 | return ( 7 |
8 | 9 |
{children}
10 |
11 |
12 | ); 13 | }; 14 | 15 | export default Layout; 16 | -------------------------------------------------------------------------------- /react-crowdfunding/src/components/Login/Ledger.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {HWProvider, ProxyProvider} from "@elrondnetwork/erdjs" 3 | import {useDispatch} from "../../context"; 4 | 5 | const LedgerLogin = () => { 6 | const dispatch = useDispatch(); 7 | 8 | const handleOnClick = () => { 9 | const httpProvider = new ProxyProvider(""); 10 | const hwWalletP = new HWProvider(httpProvider); 11 | 12 | dispatch({type: "loading", loading: true}); 13 | hwWalletP.init() 14 | .then((success: any) => { 15 | if (!success) { 16 | dispatch({type: "loading", loading: false}); 17 | console.warn("could not initialise ledger app, make sure Elrond app is open"); 18 | return; 19 | } 20 | 21 | hwWalletP.login() 22 | .then(address => { 23 | // Set this provider as default inside the app 24 | dispatch({type: 'setProvider', provider: hwWalletP}); 25 | dispatch({type: "login", address}); 26 | }).catch((err: any) => { 27 | dispatch({type: "loading", loading: false}); 28 | console.warn(err); 29 | }); 30 | 31 | }).catch((err: any) => { 32 | dispatch({type: "loading", loading: false}); 33 | console.warn("could not initialise ledger app, make sure Elrond app is open", err) 34 | }) 35 | }; 36 | return ( 37 |
38 |
39 |
40 |

Ledger

41 | 42 |

43 | Secure Ledger login. 44 |

45 | 46 | 49 |
50 |
51 |
52 | ) 53 | }; 54 | 55 | export default LedgerLogin; 56 | -------------------------------------------------------------------------------- /react-crowdfunding/src/components/Login/Wallet.tsx: -------------------------------------------------------------------------------- 1 | import React, {useEffect} from "react"; 2 | import {useContext, useDispatch} from "../../context"; 3 | import {getItem, removeItem, setItem} from "../../storage/session"; 4 | 5 | const WalletLogin = () => { 6 | 7 | const dispatch = useDispatch(); 8 | const {dapp} = useContext(); 9 | const handleOnClick = () => { 10 | dispatch({type: 'loading', loading: true}); 11 | dapp.provider.init() 12 | .then(initialised => { 13 | if (initialised) { 14 | // Wallet provider will redirect, we can set a session information so we know when we are getting back 15 | // that we initiated a wallet provider login 16 | setItem('wallet_login', {}, 60); // Set a 60s session only 17 | dapp.provider.login(); 18 | } else { 19 | dispatch({type: 'loading', loading: true}); 20 | console.warn('Something went wrong trying to redirect to wallet login..'); 21 | } 22 | }).catch(err => { 23 | dispatch({type: 'loading', loading: false}); 24 | console.warn(err); 25 | }); 26 | }; 27 | 28 | // The wallet login component can check for the session and the address get param 29 | useEffect(() => { 30 | if (getItem('wallet_login')) { 31 | dispatch({type: 'loading', loading: true}); 32 | dapp.provider.init() 33 | .then(initialised => { 34 | if (!initialised) { 35 | dispatch({type: 'loading', loading: false}); 36 | return; 37 | } 38 | 39 | dapp.provider.getAddress() 40 | .then(address => { 41 | removeItem('wallet_login'); 42 | dispatch({type: "login", address}); 43 | }).catch(err => { 44 | dispatch({type: 'loading', loading: false}); 45 | }); 46 | }) 47 | 48 | } 49 | 50 | }, [dapp.provider, dispatch]); 51 | 52 | return ( 53 |
54 |
55 |
56 |

Wallet

57 | 58 |

59 | Login with keystore file. 60 |
Login with: 61 |

62 | 63 | 66 |
67 |
68 |
69 | ) 70 | }; 71 | 72 | export default WalletLogin; 73 | -------------------------------------------------------------------------------- /react-crowdfunding/src/components/PageNotFound/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const PageNotFound = () => { 4 | return ( 5 |
6 | 404 7 |
8 | ); 9 | }; 10 | 11 | export default PageNotFound; 12 | -------------------------------------------------------------------------------- /react-crowdfunding/src/components/PageState/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | const PageState = ({ 4 | title, 5 | description, 6 | svgComponent, 7 | className, 8 | spin = false, 9 | }: { 10 | title?: string; 11 | description?: string | React.ReactNode; 12 | svgComponent: React.ReactNode; 13 | className?: string; 14 | spin?: boolean; 15 | }) => { 16 | return ( 17 |
18 |
19 | {spin ? ( 20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | ) : ( 29 |
30 |
31 |
32 |
{svgComponent}
33 | {title &&

{title}

} 34 | {description &&
{description}
} 35 |
36 |
37 |
38 | )} 39 |
40 |
41 | ); 42 | }; 43 | 44 | export default PageState; 45 | -------------------------------------------------------------------------------- /react-crowdfunding/src/components/PageTitle/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, memo } from 'react'; 2 | 3 | const withPageTitle = (title: string, Component: React.ComponentType) => () => { 4 | const Memoized = memo(Component); 5 | 6 | useEffect(() => { 7 | document.title = title; 8 | }, []); 9 | return ; 10 | }; 11 | 12 | export default withPageTitle; 13 | -------------------------------------------------------------------------------- /react-crowdfunding/src/context/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StateType, initialState } from './state'; 3 | import { DispatchType, reducer } from "./reducer"; 4 | 5 | export interface ContextType { 6 | children: React.ReactNode; 7 | } 8 | 9 | const Context = React.createContext(undefined); 10 | const Dispatch = React.createContext(undefined); 11 | 12 | function ContextProvider({ children }: ContextType) { 13 | const [state, dispatch] = React.useReducer(reducer, initialState()); 14 | 15 | return ( 16 | 17 | {children} 18 | 19 | ); 20 | } 21 | 22 | function useContext() { 23 | const context = React.useContext(Context); 24 | if (context === undefined) { 25 | throw new Error('useState must be used within a Context.Provider'); 26 | } 27 | return context; 28 | } 29 | 30 | 31 | function useDispatch() { 32 | const context = React.useContext(Dispatch); 33 | if (context === undefined) { 34 | throw new Error('useDispatch must be used within a Dispatch.Provider'); 35 | } 36 | return context; 37 | } 38 | 39 | export { ContextProvider, useContext, useDispatch }; 40 | -------------------------------------------------------------------------------- /react-crowdfunding/src/context/reducer.tsx: -------------------------------------------------------------------------------- 1 | import {initialState, StateType} from "./state"; 2 | import {setItem, removeItem} from '../storage/session'; 3 | 4 | export type DispatchType = (action: ActionType) => void; 5 | 6 | export type ActionType = 7 | | { type: 'login'; address: StateType['address'] } 8 | | { type: 'logout' } 9 | | { type: 'loading'; loading: StateType['loading'] } 10 | | { type: 'setProvider'; provider: StateType['dapp']["provider"] } 11 | ; 12 | 13 | export function reducer(state: StateType, action: ActionType): StateType { 14 | switch (action.type) { 15 | case 'login': { 16 | const { address } = action; 17 | setItem('logged_in', true); 18 | setItem('address', address); 19 | return { 20 | ...state, 21 | address, 22 | loggedIn: true, 23 | }; 24 | } 25 | 26 | case 'loading': { 27 | const { loading } = action; 28 | return { 29 | ...state, 30 | loading 31 | } 32 | } 33 | 34 | case 'setProvider': { 35 | const { provider } = action; 36 | return { 37 | ...state, 38 | dapp: { 39 | ...state.dapp, 40 | provider: provider, 41 | } 42 | } 43 | } 44 | 45 | case 'logout': { 46 | removeItem('logged_in'); 47 | removeItem('address'); 48 | return initialState() 49 | } 50 | 51 | default: { 52 | throw new Error(`Unhandled action type: ${action!.type}`); 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /react-crowdfunding/src/context/state.tsx: -------------------------------------------------------------------------------- 1 | import {IDappProvider, WalletProvider, ProxyProvider, WALLET_PROVIDER_TESTNET} from "@elrondnetwork/erdjs"; 2 | import {getItem} from '../storage/session'; 3 | 4 | interface DappState { 5 | provider: IDappProvider, 6 | proxy: ProxyProvider, 7 | } 8 | 9 | export interface StateType { 10 | dapp: DappState, 11 | loading: boolean, 12 | error: string, 13 | loggedIn: boolean, 14 | address: string, 15 | denomination: number, 16 | decimals: number, 17 | } 18 | 19 | export const initialState = () => { 20 | return { 21 | denomination: 18, 22 | decimals: 2, 23 | dapp: { 24 | provider: new WalletProvider(WALLET_PROVIDER_TESTNET), 25 | //provider: new WalletProvider("https://localhost:3000/dapp/init"), 26 | proxy: new ProxyProvider("https://testnet-api.elrond.com", 4000), 27 | }, 28 | loading: false, 29 | error: '', 30 | loggedIn: !!getItem("logged_in"), 31 | address: getItem("address"), 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /react-crowdfunding/src/contracts/Crowdfund.tsx: -------------------------------------------------------------------------------- 1 | import {SmartContract, Address, ProxyProvider, ContractFunction, 2 | Transaction, TransactionPayload, Balance, GasLimit, IDappProvider, 3 | WalletProvider, HWProvider} from "@elrondnetwork/erdjs"; 4 | import BigNumber from "bignumber.js"; 5 | import { setItem } from '../storage/session'; 6 | 7 | export default class Crowdfund { 8 | contract: SmartContract; 9 | proxyProvider: ProxyProvider; 10 | signerProvider?: IDappProvider; 11 | 12 | constructor(contractAddress = '', provider: ProxyProvider, signer?: IDappProvider) { 13 | const address = new Address(contractAddress); 14 | this.contract = new SmartContract({address}); 15 | this.proxyProvider = provider; 16 | this.signerProvider = signer; 17 | } 18 | 19 | async currentFunds(): Promise { 20 | const func = new ContractFunction("currentFunds"); 21 | const qResponse = await this.contract.runQuery(this.proxyProvider, {func}); 22 | qResponse.assertSuccess(); 23 | 24 | return new BigNumber(qResponse.firstResult()?.asBigInt.toString() || 0); 25 | } 26 | 27 | async sendFunds(): Promise { 28 | if (!this.signerProvider) { 29 | throw new Error("You need a singer to send a transaction, use either WalletProvider or LedgerProvider"); 30 | } 31 | 32 | switch (this.signerProvider.constructor) { 33 | case WalletProvider: 34 | return this.sendFundsWalletProvider(); 35 | case HWProvider: 36 | return this.sendFundsHWProvider(); 37 | default: 38 | console.warn("invalid signerProvider"); 39 | } 40 | 41 | return true; 42 | } 43 | 44 | private async sendFundsWalletProvider(): Promise { 45 | const func = new ContractFunction("fund"); 46 | let payload = TransactionPayload.contractCall() 47 | .setFunction(func) 48 | .build(); 49 | 50 | let transaction = new Transaction({ 51 | receiver: this.contract.getAddress(), 52 | value: new Balance(BigInt(3)), 53 | gasLimit: new GasLimit(10000000), 54 | data: payload 55 | }); 56 | 57 | // Can use something like this to handle callback redirect 58 | setItem("transaction_identifier", true, 120); 59 | // @ts-ignore 60 | await this.signerProvider.sendTransaction(transaction); 61 | 62 | return true; 63 | } 64 | private async sendFundsHWProvider(): Promise { 65 | const func = new ContractFunction("fund"); 66 | let payload = TransactionPayload.contractCall() 67 | .setFunction(func) 68 | .build(); 69 | 70 | let transaction = new Transaction({ 71 | receiver: this.contract.getAddress(), 72 | value: new Balance(BigInt(3)), 73 | gasLimit: new GasLimit(10000000), 74 | data: payload 75 | }); 76 | 77 | // @ts-ignore 78 | await this.signerProvider.sendTransaction(transaction); 79 | 80 | return true; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /react-crowdfunding/src/contracts/abis.js: -------------------------------------------------------------------------------- 1 | const abis = { 2 | 3 | }; 4 | 5 | export default abis; 6 | -------------------------------------------------------------------------------- /react-crowdfunding/src/contracts/addresses.js: -------------------------------------------------------------------------------- 1 | const addresses = { 2 | "crowdfunding_testnet": "erd1qqqqqqqqqqqqqpgqhf8qase6a8nw8phjwm2wd7uvkutexk67d8sszf9xuu" 3 | }; 4 | 5 | export default addresses; 6 | -------------------------------------------------------------------------------- /react-crowdfunding/src/contracts/index.js: -------------------------------------------------------------------------------- 1 | export { default as abis } from "./abis"; 2 | export { default as addresses } from "./addresses"; 3 | export { default as Crowdfund } from "./Crowdfund"; 4 | -------------------------------------------------------------------------------- /react-crowdfunding/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 | -------------------------------------------------------------------------------- /react-crowdfunding/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { StateInspector } from 'reinspect'; 4 | import App from './App'; 5 | import './assets/sass/theme.scss'; 6 | 7 | let MountedApp = ( 8 | 9 | 10 | 11 | ); 12 | 13 | if (process.env.NODE_ENV === 'development') { 14 | MountedApp = ( 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | } 22 | 23 | ReactDOM.render(MountedApp, document.getElementById('root')); -------------------------------------------------------------------------------- /react-crowdfunding/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /react-crowdfunding/src/pages/Dashboard/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Redirect} from 'react-router-dom'; 3 | import { faExchangeAlt, faArrowUp } from '@fortawesome/free-solid-svg-icons'; 4 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 5 | 6 | import PageState from "../../components/PageState"; 7 | import Denominate from "../../components/Denominate"; 8 | import {useContext} from "../../context"; 9 | import {addresses, Crowdfund} from '../../contracts'; 10 | 11 | const Dashboard = () => { 12 | 13 | const { loggedIn, address, dapp } = useContext(); 14 | if (!loggedIn) { 15 | return 16 | } 17 | 18 | const handleSendFunds = () => { 19 | const crowdfundContract = new Crowdfund(addresses["crowdfunding_testnet"], dapp.proxy, dapp.provider); 20 | crowdfundContract.sendFunds().then(); 21 | }; 22 | 23 | return ( 24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | Your address: 34 | {address} 35 |
36 |
37 | Contract address: 38 | {addresses["crowdfunding_testnet"]} 39 |
40 |
41 |

42 | 43 |

44 |
45 |
46 | 60 |
61 |
62 |
63 | } 65 | title="No info to show" 66 | /> 67 |
68 |
69 |
70 |
71 |
72 |
73 | 74 | ) 75 | }; 76 | 77 | export default Dashboard; 78 | -------------------------------------------------------------------------------- /react-crowdfunding/src/pages/Home/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { Redirect } from 'react-router-dom'; 3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 4 | import { faBan } from '@fortawesome/free-solid-svg-icons'; 5 | import BigNumber from "bignumber.js"; 6 | import PageState from "../../components/PageState"; 7 | import {useContext} from "../../context"; 8 | import LedgerLogin from "../../components/Login/Ledger"; 9 | import WalletLogin from "../../components/Login/Wallet"; 10 | import {addresses, Crowdfund} from '../../contracts'; 11 | import Denominate from '../../components/Denominate'; 12 | 13 | const Home = () => { 14 | const {loading, error, loggedIn, dapp} = useContext(); 15 | const [raised, setRaised] = useState(new BigNumber(0)); 16 | const [valueLoading, setValueLoading] = useState(true); 17 | const ref = React.useRef(null); 18 | 19 | useEffect(() => { 20 | const crowdfundContract = new Crowdfund(addresses["crowdfunding_testnet"], dapp.proxy); 21 | crowdfundContract.currentFunds().then((value: BigNumber) => { 22 | setRaised(value); 23 | }).catch(err => console.warn(err)) 24 | .finally(() => setValueLoading(false)); 25 | }, []); 26 | 27 | return ( 28 |
29 | { 30 | error ? 31 | } 33 | title="Unavailable" 34 | className="dapp-icon icon-medium" 35 | /> : 36 | loggedIn ? 37 | : 38 | loading ? 39 | } spin /> : 40 | 41 |
42 |

Welcome to our Crowdfunding App

43 |

We currently raised  44 | {valueLoading ? 45 | "..." 46 | : 47 | 48 | 49 | 50 | } 51 |

52 | 53 | 54 |
55 | } 56 |
57 | ) 58 | }; 59 | 60 | export default Home; 61 | -------------------------------------------------------------------------------- /react-crowdfunding/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /react-crowdfunding/src/routes.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Dashboard from "./pages/Dashboard"; 3 | import Home from "./pages/Home"; 4 | import withPageTitle from "./components/PageTitle"; 5 | 6 | interface RouteType { 7 | path: string; 8 | title: string; 9 | component: any; 10 | } 11 | 12 | const routes: RouteType[] = [ 13 | { 14 | path: '/', 15 | title: 'Home', 16 | component: Home, 17 | }, 18 | { 19 | path: '/dashboard', 20 | title: 'Dashboard', 21 | component: Dashboard, 22 | }, 23 | ]; 24 | 25 | const wrappedRoutes = () => { 26 | return routes.map((route) => { 27 | const title = route.title ? `${route.title} • Elrond Dapp` : 'Elrond Dapp'; 28 | return { 29 | path: route.path, 30 | component: (withPageTitle(title, route.component) as any) as React.ComponentClass<{}, any>, 31 | }; 32 | }); 33 | }; 34 | 35 | export default wrappedRoutes(); 36 | -------------------------------------------------------------------------------- /react-crowdfunding/src/setupTests.ts: -------------------------------------------------------------------------------- 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/extend-expect'; 6 | -------------------------------------------------------------------------------- /react-crowdfunding/src/storage/session.tsx: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | 3 | export const setItem = (key: string, item: any, ttl: number = 3600) => { 4 | const expires = moment().unix() + ttl; 5 | sessionStorage.setItem(key, JSON.stringify({ 6 | expires, 7 | data: item, 8 | })); 9 | }; 10 | 11 | export const getItem = (key: string): any => { 12 | const item = sessionStorage.getItem(key); 13 | if (!item) { 14 | return null; 15 | } 16 | 17 | const deserializedItem = JSON.parse(item); 18 | if (!deserializedItem) { 19 | return null; 20 | } 21 | 22 | if (!deserializedItem.hasOwnProperty('expires') || !deserializedItem.hasOwnProperty('data')) { 23 | return null; 24 | } 25 | 26 | const expired = moment().unix() >= deserializedItem.expires; 27 | if (expired) { 28 | sessionStorage.removeItem(key); 29 | return null; 30 | } 31 | 32 | return deserializedItem.data; 33 | }; 34 | 35 | export const removeItem = (key: string) => sessionStorage.removeItem(key); 36 | -------------------------------------------------------------------------------- /react-crowdfunding/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2015", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "es2015" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "noEmit": true, 20 | "jsx": "react" 21 | }, 22 | "include": [ 23 | "src" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /react-delegationdashboard/.env.example: -------------------------------------------------------------------------------- 1 | PUBLIC_URL=/ -------------------------------------------------------------------------------- /react-delegationdashboard/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | .idea/ 4 | 5 | # dependencies 6 | /node_modules 7 | /.pnp 8 | .pnp.js 9 | 10 | # testing 11 | /coverage 12 | 13 | # production 14 | /build 15 | 16 | # misc 17 | .DS_Store 18 | .env.local 19 | .env.development.local 20 | .env.test.local 21 | .env.production.local 22 | .env 23 | 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* -------------------------------------------------------------------------------- /react-delegationdashboard/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dapp-boilerplate", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@elrondnetwork/erdjs": "8.0.0", 7 | "@fortawesome/fontawesome-svg-core": "^1.2.34", 8 | "@fortawesome/free-solid-svg-icons": "^5.15.2", 9 | "@fortawesome/react-fontawesome": "^0.1.12", 10 | "@testing-library/jest-dom": "^4.2.4", 11 | "@testing-library/react": "^9.3.2", 12 | "@testing-library/user-event": "^7.1.2", 13 | "@types/jest": "^24.0.0", 14 | "@types/react": "^16.14.3", 15 | "@types/react-dom": "^16.9.0", 16 | "@types/react-router-dom": "^5.1.6", 17 | "axios": "^0.21.1", 18 | "bignumber.js": "^9.0.1", 19 | "bootstrap": "^4.6.0", 20 | "formik": "^2.2.6", 21 | "immer": "^8.0.1", 22 | "moment": "^2.29.1", 23 | "node-sass": "^4.0.0", 24 | "qrcode": "^1.4.4", 25 | "react": "^16.13.1", 26 | "react-bootstrap": "1.4.3", 27 | "react-dom": "^16.13.1", 28 | "react-dropzone": "^11.3.1", 29 | "react-inlinesvg": "^2.2.2", 30 | "react-modal": "3.12.1", 31 | "react-router-dom": "^5.2.0", 32 | "react-scripts": "4.0.1", 33 | "reinspect": "^1.1.0", 34 | "yup": "^0.32.8" 35 | }, 36 | "scripts": { 37 | "start": "HTTPS=true react-scripts start", 38 | "build": "react-scripts build", 39 | "test": "react-scripts test", 40 | "eject": "react-scripts eject", 41 | "build-mainnet": "npm run copy-mainnet-config & npm run build", 42 | "build-testnet": "npm run copy-testnet-config & npm run build", 43 | "build-devnet": "npm run copy-devnet-config & npm run build", 44 | "copy-mainnet-config": "run-script-os", 45 | "copy-testnet-config": "run-script-os", 46 | "copy-devnet-config": "run-script-os", 47 | "copy-mainnet-config:nix": "cp ./src/config.mainnet.ts ./src/config.ts", 48 | "copy-testnet-config:nix": "cp ./src/config.testnet.ts ./src/config.ts", 49 | "copy-devnet-config:nix": "cp ./src/config.devnet.ts ./src/config.ts", 50 | "copy-mainnet-config:windows": "copy .\\src\\config.mainnet.ts .\\src\\config.ts", 51 | "copy-testnet-config:windows": "copy .\\src\\config.testnet.ts .\\src\\config.ts", 52 | "copy-devnet-config:windows": "copy .\\src\\config.devnet.ts .\\src\\config.ts" 53 | }, 54 | "eslintConfig": { 55 | "extends": "react-app" 56 | }, 57 | "browserslist": { 58 | "production": [ 59 | ">0.2%", 60 | "not dead", 61 | "not op_mini all" 62 | ], 63 | "development": [ 64 | "last 1 chrome version", 65 | "last 1 firefox version", 66 | "last 1 safari version" 67 | ] 68 | }, 69 | "devDependencies": { 70 | "@types/node": "^14.14.25", 71 | "@types/qrcode": "^1.4.0", 72 | "@typescript-eslint/eslint-plugin": "^4.14.2", 73 | "@typescript-eslint/parser": "^4.14.2", 74 | "eslint": "^7.19.0", 75 | "eslint-config-standard": "^16.0.2", 76 | "eslint-plugin-import": "^2.22.1", 77 | "eslint-plugin-node": "^11.1.0", 78 | "eslint-plugin-promise": "^4.2.1", 79 | "eslint-plugin-react": "^7.22.0", 80 | "run-script-os": "^1.1.5", 81 | "typescript": "^4.1.2" 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /react-delegationdashboard/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multiversx/mx-deprecated-starter-dapp/eb50639bdf87941689a102371e8ce823f8e08003/react-delegationdashboard/preview.png -------------------------------------------------------------------------------- /react-delegationdashboard/public/.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine on 2 | # Don't rewrite files or directories 3 | RewriteCond %{REQUEST_FILENAME} -f [OR] 4 | RewriteCond %{REQUEST_FILENAME} -d 5 | RewriteRule ^ - [L] 6 | # Rewrite everything else to index.html to allow html5 state links 7 | RewriteRule ^ index.html [L] 8 | -------------------------------------------------------------------------------- /react-delegationdashboard/public/activation.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /react-delegationdashboard/public/bls_c.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multiversx/mx-deprecated-starter-dapp/eb50639bdf87941689a102371e8ce823f8e08003/react-delegationdashboard/public/bls_c.wasm -------------------------------------------------------------------------------- /react-delegationdashboard/public/contract.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /react-delegationdashboard/public/delegation.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /react-delegationdashboard/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multiversx/mx-deprecated-starter-dapp/eb50639bdf87941689a102371e8ce823f8e08003/react-delegationdashboard/public/favicon.ico -------------------------------------------------------------------------------- /react-delegationdashboard/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | 23 | 27 | Delegation Manager 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /react-delegationdashboard/public/leaf-solid.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /react-delegationdashboard/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Delegation Manager", 3 | "name": "Delegation Manager", 4 | "icons": [], 5 | "start_url": ".", 6 | "display": "standalone", 7 | "theme_color": "#000000", 8 | "background_color": "#ffffff" 9 | } 10 | -------------------------------------------------------------------------------- /react-delegationdashboard/public/nodes.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /react-delegationdashboard/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /react-delegationdashboard/public/service.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /react-delegationdashboard/public/user.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | const { getByText } = render(); 7 | const linkElement = getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; 3 | import Layout from './components/Layout'; 4 | import routes from './routes'; 5 | import { ContextProvider } from './context'; 6 | 7 | function App() { 8 | return ( 9 | 10 | 11 | 12 | {routes.map((route, i) => ( 13 | 14 | 15 | 21 | 22 | 23 | ))} 24 | 25 | 26 | 27 | ); 28 | } 29 | 30 | export default App; 31 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/assets/images/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multiversx/mx-deprecated-starter-dapp/eb50639bdf87941689a102371e8ce823f8e08003/react-delegationdashboard/src/assets/images/background.jpg -------------------------------------------------------------------------------- /react-delegationdashboard/src/assets/images/contract.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/assets/images/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multiversx/mx-deprecated-starter-dapp/eb50639bdf87941689a102371e8ce823f8e08003/react-delegationdashboard/src/assets/images/dashboard.png -------------------------------------------------------------------------------- /react-delegationdashboard/src/assets/images/delegation.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/assets/images/leaf-solid.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/assets/images/ledger-nano.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/assets/images/nodes.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/assets/images/owner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/multiversx/mx-deprecated-starter-dapp/eb50639bdf87941689a102371e8ce823f8e08003/react-delegationdashboard/src/assets/images/owner.png -------------------------------------------------------------------------------- /react-delegationdashboard/src/assets/images/service.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/assets/styles/_components.scss: -------------------------------------------------------------------------------- 1 | @import './components/navbar'; 2 | @import './components/layout'; 3 | @import './components/footer'; 4 | @import './components/header'; 5 | @import './components/cards'; 6 | @import './components/statcard'; 7 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/assets/styles/_elements.scss: -------------------------------------------------------------------------------- 1 | body.modal-open .layout { 2 | filter: blur(6px); 3 | } 4 | 5 | .opacity-6 { 6 | opacity: 0.6; 7 | } 8 | .opacity-5 { 9 | opacity: 0.5; 10 | } 11 | 12 | .card { 13 | .card-header { 14 | &:first-child { 15 | border-radius: calc(#{$card-border-radius} - 1px) calc(#{$card-border-radius} - 1px) 0 0; 16 | } 17 | } 18 | box-shadow: rgba(255, 255, 255, 0.2) 0 0 0 0.5px inset; 19 | } 20 | 21 | .btn { 22 | &:hover:enabled { 23 | background: rgba(0, 0, 0, 0.4); 24 | } 25 | border: none !important; 26 | box-shadow: 0 0.6rem 0.6rem 0 rgba(27, 70, 194, 0.1); 27 | &.btn-primary { 28 | background: rgba(255, 255, 255, 0.2); 29 | border: rgba(255, 255, 255, 0.3); 30 | color: $white; 31 | box-shadow: 0 0.6rem 0.6rem 0 rgba(27, 70, 194, 0.1); 32 | &:hover:enabled { 33 | background: rgba(0, 0, 0, 0.2); 34 | } 35 | &:not(:disabled):not(.disabled):active, 36 | &:not(:disabled):not(.disabled).active { 37 | color: $white; 38 | background-color: rgba(0, 0, 0, 0.2); 39 | } 40 | } 41 | } 42 | 43 | .table { 44 | th, 45 | td { 46 | font-size: 0.875rem; 47 | vertical-align: middle; 48 | &:first-of-type { 49 | padding-left: 0; 50 | } 51 | &:last-of-type { 52 | text-align: right; 53 | padding-right: 0; 54 | } 55 | } 56 | th { 57 | font-size: $table-head-font-size; 58 | font-weight: 400; 59 | padding-top: 0; 60 | padding-bottom: 0.875rem; 61 | } 62 | td { 63 | font-size: $table-font-size; 64 | border-top: $table-border-width solid $table-border-color; 65 | border-bottom: $table-border-width solid $table-border-color; 66 | } 67 | .trim { 68 | max-width: 10rem; 69 | @include media-breakpoint-up(sm) { 70 | max-width: 14rem; 71 | } 72 | @include media-breakpoint-up(md) { 73 | max-width: 30rem; 74 | } 75 | @include media-breakpoint-up(xl) { 76 | max-width: 42rem; 77 | } 78 | } 79 | .dropdown { 80 | position: unset !important; 81 | .dropdown-menu { 82 | box-shadow: $dropdown-box-shadow; 83 | } 84 | } 85 | } 86 | 87 | label { 88 | @include font-size(80%); 89 | } 90 | 91 | .fa-spin { 92 | animation-duration: 0.5s; 93 | } 94 | 95 | .card-bg-red { 96 | background: rgba(0, 0, 0, 0) 97 | radial-gradient( 98 | 218.51% 281.09% at 100% 100%, 99 | rgba(253, 63, 51, 0.6) 0%, 100 | rgba(76, 0, 200, 0.6) 45.83%, 101 | rgba(76, 0, 200, 0.6) 100% 102 | ) 103 | repeat scroll 0% 0%; 104 | box-shadow: 0 0.6rem 0.6rem 0 rgba(253, 63, 51, 0.06); 105 | } 106 | 107 | .card-bg-orange { 108 | background: rgba(0, 0, 0, 0) 109 | radial-gradient( 110 | 218.51% 281.09% at 100% 100%, 111 | rgba(238, 184, 109, 0.8) 0%, 112 | rgba(76, 0, 200, 0.6) 45.83%, 113 | rgba(76, 0, 200, 0.6) 100% 114 | ) 115 | repeat scroll 0% 0%; 116 | box-shadow: 0 0.6rem 0.6rem 0 rgba(238, 184, 109, 0.06); 117 | } 118 | 119 | .card-bg-purple { 120 | background: rgba(0, 0, 0, 0) 121 | radial-gradient( 122 | 218.51% 281.09% at 100% 100%, 123 | rgba(129, 122, 227, 0.6) 0%, 124 | rgba(76, 0, 200, 0.6) 45.83%, 125 | rgba(76, 0, 200, 0.6) 100% 126 | ) 127 | repeat scroll 0% 0%; 128 | box-shadow: 0 0.6rem 0.6rem 0 rgba(129, 122, 227, 0.06); 129 | } 130 | 131 | .card-bg-green { 132 | background: rgba(0, 0, 0, 0) 133 | radial-gradient( 134 | 218.51% 281.09% at 100% 100%, 135 | rgba(27, 197, 189, 0.6) 0%, 136 | rgba(76, 0, 200, 0.6) 45.83%, 137 | rgba(76, 0, 200, 0.6) 100% 138 | ) 139 | repeat scroll 0% 0%; 140 | box-shadow: 0 0.6rem 0.6rem 0 rgba(27, 197, 189, 0.06); 141 | } 142 | 143 | @media (min-width: 1200px) { 144 | .container { 145 | max-width: 1300px; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/assets/styles/_main.scss: -------------------------------------------------------------------------------- 1 | // Variables override 2 | @import './variables'; 3 | 4 | // Bootstrap 5 | @import '~bootstrap/scss/bootstrap'; 6 | 7 | // Reboot 8 | @import '~bootstrap/scss/reboot'; 9 | 10 | // Utilities 11 | @import './utilities'; 12 | 13 | // Pages 14 | @import './pages'; 15 | 16 | // Components 17 | @import './components'; 18 | 19 | // Elements 20 | @import './elements'; 21 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/assets/styles/_pages.scss: -------------------------------------------------------------------------------- 1 | @import './pages/home'; 2 | @import './pages/owner'; 3 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/assets/styles/_utilities.scss: -------------------------------------------------------------------------------- 1 | @each $color, $value in $theme-colors { 2 | .fill-#{$color} { 3 | fill: $value; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/assets/styles/_variables.scss: -------------------------------------------------------------------------------- 1 | @import '~bootstrap/scss/functions'; 2 | @import '~bootstrap/scss/variables'; 3 | @import '~bootstrap/scss/mixins'; 4 | 5 | $spacer-val: $spacer * 1.7; 6 | // SPACING 7 | $spacers: ( 8 | spacer: ( 9 | $spacer-val, 10 | ), 11 | ); 12 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/assets/styles/components/cards.scss: -------------------------------------------------------------------------------- 1 | .cards { 2 | max-width: auto; 3 | margin-top: -4.65rem; 4 | margin-bottom: -1.5rem; 5 | } 6 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/assets/styles/components/footer.scss: -------------------------------------------------------------------------------- 1 | .footer { 2 | a { 3 | color: $white; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/assets/styles/components/header.scss: -------------------------------------------------------------------------------- 1 | .header { 2 | &.card-header { 3 | min-height: 3rem; 4 | padding-bottom: 5rem; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/assets/styles/components/layout.scss: -------------------------------------------------------------------------------- 1 | .layout { 2 | background: $bg-image; 3 | background-size: cover; 4 | margin-bottom: -1px; 5 | } 6 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/assets/styles/components/navbar.scss: -------------------------------------------------------------------------------- 1 | .navbar { 2 | svg.logo { 3 | fill: $brand-color; 4 | height: $logo-height; 5 | width: auto; 6 | } 7 | .navbar-brand { 8 | color: $brand-color; 9 | line-height: $logo-height; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/assets/styles/components/statcard.scss: -------------------------------------------------------------------------------- 1 | .statcard { 2 | min-height: 8.8rem; 3 | min-width: 10rem; 4 | flex-basis: 0; 5 | flex-grow: 1; 6 | font-size: 0.85rem; 7 | @include media-breakpoint-only(lg) { 8 | min-width: 13rem; 9 | } 10 | .icon { 11 | height: 1.5rem; 12 | 13 | svg { 14 | height: 1.5rem; 15 | width: auto; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/assets/styles/pages/home.scss: -------------------------------------------------------------------------------- 1 | .home { 2 | color: #fff; 3 | .login-container { 4 | font-size: 0.8125rem; 5 | max-width: 33.625rem; 6 | 7 | .btn { 8 | min-width: 6.875rem; 9 | } 10 | .logo { 11 | fill: $white; 12 | height: 2.5rem; 13 | width: auto; 14 | } 15 | } 16 | } 17 | 18 | @media (max-width: 560px) { 19 | .home { 20 | .login-container { 21 | .card-body { 22 | div { 23 | &:last-child { 24 | display: flex; 25 | flex-direction: column; 26 | align-items: center; 27 | button { 28 | margin: 16px 0; 29 | } 30 | } 31 | } 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/assets/styles/pages/owner.scss: -------------------------------------------------------------------------------- 1 | .owner { 2 | .action-dropdown { 3 | padding: 0; 4 | svg { 5 | width: 1.6rem; 6 | height: 1.6rem; 7 | } 8 | &::after { 9 | display: none; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/assets/styles/theme.scss: -------------------------------------------------------------------------------- 1 | $font-family-sans-serif: 'Poppins', sans-serif; 2 | $font-weight-normal: 300; 3 | $font-weight-bold: 500; 4 | $headings-font-weight: 400; 5 | $white: #fff; 6 | 7 | $body: #fff; 8 | $body-bg: #e5e5e5; 9 | $body-color: #fff; 10 | 11 | $primary: $white; 12 | $secondary: #f3f6f9; 13 | $dark: #5d6473; 14 | $muted: #b5b5c3; 15 | $light: #f3f6f9; 16 | $success: #28a745; 17 | 18 | $orange: #ffa800; 19 | $light-orange: #fff4de30; 20 | $green: #1bc5bd; 21 | $light-green: #c9f7f530; 22 | $purple: #8950fc; 23 | $light-purple: #eee5ff30; 24 | $red: #f64e60; 25 | $light-red: #ffe2e630; 26 | $light-primary: #e1f0ff30; 27 | $disabled: #333; 28 | 29 | $card-bg: rgba(0, 0, 0, 0.4); 30 | $text-muted: $muted; 31 | $brand-color: $white; 32 | $logo-height: 1.5rem; 33 | $bg-image: linear-gradient(143deg, #043a8c 0%, #4e4e4e 86%); 34 | $footer-color: $white; 35 | 36 | $lead-font-size: 1.15rem; 37 | $h6-font-size: 1.125rem; 38 | 39 | // STATCARD 40 | $statcard-title-size: 0.75rem; 41 | $statcard-value-size: 1.5rem; 42 | $statcard-subvalue-size: 0.6rem; 43 | 44 | // BORDER 45 | $border-radius: 0.65rem; 46 | $border-color: lighten($muted, 10%); 47 | 48 | // INPUT 49 | $input-border-radius: 0.2rem; 50 | $input-color: $secondary; 51 | $input-bg: $card-bg; 52 | $input-disabled-bg: $disabled; 53 | $input-hover-border-color: darken($border-color, 15%); 54 | $label-margin-bottom: 0.25rem; 55 | $form-text-margin-top: 0.25rem; 56 | 57 | // TABLE 58 | $table-color: $white; 59 | $table-cell-padding: 1.5625rem; 60 | $table-font-size: 0.875rem; 61 | $table-head-font-size: $table-font-size; 62 | $table-border-color: rgba(255, 255, 255, 0.3); 63 | 64 | // CARD 65 | $card-border-color: rgba(0, 0, 0, 0.4); 66 | $card-cap-bg: none; 67 | $card-border-radius: 0.65rem; 68 | 69 | // MODAL 70 | $modal-content-bg: rgba(25, 24, 63, 0.98) none repeat scroll 0% 0%; 71 | $modal-content-border-width: 1; 72 | $modal-content-border-radius: $border-radius; 73 | $modal-content-box-shadow-xs: rgba(255, 255, 255, 0.2) 0 0 0 0.5px inset; 74 | 75 | // BUTTONS 76 | $btn-padding-y: 0.5rem; 77 | $btn-padding-x: 1.3125rem; 78 | $btn-padding-y-sm: 0.3rem; 79 | $btn-padding-x-sm: 1.1rem; 80 | $btn-font-size: 0.875rem; 81 | $btn-font-size-sm: 0.6875rem; 82 | $btn-line-height: 1.125rem; 83 | $btn-line-height-sm: 1.0625rem; 84 | $btn-border-radius: 0.375rem; 85 | $btn-border-radius-sm: $btn-border-radius; 86 | 87 | // BADGES 88 | $badge-font-weight: 300; 89 | $badge-border-radius: 0.375rem; 90 | $badge-padding-y: 0.375rem; 91 | $badge-padding-x: 0.75rem; 92 | $badge-font-size: 0.6875rem; 93 | 94 | // DROPDOWN 95 | $dropdown-color: $primary; 96 | $dropdown-bg: rgba(0, 0, 0, 0.9); 97 | $dropdown-box-shadow: rgba(255, 255, 255, 0.2) 0 0 0 0.5px inset; 98 | $dropdown-link-disabled-color: $disabled; 99 | $dropdown-link-color: $primary; 100 | 101 | $link-hover-color: darken($primary, 7%); 102 | 103 | $theme-colors: ( 104 | 'orange': $orange, 105 | 'light-orange': $light-orange, 106 | 'green': $green, 107 | 'light-green': $light-green, 108 | 'purple': $purple, 109 | 'light-purple': $light-purple, 110 | 'red': $red, 111 | 'light-red': $light-red, 112 | 'light-primary': $light-primary, 113 | 'white': $white, 114 | ); 115 | 116 | @import './main'; 117 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/components/ConfirmTransactionModal/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { Modal } from 'react-bootstrap'; 3 | import { useDelegation } from 'helpers'; 4 | import { DelegationTransactionType } from 'helpers/contractDataDefinitions'; 5 | import { TransactionHash } from '@elrondnetwork/erdjs'; 6 | import TransactionStatusModal from 'components/TransactionStatus'; 7 | import { useHistory } from 'react-router-dom'; 8 | import { useContext, useDispatch } from 'context'; 9 | export interface ConfirmTransactionModalType { 10 | show: boolean; 11 | transactionArguments: DelegationTransactionType; 12 | handleClose: () => void; 13 | } 14 | const ConfirmTransactionModal = ({ 15 | show, 16 | transactionArguments, 17 | handleClose, 18 | }: ConfirmTransactionModalType) => { 19 | const dispatch = useDispatch(); 20 | const { egldLabel, delegationContract, dapp } = useContext(); 21 | const [error, setError] = useState(''); 22 | const [showTransactionStatus, setShowTransactionStatus] = useState(false); 23 | const [txHash, setTxHash] = useState(new TransactionHash('')); 24 | const closeTransactionModal = (txHash: TransactionHash) => { 25 | setTxHash(txHash); 26 | handleClose(); 27 | setShowTransactionStatus(true); 28 | }; 29 | const history = useHistory(); 30 | 31 | const handleCloseModal = () => { 32 | setShowTransactionStatus(false); 33 | if (error === 'Your session has expired. Please login again') { 34 | dispatch({ type: 'logout', provider: dapp.provider }); 35 | } 36 | history.push(''); 37 | }; 38 | const { sendTransaction } = useDelegation({ 39 | handleClose: closeTransactionModal, 40 | setError: setError, 41 | }); 42 | 43 | useEffect( 44 | () => { 45 | if (transactionArguments.type !== '') { 46 | sendTransaction(transactionArguments); 47 | } 48 | }, 49 | /* eslint-disable react-hooks/exhaustive-deps */ 50 | [transactionArguments] 51 | ); 52 | return ( 53 | <> 54 | 61 |
62 |
63 |
64 | Confirm transaction on device 65 |
66 |
67 | To: 68 | {delegationContract} 69 |
70 |
71 | Amount: 72 | {transactionArguments.value} {egldLabel} 73 |
74 |
75 | Data: 76 | {transactionArguments.type} 77 | {`${transactionArguments.args !== '' ? '@' + transactionArguments.args : ''}`} 78 |
79 | {error && ( 80 |

81 | {error} 82 |

83 | )} 84 |
85 | 93 |
94 |
95 |
96 |
97 | 98 | 99 | ); 100 | }; 101 | 102 | export default ConfirmTransactionModal; 103 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/components/ContinueAndCloseButtons/index.tsx: -------------------------------------------------------------------------------- 1 | interface ContinueAndCloseButtonsType { 2 | actionTitle: string; 3 | color: string; 4 | handleContinue: () => void; 5 | handleClose: () => void; 6 | } 7 | 8 | const ContinueAndCloseButtons = ({ 9 | actionTitle, 10 | color, 11 | handleContinue, 12 | handleClose, 13 | }: ContinueAndCloseButtonsType) => { 14 | return ( 15 |
16 | 24 | 27 |
28 | ); 29 | }; 30 | 31 | export default ContinueAndCloseButtons; 32 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/components/Denominate/formatters.tsx: -------------------------------------------------------------------------------- 1 | function format( 2 | big: string, 3 | denomination: number, 4 | decimals: number, 5 | showLastNonZeroDecimal: boolean, 6 | addCommas: boolean 7 | ) { 8 | showLastNonZeroDecimal = 9 | typeof showLastNonZeroDecimal !== 'undefined' ? showLastNonZeroDecimal : false; 10 | let array = big.toString().split(''); 11 | if (denomination !== 0) { 12 | // make sure we have enough characters 13 | while (array.length < denomination + 1) { 14 | array.unshift('0'); 15 | } 16 | // add our dot 17 | array.splice(array.length - denomination, 0, '.'); 18 | // make sure there are enough decimals after the dot 19 | while (array.length - array.indexOf('.') <= decimals) { 20 | array.push('0'); 21 | } 22 | 23 | if (showLastNonZeroDecimal) { 24 | let nonZeroDigitIndex = 0; 25 | for (let i = array.length - 1; i > 0; i--) { 26 | if (array[i] !== '0') { 27 | nonZeroDigitIndex = i + 1; 28 | break; 29 | } 30 | } 31 | const decimalsIndex = array.indexOf('.') + decimals + 1; 32 | const sliceIndex = Math.max(decimalsIndex, nonZeroDigitIndex); 33 | array = array.slice(0, sliceIndex); 34 | } else { 35 | // trim unnecessary characters after the dot 36 | array = array.slice(0, array.indexOf('.') + decimals + 1); 37 | } 38 | } 39 | if (addCommas) { 40 | // add comas every 3 characters 41 | array = array.reverse(); 42 | const reference = denomination ? array.length - array.indexOf('.') - 1 : array.length; 43 | const count = Math.floor(reference / 3); 44 | for (let i = 1; i <= count; i++) { 45 | const position = array.indexOf('.') + 3 * i + i; 46 | if (position !== array.length) { 47 | array.splice(position, 0, ','); 48 | } 49 | } 50 | array = array.reverse(); 51 | } 52 | 53 | const allDecimalsZero = array 54 | .slice(array.indexOf('.') + 1) 55 | .every(digit => digit.toString() === '0'); 56 | 57 | const string = array.join(''); 58 | 59 | if (allDecimalsZero) { 60 | return string.split('.')[0]; 61 | } 62 | 63 | return decimals === 0 ? string.split('.').join('') : string; 64 | } 65 | 66 | interface DenominateType { 67 | input: string; 68 | denomination: number; 69 | decimals: number; 70 | showLastNonZeroDecimal?: boolean; 71 | addCommas?: boolean; 72 | } 73 | 74 | export default function denominate({ 75 | input, 76 | denomination, 77 | decimals, 78 | showLastNonZeroDecimal = false, 79 | addCommas = true, 80 | }: DenominateType): string { 81 | if (input === '...') { 82 | return input; 83 | } 84 | if (input === '' || input === '0' || input === undefined) { 85 | input = '0'; 86 | } 87 | return format(input, denomination, decimals, showLastNonZeroDecimal, addCommas); 88 | } 89 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/components/Denominate/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useContext } from '../../context'; 3 | import denominate from './formatters'; 4 | 5 | interface DenominateType { 6 | value: string; 7 | showLastNonZeroDecimal?: boolean; 8 | showErd?: boolean; 9 | decimals?: number; 10 | } 11 | 12 | const Denominate = ({ 13 | value, 14 | showLastNonZeroDecimal = false, 15 | showErd = true, 16 | decimals, 17 | }: DenominateType) => { 18 | const { denomination, decimals: configDecimals, egldLabel } = useContext(); 19 | 20 | decimals = decimals !== undefined ? decimals : configDecimals; 21 | 22 | const denominatedValue = denominate({ 23 | input: value, 24 | denomination, 25 | decimals, 26 | showLastNonZeroDecimal, 27 | }); 28 | 29 | const valueParts = denominatedValue.split('.'); 30 | const hasNoDecimals = valueParts.length === 1; 31 | const isNotZero = denominatedValue !== '0'; 32 | 33 | if (decimals > 0 && hasNoDecimals && isNotZero) { 34 | let zeros = ''; 35 | 36 | for (let i = 1; i <= decimals; i++) { 37 | zeros = zeros + '0'; 38 | } 39 | 40 | valueParts.push(zeros); 41 | } 42 | 43 | return ( 44 | 45 | {valueParts[0]} 46 | {valueParts.length > 1 && .{valueParts[1]}} 47 | {showErd &&  {egldLabel}} 48 | 49 | ); 50 | }; 51 | 52 | export default Denominate; 53 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/components/DropzonePem/PemUpload.tsx: -------------------------------------------------------------------------------- 1 | import ModalActionButton from 'components/ModalActionButton'; 2 | import { Formik } from 'formik'; 3 | import * as React from 'react'; 4 | import { object, array, mixed } from 'yup'; 5 | import DropzonePem, { DropzoneFileType } from './index'; 6 | 7 | const PemUpload = ({ 8 | handleClose, 9 | onSubmit, 10 | }: { 11 | handleClose: () => void; 12 | onSubmit: (pemFiles: DropzoneFileType[]) => void; 13 | }) => { 14 | const initialValues: { pemFiles: DropzoneFileType[] } = { pemFiles: [] }; 15 | const ref = React.useRef(null); 16 | 17 | return ( 18 | { 21 | onSubmit(pemFiles); 22 | }} 23 | validationSchema={object().shape({ 24 | pemFiles: array() 25 | .of(mixed()) 26 | .test('validKeyLength', 'Invalid PEM file', (value: DropzoneFileType[] | undefined) => { 27 | return !!value && value.every(file => !file.errors.includes('length')); 28 | }) 29 | .test( 30 | 'keyIsUnique', 31 | 'Duplicate key detected', 32 | (value: DropzoneFileType[] | undefined) => { 33 | return !!value && value.every(file => !file.errors.includes('unique')); 34 | } 35 | ) 36 | .required('PEM file is required'), 37 | })} 38 | > 39 | {props => { 40 | const { setFieldValue, handleSubmit, errors } = props; 41 | 42 | return ( 43 |
44 |
45 | 53 |
54 |
55 | 60 |
61 |
62 | ); 63 | }} 64 |
65 | ); 66 | }; 67 | 68 | export default PemUpload; 69 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/components/DropzonePem/Request.tsx: -------------------------------------------------------------------------------- 1 | export interface VariableType { 2 | name: string; 3 | type: string; 4 | } 5 | 6 | export interface RequestType { 7 | data: string | ((args: any) => void); 8 | variables?: VariableType[]; 9 | } 10 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/components/DropzonePem/RequestVariablesModal.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Modal } from 'react-bootstrap'; 3 | import { DropzoneFileType } from '.'; 4 | import PemUpload from './PemUpload'; 5 | import { RequestType } from './Request'; 6 | 7 | interface RequestVariablesModalType { 8 | name: string; 9 | show: boolean; 10 | variables: RequestType['variables']; 11 | data: RequestType['data']; 12 | handleClose: () => void; 13 | triggerDispatchEvent: (variablesData: string) => void; 14 | } 15 | 16 | interface ModalValuesType { 17 | [key: string]: any; 18 | } 19 | 20 | const RequestVariablesModal = ({ 21 | name, 22 | show, 23 | variables, 24 | data, 25 | handleClose, 26 | triggerDispatchEvent, 27 | }: RequestVariablesModalType) => { 28 | const [modalValues, setModalValues] = React.useState({}); 29 | 30 | const onSubmit = (pemFiles?: DropzoneFileType[]) => { 31 | if (typeof data !== 'string') { 32 | triggerDispatchEvent(`${data(pemFiles ? pemFiles : modalValues)}`); 33 | } 34 | }; 35 | return ( 36 | 37 |
38 |
39 |

40 | {name} 41 |

42 |
43 | {variables?.map(variable => { 44 | return ( 45 |
46 | {variable.type === 'input' && ( 47 |
48 | 49 |
50 | { 56 | const vals = { ...modalValues }; 57 | vals[variable.name] = e.target.value; 58 | setModalValues(vals); 59 | }} 60 | autoComplete="off" 61 | /> 62 |
63 |
64 | )} 65 | 66 |
67 | ); 68 | })} 69 |
70 |
71 |
72 |
73 | ); 74 | }; 75 | 76 | export default RequestVariablesModal; 77 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/components/Layout/APRCalculation.ts: -------------------------------------------------------------------------------- 1 | import { decodeString } from '@elrondnetwork/erdjs'; 2 | import denominate from 'components/Denominate/formatters'; 3 | import { 4 | yearSettings, 5 | genesisTokenSuply, 6 | denomination, 7 | decimals, 8 | feesInEpoch, 9 | protocolSustainabilityRewards, 10 | stakePerNode, 11 | } from 'config'; 12 | import { NetworkConfig, NetworkStake, Stats } from 'helpers/contractDataDefinitions'; 13 | 14 | const denominateValue = (value: string) => { 15 | const denominatedValueString = denominate({ 16 | input: value, 17 | denomination, 18 | decimals, 19 | }); 20 | const valueWithoutComma = denominatedValueString.replace(/,/g, ''); 21 | return valueWithoutComma; 22 | }; 23 | 24 | const calculateAPR = ({ 25 | stats, 26 | networkConfig, 27 | networkStake, 28 | blsKeys, 29 | totalActiveStake, 30 | }: { 31 | stats: Stats; 32 | networkConfig: NetworkConfig; 33 | networkStake: NetworkStake; 34 | blsKeys: Buffer[]; 35 | totalActiveStake: string; 36 | }) => { 37 | const allNodes = blsKeys.filter( 38 | key => 39 | decodeString(key) === 'staked' || 40 | decodeString(key) === 'jailed' || 41 | decodeString(key) === 'queued' 42 | ).length; 43 | const allActiveNodes = blsKeys.filter(key => decodeString(key) === 'staked').length; 44 | if (allActiveNodes <= 0) { 45 | return '0.00'; 46 | } 47 | 48 | const epochDurationInSeconds = 49 | (networkConfig.roundDuration / 1000) * networkConfig.roundsPerEpoch; 50 | const secondsInYear = 365 * 24 * 3600; 51 | const epochsInYear = secondsInYear / epochDurationInSeconds; 52 | const inflationRate = 53 | yearSettings.find(x => x.year === Math.floor(stats.epoch / epochsInYear) + 1) 54 | ?.maximumInflation || 0; 55 | const rewardsPerEpoch = Math.max((inflationRate * genesisTokenSuply) / epochsInYear, feesInEpoch); 56 | const rewardsPerEpochWithoutProtocolSustainability = 57 | (1 - protocolSustainabilityRewards) * rewardsPerEpoch; 58 | const topUpRewardsLimit = 59 | networkConfig.topUpFactor * rewardsPerEpochWithoutProtocolSustainability; 60 | 61 | const networkBaseStake = networkStake.activeValidators * stakePerNode; 62 | const networkTotalStake = parseInt(denominateValue(networkStake.totalStaked.toFixed())); 63 | const networkTopUpStake = 64 | networkTotalStake - 65 | networkStake.totalValidators * stakePerNode - 66 | networkStake.queueSize * stakePerNode; 67 | const topUpReward = 68 | ((2 * topUpRewardsLimit) / Math.PI) * 69 | Math.atan( 70 | networkTopUpStake / 71 | (2 * parseInt(denominateValue(networkConfig.topUpRewardsGradientPoint.toFixed()))) 72 | ); 73 | const baseReward = rewardsPerEpochWithoutProtocolSustainability - topUpReward; 74 | const validatorTotalStake = parseInt(denominateValue(totalActiveStake)); 75 | const actualNumberOfNodes = Math.min( 76 | Math.floor(validatorTotalStake / stakePerNode), 77 | allActiveNodes 78 | ); 79 | const validatorBaseStake = actualNumberOfNodes * stakePerNode; 80 | const validatorTopUpStake = 81 | ((validatorTotalStake - allNodes * stakePerNode) / allNodes) * allActiveNodes; 82 | const validatorTopUpReward = 83 | networkTopUpStake > 0 ? (validatorTopUpStake / networkTopUpStake) * topUpReward : 0; 84 | const validatorBaseReward = (validatorBaseStake / networkBaseStake) * baseReward; 85 | const anualPercentageRate = 86 | (epochsInYear * (validatorTopUpReward + validatorBaseReward)) / validatorTotalStake; 87 | return (anualPercentageRate * 100).toFixed(2); 88 | }; 89 | 90 | export { calculateAPR }; 91 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/components/Layout/Footer/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Footer = () => { 4 | return ( 5 | 10 | ); 11 | }; 12 | 13 | export default Footer; 14 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/components/Layout/Navbar/index.tsx: -------------------------------------------------------------------------------- 1 | import Denominate from 'components/Denominate'; 2 | import React from 'react'; 3 | import { ReactComponent as Logo } from 'assets/images/logo.svg'; 4 | import { useContext, useDispatch } from 'context'; 5 | 6 | const Navbar = () => { 7 | const { loggedIn, dapp, address, account } = useContext(); 8 | const dispatch = useDispatch(); 9 | 10 | const logOut = () => { 11 | dispatch({ type: 'logout', provider: dapp.provider }); 12 | }; 13 | 14 | return ( 15 |
16 |
17 |
18 | 19 | Delegation Manager 20 |
21 | {loggedIn && ( 22 |
23 | Balance: 24 | 25 | 26 | 27 | Wallet address: 28 | {address} 29 | 30 | Close 31 | 32 |
33 | )} 34 |
35 |
36 | ); 37 | }; 38 | 39 | export default Navbar; 40 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/components/ModalActionButton/index.tsx: -------------------------------------------------------------------------------- 1 | interface SubmitAndCloseButtonsType { 2 | action: string; 3 | actionTitle: string; 4 | isHandleActionDisabled?: boolean; 5 | handleClose: () => void; 6 | } 7 | 8 | const ModalActionButton = ({ 9 | action, 10 | actionTitle, 11 | isHandleActionDisabled, 12 | handleClose, 13 | }: SubmitAndCloseButtonsType) => { 14 | return ( 15 | <> 16 |
17 | 26 | 29 |
30 | 31 | ); 32 | }; 33 | 34 | export default ModalActionButton; 35 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/components/Overview/Cards/AutomaticActivationAction/index.tsx: -------------------------------------------------------------------------------- 1 | import ConfirmTransactionModal from 'components/ConfirmTransactionModal'; 2 | import OwnerActionModal from 'components/Overview/OwnerActionModal'; 3 | import { useContext } from 'context'; 4 | import { DelegationTransactionType } from 'helpers/contractDataDefinitions'; 5 | import { useDelegationWallet } from 'helpers/useDelegation'; 6 | import React, { useState } from 'react'; 7 | 8 | const AutomaticActivationAction = ({ automaticFlag }: { automaticFlag: string }) => { 9 | const { ledgerAccount, walletConnectAccount } = useContext(); 10 | const [showAutomaticActivationModal, setShowAutomaticActivationModal] = useState(false); 11 | const [showCheckYourLedgerModal, setShowCheckYourLedgerModal] = useState(false); 12 | const [transactionArguments, setTransactionArguments] = useState( 13 | new DelegationTransactionType('', '') 14 | ); 15 | const { sendTransactionWallet } = useDelegationWallet(); 16 | 17 | const handleAutomaticActivation = () => { 18 | let activation = Buffer.from(automaticFlag === 'true' ? 'false' : 'true').toString('hex'); 19 | let txArguments = new DelegationTransactionType('0', 'setAutomaticActivation', activation); 20 | if (ledgerAccount || walletConnectAccount) { 21 | setShowAutomaticActivationModal(false); 22 | setTransactionArguments(txArguments); 23 | setShowCheckYourLedgerModal(true); 24 | } else { 25 | sendTransactionWallet(txArguments); 26 | } 27 | }; 28 | return ( 29 |
30 | 36 | { 43 | setShowAutomaticActivationModal(false); 44 | }} 45 | handleContinue={handleAutomaticActivation} 46 | /> 47 | { 51 | setShowCheckYourLedgerModal(false); 52 | }} 53 | /> 54 |
55 | ); 56 | }; 57 | 58 | export default AutomaticActivationAction; 59 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/components/Overview/Cards/ReDelegateCapActivationAction/index.tsx: -------------------------------------------------------------------------------- 1 | import ConfirmTransactionModal from 'components/ConfirmTransactionModal'; 2 | import OwnerActionModal from 'components/Overview/OwnerActionModal'; 3 | import { useContext } from 'context'; 4 | import { DelegationTransactionType } from 'helpers/contractDataDefinitions'; 5 | import { useDelegationWallet } from 'helpers/useDelegation'; 6 | import React, { useState } from 'react'; 7 | 8 | const ReDelegateCapActivationAction = ({ automaticFlag }: { automaticFlag: string }) => { 9 | const { ledgerAccount, walletConnectAccount } = useContext(); 10 | const [showReDelegationCapActivationModal, setShowReDelegationCapActivationModal] = useState( 11 | false 12 | ); 13 | const [showCheckYourLedgerModal, setShowCheckYourLedgerModal] = useState(false); 14 | const [transactionArguments, setTransactionArguments] = useState( 15 | new DelegationTransactionType('', '') 16 | ); 17 | const { sendTransactionWallet } = useDelegationWallet(); 18 | const handleReDelegationCapActivation = () => { 19 | let redelegateRewardsActivation = Buffer.from( 20 | automaticFlag === 'true' ? 'false' : 'true' 21 | ).toString('hex'); 22 | let txArguments = new DelegationTransactionType( 23 | '0', 24 | 'setReDelegateCapActivation', 25 | redelegateRewardsActivation 26 | ); 27 | 28 | if (ledgerAccount || walletConnectAccount) { 29 | setShowReDelegationCapActivationModal(false); 30 | setTransactionArguments(txArguments); 31 | setShowCheckYourLedgerModal(true); 32 | } else { 33 | sendTransactionWallet(txArguments); 34 | } 35 | }; 36 | 37 | return ( 38 |
39 | 45 | { 52 | setShowReDelegationCapActivationModal(false); 53 | }} 54 | handleContinue={handleReDelegationCapActivation} 55 | /> 56 | { 60 | setShowCheckYourLedgerModal(false); 61 | }} 62 | /> 63 |
64 | ); 65 | }; 66 | 67 | export default ReDelegateCapActivationAction; 68 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/components/Overview/Cards/SetPercentageFeeAction/SetPercentageFeeModal.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Modal } from 'react-bootstrap'; 3 | import { ErrorMessage, Formik } from 'formik'; 4 | import { object, string } from 'yup'; 5 | import ModalActionButton from 'components/ModalActionButton'; 6 | 7 | const SetPercentageFeeSchema = object().shape({ 8 | amount: string() 9 | .required('Required') 10 | .test('minimum', 'Minimum fee percentage is 0.01', value => { 11 | const feeAmount = parseFloat(value !== undefined ? value : ''); 12 | return feeAmount > 0; 13 | }) 14 | .test('minimum', 'Maximum fee percentage is 100', value => { 15 | const feeAmount = parseFloat(value !== undefined ? value : ''); 16 | return feeAmount <= 100; 17 | }), 18 | }); 19 | 20 | interface SetPercentageFeeModalType { 21 | show: boolean; 22 | handleClose: () => void; 23 | handleContinue: (value: string) => void; 24 | } 25 | 26 | const SetPercentageFeeModal = ({ 27 | show, 28 | handleClose, 29 | handleContinue, 30 | }: SetPercentageFeeModalType) => { 31 | return ( 32 | 33 |
34 |
35 |

36 | Change service fee 37 |

38 |

39 | Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt 40 | mollit anim id est laborum. 41 |

42 | { 47 | handleContinue(values.amount); 48 | }} 49 | validationSchema={SetPercentageFeeSchema} 50 | > 51 | {props => { 52 | const { handleSubmit, values, handleBlur, handleChange, errors, touched } = props; 53 | 54 | return ( 55 |
56 |
57 | 58 | 72 | {!(errors.amount && touched.amount) && ( 73 | For example: 12.30 74 | )} 75 | 76 |
77 | 82 | 83 | ); 84 | }} 85 |
86 |
87 |
88 |
89 | ); 90 | }; 91 | 92 | export default SetPercentageFeeModal; 93 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/components/Overview/Cards/SetPercentageFeeAction/index.tsx: -------------------------------------------------------------------------------- 1 | import ConfirmTransactionModal from 'components/ConfirmTransactionModal'; 2 | import { useContext } from 'context'; 3 | import { DelegationTransactionType } from 'helpers/contractDataDefinitions'; 4 | import { useDelegationWallet } from 'helpers/useDelegation'; 5 | import React, { useState } from 'react'; 6 | import SetPercentageFeeModal from './SetPercentageFeeModal'; 7 | 8 | const SetPercentageFeeAction = () => { 9 | const { ledgerAccount, walletConnectAccount } = useContext(); 10 | 11 | const [showUpdateFeeModal, setShowUpdateFeeModal] = useState(false); 12 | const [showCheckYourLedgerModal, setShowCheckYourLedgerModal] = useState(false); 13 | const [transactionArguments, setTransactionArguments] = useState( 14 | new DelegationTransactionType('', '') 15 | ); 16 | const { sendTransactionWallet } = useDelegationWallet(); 17 | 18 | const nominateVal = (value: string) => { 19 | let perc = (parseFloat(value) * 100).toString(16); 20 | if (perc.length % 2 !== 0) { 21 | perc = '0' + perc; 22 | } 23 | return perc; 24 | }; 25 | const handleUpdateFee = (value: string) => { 26 | let txArguments = new DelegationTransactionType('0', 'changeServiceFee', nominateVal(value)); 27 | if (ledgerAccount || walletConnectAccount) { 28 | setShowUpdateFeeModal(false); 29 | setTransactionArguments(txArguments); 30 | setShowCheckYourLedgerModal(true); 31 | } else { 32 | sendTransactionWallet(txArguments); 33 | } 34 | }; 35 | return ( 36 |
37 | 43 | { 46 | setShowUpdateFeeModal(false); 47 | }} 48 | handleContinue={handleUpdateFee} 49 | /> 50 | { 54 | setShowCheckYourLedgerModal(false); 55 | }} 56 | /> 57 |
58 | ); 59 | }; 60 | 61 | export default SetPercentageFeeAction; 62 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/components/Overview/Cards/UpdateDelegationCapAction/index.tsx: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js'; 2 | import React, { useState } from 'react'; 3 | import nominate from 'helpers/nominate'; 4 | import DelegationCapModal from './DelegationCapModal'; 5 | import { DelegationTransactionType } from 'helpers/contractDataDefinitions'; 6 | import { useContext } from 'context'; 7 | import { useDelegationWallet } from 'helpers/useDelegation'; 8 | import ConfirmTransactionModal from 'components/ConfirmTransactionModal'; 9 | 10 | const UpdateDelegationCapAction = () => { 11 | const { ledgerAccount, walletConnectAccount } = useContext(); 12 | const [showDelegationCapModal, setShowDelegationCapModal] = useState(false); 13 | const [showCheckYourLedgerModal, setShowCheckYourLedgerModal] = useState(false); 14 | const [transactionArguments, setTransactionArguments] = useState( 15 | new DelegationTransactionType('', '') 16 | ); 17 | const { sendTransactionWallet } = useDelegationWallet(); 18 | 19 | const nominateValToHex = (value: string) => { 20 | let val = value && value.length > 0 ? new BigNumber(nominate(value)).toString(16) : '0'; 21 | 22 | if (val.length % 2 !== 0) { 23 | val = '0' + val; 24 | } 25 | return val; 26 | }; 27 | 28 | const handleUpdateDelegationCap = (value: string) => { 29 | let txArguments = new DelegationTransactionType( 30 | '0', 31 | 'modifyTotalDelegationCap', 32 | nominateValToHex(value) 33 | ); 34 | if (ledgerAccount || walletConnectAccount) { 35 | setShowDelegationCapModal(false); 36 | setTransactionArguments(txArguments); 37 | setShowCheckYourLedgerModal(true); 38 | } else { 39 | sendTransactionWallet(txArguments); 40 | } 41 | }; 42 | 43 | return ( 44 |
45 | 51 | { 56 | setShowDelegationCapModal(false); 57 | }} 58 | handleContinue={handleUpdateDelegationCap} 59 | /> 60 | { 64 | setShowCheckYourLedgerModal(false); 65 | }} 66 | /> 67 |
68 | ); 69 | }; 70 | 71 | export default UpdateDelegationCapAction; 72 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/components/Overview/Header/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { Link, useLocation } from 'react-router-dom'; 3 | import { Address } from '@elrondnetwork/erdjs'; 4 | import { useContext, useDispatch } from 'context'; 5 | import SetAgencyMetaDataModal from './SetAgencyMetaDataModal'; 6 | import { getItem } from 'storage/session'; 7 | 8 | const Header = () => { 9 | const { pathname } = useLocation(); 10 | const dispatch = useDispatch(); 11 | const { 12 | address, 13 | delegationContract, 14 | contractOverview, 15 | ledgerAccount, 16 | walletConnectAccount, 17 | } = useContext(); 18 | 19 | const isOwner = () => { 20 | let loginAddress = new Address(address).hex(); 21 | return loginAddress.localeCompare(contractOverview.ownerAddress) === 0; 22 | }; 23 | 24 | const isOwnerPath = () => { 25 | let currentURL = window.location.pathname; 26 | return currentURL.includes('owner') === true; 27 | }; 28 | 29 | const fetchLedger = () => { 30 | if (getItem('ledgerLogin') && !ledgerAccount) { 31 | const ledgerLogin = getItem('ledgerLogin'); 32 | dispatch({ 33 | type: 'setLedgerAccount', 34 | ledgerAccount: { 35 | index: ledgerLogin.index, 36 | address: address, 37 | }, 38 | }); 39 | } 40 | }; 41 | 42 | const fetchWalletConnect = () => { 43 | if (getItem('walletConnectLogin') && !walletConnectAccount) { 44 | dispatch({ 45 | type: 'setWalletConnectAccount', 46 | walletConnectAccount: address, 47 | }); 48 | } 49 | }; 50 | useEffect(fetchLedger, /* eslint-disable react-hooks/exhaustive-deps */ []); 51 | useEffect(fetchWalletConnect, /* eslint-disable react-hooks/exhaustive-deps */ []); 52 | 53 | return ( 54 |
55 |
56 |

Contract Address

57 | {delegationContract} 58 |
59 |
60 | {isOwner() && !isOwnerPath() ? ( 61 | 62 | Admin 63 | 64 | ) : null} 65 | {pathname !== '/dashboard' ? ( 66 | 67 | Dashboard 68 | 69 | ) : null} 70 | {isOwner() && isOwnerPath() ? : null} 71 |
72 |
73 | ); 74 | }; 75 | 76 | export default Header; 77 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/components/Overview/OwnerActionModal.tsx: -------------------------------------------------------------------------------- 1 | import { Modal } from 'react-bootstrap'; 2 | import ContinueAndCloseButtons from 'components/ContinueAndCloseButtons'; 3 | 4 | export interface OwnerActionModalType { 5 | show: boolean; 6 | title: string; 7 | actionTitle: string; 8 | description: string; 9 | extraDescription?: string; 10 | handleClose: () => void; 11 | handleContinue: () => void; 12 | } 13 | 14 | const OwnerActionModal = ({ 15 | show, 16 | title, 17 | actionTitle, 18 | description, 19 | extraDescription, 20 | handleClose, 21 | handleContinue, 22 | }: OwnerActionModalType) => { 23 | return ( 24 | 25 |
26 |
27 |

28 | {title} 29 |

30 | {description &&

{description}

} 31 | {extraDescription &&

{extraDescription}

} 32 | 38 |
39 |
40 |
41 | ); 42 | }; 43 | 44 | export default OwnerActionModal; 45 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/components/Overview/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Views from './Cards'; 3 | import Header from './Header'; 4 | 5 | const Overview = () => { 6 | return ( 7 | <> 8 |
9 | 10 | 11 | ); 12 | }; 13 | 14 | export default Overview; 15 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/components/PageNotFound/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | const PageNotFound = () => { 3 | return
404
; 4 | }; 5 | 6 | export default PageNotFound; 7 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/components/PageTitle/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, memo } from 'react'; 2 | 3 | const withPageTitle = (title: string, Component: React.ComponentType) => () => { 4 | const Memoized = memo(Component); 5 | 6 | useEffect(() => { 7 | document.title = title; 8 | }, []); 9 | return ; 10 | }; 11 | 12 | export default withPageTitle; 13 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/components/StatCard/index.tsx: -------------------------------------------------------------------------------- 1 | import SVG from 'react-inlinesvg'; 2 | import { StatCardType } from 'helpers/types'; 3 | import React from 'react'; 4 | import { faInfoCircle } from '@fortawesome/free-solid-svg-icons'; 5 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 6 | import { OverlayTrigger, Tooltip } from 'react-bootstrap'; 7 | 8 | const StatCard = ({ 9 | title = '', 10 | value = '0', 11 | valueUnit = '', 12 | color = '', 13 | svg = '', 14 | percentage = '', 15 | tooltipText = '', 16 | children = null, 17 | }: StatCardType) => { 18 | return ( 19 |
20 |
21 |
22 | 23 |
24 |
{children}
25 |
26 | {title} 27 |

28 | {value} {valueUnit} 29 |

30 | 31 | {percentage} 32 | {tooltipText !== '' && ( 33 | ( 37 | 38 | {tooltipText} 39 | 40 | )} 41 | > 42 | 43 | 44 | )} 45 | 46 |
47 | ); 48 | }; 49 | 50 | export default StatCard; 51 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/components/State/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 3 | 4 | const State = ({ 5 | icon, 6 | iconClass, 7 | title, 8 | description, 9 | action, 10 | }: { 11 | icon?: any; 12 | iconClass?: string; 13 | title?: string; 14 | description?: string | React.ReactNode; 15 | action?: React.ReactNode; 16 | }) => ( 17 |
18 | {icon && } 19 | {title &&

{title}

} 20 | {description &&
{description}
} 21 | {action && <>{action}} 22 |
23 | ); 24 | 25 | export default State; 26 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/components/TransactionStatus/StatusTxDetails.tsx: -------------------------------------------------------------------------------- 1 | import { faSearch } from '@fortawesome/free-solid-svg-icons'; 2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 3 | import { useContext } from 'context'; 4 | import * as React from 'react'; 5 | 6 | const StatusTxDetails = ({ txHash }: { txHash: string }) => { 7 | const { explorerAddress } = useContext(); 8 | return ( 9 | <> 10 |
Transaction hash:
{' '} 11 | 12 | {txHash} 13 | 14 | 21 | 22 | 23 | 24 | ); 25 | }; 26 | 27 | export default StatusTxDetails; 28 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/components/TransactionStatus/index.tsx: -------------------------------------------------------------------------------- 1 | import { TransactionHash } from '@elrondnetwork/erdjs'; 2 | import { faCheck, faHourglass, faTimes } from '@fortawesome/free-solid-svg-icons'; 3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 4 | import { useContext } from 'context'; 5 | import useInterval from 'helpers/useInterval'; 6 | import { useEffect, useState } from 'react'; 7 | import { Modal } from 'react-bootstrap'; 8 | import { useHistory } from 'react-router-dom'; 9 | import StatusTxDetails from './StatusTxDetails'; 10 | import txStatus from './txStatus'; 11 | export interface TransactionStatusModalType { 12 | show: boolean; 13 | txHash: TransactionHash; 14 | } 15 | const TransactionStatusModal = ({ show, txHash }: TransactionStatusModalType) => { 16 | const [lastTxStatus, setLastTxStatus] = useState(txStatus.pending); 17 | const [spin, setSpin] = useState(false); 18 | const [delay] = useState(1000); 19 | const [txDStatus, setTxStatus] = useState({ 20 | icon: faHourglass, 21 | status: txStatus.pending, 22 | title: 'Procesing request', 23 | }); 24 | const { dapp } = useContext(); 25 | const history = useHistory(); 26 | 27 | const handleCloseModal = () => { 28 | history.push(''); 29 | }; 30 | const getStatus = (current: string) => lastTxStatus === current.toLowerCase(); 31 | 32 | useInterval( 33 | () => { 34 | getTransactionStatus(txHash); 35 | setSpin(currentSpin => !currentSpin); 36 | }, 37 | txDStatus.status.toLowerCase() === 'pending' ? delay : null 38 | ); 39 | 40 | useEffect(() => {}, [lastTxStatus]); 41 | 42 | const getTransactionStatus = (hash: TransactionHash) => { 43 | if (!txHash.isEmpty()) { 44 | dapp.apiProvider 45 | .getTransaction(hash) 46 | .then(transaction => { 47 | switch (true) { 48 | case getStatus(txStatus.success): 49 | setTxStatus({ icon: faCheck, status: 'Success', title: 'Success' }); 50 | break; 51 | 52 | case getStatus(txStatus.notExecuted): 53 | case getStatus(txStatus.invalid): 54 | case getStatus(txStatus.fail): 55 | setTxStatus({ icon: faTimes, status: 'Failed', title: 'Request failed' }); 56 | break; 57 | default: 58 | setTxStatus({ icon: faHourglass, status: 'Pending', title: 'Procesing request' }); 59 | } 60 | setLastTxStatus(transaction.status.status.valueOf().toLowerCase()); 61 | }) 62 | .catch(e => console.log('error ', e)); 63 | } 64 | }; 65 | 66 | return ( 67 | 74 |
75 |
76 |
77 | {txDStatus.title} 78 |
79 |
80 | 84 | 85 |
86 | 87 | 95 |
96 |
97 |
98 | ); 99 | }; 100 | 101 | export default TransactionStatusModal; 102 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/components/TransactionStatus/txStatus.tsx: -------------------------------------------------------------------------------- 1 | const txStatus = { 2 | pending: 'Pending', 3 | notExecuted: 'Not Executed', 4 | invalid: 'Invalid', 5 | success: 'Success', 6 | fail: 'Fail', 7 | }; 8 | 9 | export default txStatus; 10 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/context/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { StateType, initialState } from './state'; 3 | import { DispatchType, reducer } from './reducer'; 4 | 5 | export interface ContextType { 6 | children: React.ReactNode; 7 | } 8 | 9 | const Context = React.createContext(undefined); 10 | const Dispatch = React.createContext(undefined); 11 | 12 | function ContextProvider({ children }: ContextType) { 13 | const [state, dispatch] = React.useReducer(reducer, initialState()); 14 | return ( 15 | 16 | {children} 17 | 18 | ); 19 | } 20 | 21 | function useContext() { 22 | const context = React.useContext(Context); 23 | if (context === undefined) { 24 | throw new Error('useState must be used within a Context.Provider'); 25 | } 26 | return context; 27 | } 28 | 29 | function useDispatch() { 30 | const context = React.useContext(Dispatch); 31 | if (context === undefined) { 32 | throw new Error('useDispatch must be used within a Dispatch.Provider'); 33 | } 34 | return context; 35 | } 36 | 37 | export { ContextProvider, useContext, useDispatch }; 38 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/contracts/ContractViews.ts: -------------------------------------------------------------------------------- 1 | import { Address, AddressValue, ContractFunction, Query } from '@elrondnetwork/erdjs'; 2 | import { auctionContract, delegationManagerContract } from 'config'; 3 | import { DappState } from '../context/state'; 4 | 5 | export const contractViews = { 6 | getUserActiveStake: (dapp: DappState, address: string, delegationContract?: string) => { 7 | const query = new Query({ 8 | address: new Address(delegationContract), 9 | func: new ContractFunction('getUserActiveStake'), 10 | args: [new AddressValue(new Address(address))], 11 | }); 12 | return dapp.proxy.queryContract(query); 13 | }, 14 | getUserUnDelegatedList: (dapp: DappState, address: string, delegationContract?: string) => { 15 | const query = new Query({ 16 | address: new Address(delegationContract), 17 | func: new ContractFunction('getUserUnDelegatedList'), 18 | args: [new AddressValue(new Address(address))], 19 | }); 20 | return dapp.proxy.queryContract(query); 21 | }, 22 | getClaimableRewards: (dapp: DappState, address: string, delegationContract?: string) => { 23 | const query = new Query({ 24 | address: new Address(delegationContract), 25 | func: new ContractFunction('getClaimableRewards'), 26 | args: [new AddressValue(new Address(address))], 27 | }); 28 | return dapp.proxy.queryContract(query); 29 | }, 30 | getTotalActiveStake: (dapp: DappState, delegationContract?: string) => { 31 | const query = new Query({ 32 | address: new Address(delegationContract), 33 | func: new ContractFunction('getTotalActiveStake'), 34 | }); 35 | return dapp.proxy.queryContract(query); 36 | }, 37 | getNumNodes: (dapp: DappState, delegationContract?: string) => { 38 | const query = new Query({ 39 | address: new Address(delegationContract), 40 | func: new ContractFunction('getNumNodes'), 41 | }); 42 | return dapp.proxy.queryContract(query); 43 | }, 44 | getNumUsers: (dapp: DappState, delegationContract?: string) => { 45 | const query = new Query({ 46 | address: new Address(delegationContract), 47 | func: new ContractFunction('getNumUsers'), 48 | }); 49 | return dapp.proxy.queryContract(query); 50 | }, 51 | getContractConfig: (dapp: DappState, delegationContract?: string) => { 52 | const query = new Query({ 53 | address: new Address(delegationContract), 54 | func: new ContractFunction('getContractConfig'), 55 | }); 56 | return dapp.proxy.queryContract(query); 57 | }, 58 | getMetaData: (dapp: DappState, delegationContract?: string) => { 59 | const query = new Query({ 60 | address: new Address(delegationContract), 61 | func: new ContractFunction('getMetaData'), 62 | }); 63 | return dapp.proxy.queryContract(query); 64 | }, 65 | getBlsKeys: (dapp: DappState, delegationContract?: string) => { 66 | const query = new Query({ 67 | address: new Address(auctionContract), 68 | func: new ContractFunction('getBlsKeysStatus'), 69 | args: [new AddressValue(new Address(delegationContract))], 70 | }); 71 | return dapp.proxy.queryContract(query); 72 | }, 73 | getDelegationManagerContractConfig: (dapp: DappState) => { 74 | const query = new Query({ 75 | address: new Address(delegationManagerContract), 76 | func: new ContractFunction('getContractConfig'), 77 | }); 78 | return dapp.proxy.queryContract(query); 79 | }, 80 | }; 81 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/contracts/Delegation.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | ProxyProvider, 3 | ContractFunction, 4 | Transaction, 5 | TransactionPayload, 6 | Balance, 7 | GasLimit, 8 | IDappProvider, 9 | WalletProvider, 10 | WalletConnectProvider, 11 | HWProvider, 12 | Address, 13 | SmartContract, 14 | } from '@elrondnetwork/erdjs'; 15 | import { setItem } from '../storage/session'; 16 | import { delegationContractData } from '../config'; 17 | import { AccountType, DelegationTransactionType } from 'helpers/contractDataDefinitions'; 18 | 19 | export default class Delegation { 20 | contract: SmartContract; 21 | proxyProvider: ProxyProvider; 22 | signerProvider?: IDappProvider; 23 | account?: AccountType; 24 | 25 | constructor( 26 | provider: ProxyProvider, 27 | delegationContract?: string, 28 | signer?: IDappProvider, 29 | account?: AccountType 30 | ) { 31 | const address = new Address(delegationContract); 32 | this.contract = new SmartContract({ address }); 33 | this.proxyProvider = provider; 34 | this.signerProvider = signer; 35 | this.account = account; 36 | } 37 | 38 | async sendTransaction( 39 | delegationTransactionType: DelegationTransactionType 40 | ): Promise { 41 | if (!this.signerProvider) { 42 | throw new Error( 43 | 'You need a singer to send a transaction, use either WalletProvider or LedgerProvider' 44 | ); 45 | } 46 | 47 | switch (this.signerProvider.constructor) { 48 | case WalletProvider: 49 | // Can use something like this to handle callback redirect 50 | setItem('transaction_identifier', true, 120); 51 | return this.sendTransactionBasedOnType(delegationTransactionType); 52 | case HWProvider: 53 | return this.sendTransactionBasedOnType(delegationTransactionType); 54 | case WalletConnectProvider: 55 | return this.sendTransactionBasedOnType(delegationTransactionType); 56 | default: 57 | console.warn('invalid signerProvider'); 58 | } 59 | 60 | throw new Error('invalid signerProvider'); 61 | } 62 | 63 | private async sendTransactionBasedOnType( 64 | delegationTransactionType: DelegationTransactionType 65 | ): Promise { 66 | let delegationContract = delegationContractData.find( 67 | d => d.name === delegationTransactionType.type 68 | ); 69 | if (!delegationContract) { 70 | throw new Error('The contract for this action in not defined'); 71 | } else { 72 | let funcName = delegationContract.data; 73 | let gasLimit = delegationContract.gasLimit; 74 | if (delegationTransactionType.args !== '') { 75 | funcName = `${delegationContract.data}${delegationTransactionType.args}`; 76 | } 77 | if (delegationContract.data === 'addNodes' && delegationTransactionType.args) { 78 | const nodeKeys = delegationTransactionType.args.split('@').slice(1); 79 | const numNodes = nodeKeys.length / 2; 80 | gasLimit = delegationContract.gasLimit * numNodes; 81 | } 82 | const func = new ContractFunction(funcName); 83 | let payload = TransactionPayload.contractCall() 84 | .setFunction(func) 85 | .build(); 86 | let transaction = new Transaction({ 87 | chainID: delegationTransactionType.chainId, 88 | receiver: this.contract.getAddress(), 89 | value: Balance.egld(delegationTransactionType.value), 90 | gasLimit: new GasLimit(gasLimit), 91 | data: payload, 92 | nonce: this.account?.nonce, 93 | }); 94 | 95 | // @ts-ignore 96 | 97 | return await this.signerProvider.sendTransaction(transaction); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/contracts/index.js: -------------------------------------------------------------------------------- 1 | export { default as Delegation } from './Delegation'; 2 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/helpers/contractDataDefinitions.ts: -------------------------------------------------------------------------------- 1 | import { ChainID, Nonce } from '@elrondnetwork/erdjs'; 2 | import BigNumber from 'bignumber.js'; 3 | 4 | export class ContractOverview { 5 | ownerAddress: string; 6 | serviceFee?: string; 7 | maxDelegationCap: string; 8 | initialOwnerFunds?: string; 9 | automaticActivation: string; 10 | withDelegationCap?: string; 11 | changeableServiceFee?: string; 12 | reDelegationCap: string; 13 | createdNounce?: string; 14 | unBondPeriod?: number; 15 | public constructor( 16 | ownerAddress: string = '', 17 | serviceFee?: string, 18 | maxDelegationCap: string = '', 19 | initialOwnerFunds?: string, 20 | automaticActivation: string = 'false', 21 | withDelegationCap: string = 'false', 22 | changeableServiceFee?: string, 23 | reDelegationCap: string = 'false', 24 | createdNounce?: string, 25 | unBondPeriod?: number 26 | ) { 27 | this.ownerAddress = ownerAddress; 28 | this.serviceFee = serviceFee; 29 | this.maxDelegationCap = maxDelegationCap; 30 | this.initialOwnerFunds = initialOwnerFunds; 31 | this.automaticActivation = automaticActivation; 32 | this.withDelegationCap = withDelegationCap; 33 | this.changeableServiceFee = changeableServiceFee; 34 | this.reDelegationCap = reDelegationCap; 35 | this.createdNounce = createdNounce; 36 | this.unBondPeriod = unBondPeriod; 37 | } 38 | } 39 | 40 | export class NetworkStake { 41 | totalValidators: number; 42 | activeValidators: number; 43 | queueSize: number; 44 | totalStaked: BigNumber; 45 | public constructor( 46 | totalValidators: number, 47 | activeValidators: number, 48 | queueSize: number, 49 | totalStaked: BigNumber 50 | ) { 51 | this.totalValidators = totalValidators; 52 | this.activeValidators = activeValidators; 53 | this.queueSize = queueSize; 54 | this.totalStaked = totalStaked; 55 | } 56 | } 57 | 58 | export class Stats { 59 | epoch: number; 60 | public constructor(epoch: number) { 61 | this.epoch = epoch; 62 | } 63 | } 64 | 65 | export class NetworkConfig { 66 | topUpFactor: number; 67 | roundDuration: number; 68 | roundsPerEpoch: number; 69 | roundsPassedInCurrentEpoch: number; 70 | topUpRewardsGradientPoint: BigNumber; 71 | chainId: ChainID; 72 | public constructor( 73 | topUpFactor: number, 74 | roundDuration: number, 75 | roundsPerEpoch: number, 76 | roundsPassedInCurrentEpoch: number, 77 | topUpRewardsGradientPoint: BigNumber, 78 | chainId: ChainID 79 | ) { 80 | this.topUpFactor = topUpFactor; 81 | this.roundDuration = roundDuration; 82 | this.roundsPerEpoch = roundsPerEpoch; 83 | this.roundsPassedInCurrentEpoch = roundsPassedInCurrentEpoch; 84 | this.topUpRewardsGradientPoint = topUpRewardsGradientPoint; 85 | this.chainId = chainId; 86 | } 87 | } 88 | 89 | export class AgencyMetadata { 90 | name: string; 91 | website: string; 92 | keybase: string; 93 | public constructor(name: string = '', website: string = '', keybase: string = '') { 94 | this.name = name; 95 | this.website = website; 96 | this.keybase = keybase; 97 | } 98 | } 99 | 100 | export class AccountType { 101 | balance: string; 102 | nonce: Nonce; 103 | public constructor(balance: string = '', nonce: Nonce) { 104 | this.balance = balance; 105 | this.nonce = nonce; 106 | } 107 | } 108 | 109 | export class DelegationTransactionType { 110 | value: string; 111 | type: string; 112 | chainId?: ChainID; 113 | args?: string; 114 | public constructor( 115 | value: string = '', 116 | type: string, 117 | args: string = '', 118 | chainId: ChainID = new ChainID('T') 119 | ) { 120 | this.value = value; 121 | this.type = type; 122 | this.args = args; 123 | this.chainId = chainId; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/helpers/decodePem.tsx: -------------------------------------------------------------------------------- 1 | import { Address, BLS, ValidatorSecretKey } from '@elrondnetwork/erdjs'; 2 | 3 | function hexStringToByte(str: string) { 4 | if (!str) { 5 | return new Uint8Array(); 6 | } 7 | const a = []; 8 | for (let i = 0, len = str.length; i < len; i += 2) { 9 | a.push(parseInt(str.substr(i, 2), 16)); 10 | } 11 | return new Uint8Array(a); 12 | } 13 | 14 | function getPubKey(file: string, indices: any[]) { 15 | const headerParts = file 16 | .toString() 17 | .substring(indices[0], indices[1]) 18 | .split(' '); 19 | return headerParts[4] ? headerParts[4] : ''; 20 | } 21 | 22 | export default async function decodePem(file: string, delegationContract?: string) { 23 | await BLS.initIfNecessary(); 24 | let myKey = ValidatorSecretKey.fromPem(file); 25 | let dsc = new Address(delegationContract); 26 | let signature = myKey.sign(Buffer.from(dsc.pubkey())).toString('hex'); 27 | 28 | const regex = /-----/gi; 29 | let result; 30 | const indices = []; 31 | while ((result = regex.exec(file.toString()))) { 32 | indices.push(result.index); 33 | } 34 | 35 | const key = file.toString().substring(indices[1] + 6, indices[2] - 1); 36 | const decoded = window.atob(key); 37 | 38 | const value = hexStringToByte(decoded); 39 | 40 | const pubKey = getPubKey(file, indices); 41 | 42 | return { value, pubKey, signature }; 43 | } 44 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/helpers/entireBalance.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js'; 2 | import { minDust } from 'config'; 3 | import denominate from '../components/Denominate/formatters'; 4 | 5 | interface EntireBalanceType { 6 | balance: string; 7 | gasPrice: string; 8 | gasLimit: string; 9 | denomination: number; 10 | decimals: number; 11 | } 12 | 13 | export default function entireBalance({ 14 | balance, 15 | gasLimit, 16 | gasPrice, 17 | denomination, 18 | decimals, 19 | }: EntireBalanceType) { 20 | const bnBalance = new BigNumber(balance); 21 | const bnMinDust = new BigNumber(minDust); 22 | const bnGasPrice = new BigNumber(gasPrice); 23 | const bnGasLimit = new BigNumber(gasLimit); 24 | const entireBn = bnBalance.minus(bnGasPrice.times(bnGasLimit)); 25 | const entireBnMinusDust = entireBn.minus(bnMinDust); 26 | 27 | const entireBalance = 28 | // entireBalance >= 0 29 | entireBn.comparedTo(0) === 1 30 | ? denominate({ 31 | input: entireBn.toString(10), 32 | denomination, 33 | decimals, 34 | showLastNonZeroDecimal: true, 35 | addCommas: false, 36 | }) 37 | : '0'; 38 | 39 | const entireBalanceMinusDust = 40 | entireBnMinusDust.comparedTo(0) === 1 41 | ? denominate({ 42 | input: entireBnMinusDust.toString(10), 43 | denomination, 44 | decimals, 45 | showLastNonZeroDecimal: true, 46 | addCommas: false, 47 | }) 48 | : entireBalance; 49 | 50 | return { 51 | entireBalance, 52 | entireBalanceMinusDust, 53 | }; 54 | } 55 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/helpers/index.ts: -------------------------------------------------------------------------------- 1 | import useDelegation from './useDelegation'; 2 | import * as decodePem from './decodePem'; 3 | import entireBalance from './entireBalance'; 4 | import * as nominate from './nominate'; 5 | import * as types from './types'; 6 | 7 | export { decodePem }; 8 | export { entireBalance }; 9 | export { nominate }; 10 | export { types }; 11 | export { useDelegation }; 12 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/helpers/ledgerErrorCodes.ts: -------------------------------------------------------------------------------- 1 | export const ledgerErrorCodes = { 2 | 0x9000: { 3 | code: 'codeSuccess', 4 | message: 'Success', 5 | }, 6 | 0x6985: { 7 | code: 'ERR_USER_DENIED', 8 | message: 'Rejected by user', 9 | }, 10 | 0x6d00: { 11 | code: 'ERR_UNKNOWN_INSTRUCTION', 12 | message: 'Unknown instruction', 13 | }, 14 | 0x6e00: { 15 | code: 'ERR_WRONG_CLA', 16 | message: 'Wrong CLA', 17 | }, 18 | 0x6e01: { 19 | code: 'ERR_INVALID_ARGUMENTS', 20 | message: 'Invalid arguments', 21 | }, 22 | 0x6e02: { 23 | code: 'ERR_INVALID_MESSAGE', 24 | message: 'Invalid message', 25 | }, 26 | 0x6e03: { 27 | code: 'ERR_INVALID_P1', 28 | message: 'Invalid P1', 29 | }, 30 | 0x6e04: { 31 | code: 'ERR_MESSAGE_TOO_LONG', 32 | message: 'Message too long', 33 | }, 34 | 0x6e05: { 35 | code: 'ERR_RECEIVER_TOO_LONG', 36 | message: 'Receiver too long', 37 | }, 38 | 0x6e06: { 39 | code: 'ERR_AMOUNT_TOO_LONG', 40 | message: 'Amount too long', 41 | }, 42 | 0x6e07: { 43 | code: 'ERR_CONTRACT_DATA_DISABLED', 44 | message: 'Contract data disabled in app options', 45 | }, 46 | 0x6e08: { 47 | code: 'ERR_MESSAGE_INCOMPLETE', 48 | message: 'Message incomplete', 49 | }, 50 | 0x6e10: { 51 | code: 'ERR_SIGNATURE_FAILED', 52 | message: 'Signature failed', 53 | }, 54 | 0x6e09: { 55 | code: 'ERR_WRONG_TX_VERSION', 56 | message: 'Wrong TX version', 57 | }, 58 | 0x6e0a: { 59 | code: 'ERR_NONCE_TOO_LONG', 60 | message: 'Nonce too long', 61 | }, 62 | 0x6e0b: { 63 | code: 'ERR_INVALID_AMOUNT', 64 | message: 'Invalid amount', 65 | }, 66 | 0x6e0c: { 67 | code: 'ERR_INVALID_FEE', 68 | message: 'Invalid fee', 69 | }, 70 | 0x6e0d: { 71 | code: 'ERR_PRETTY_FAILED', 72 | message: 'Pretty failed', 73 | }, 74 | 0x6e0e: { 75 | code: 'ERR_DATA_TOO_LONG', 76 | message: 'Data too long', 77 | }, 78 | 0x6e0f: { 79 | code: 'ERR_WRONG_TX_OPTIONS', 80 | message: 'Invalid transaction options', 81 | }, 82 | 0x6e11: { 83 | code: 'ERR_SIGN_TX_DEPRECATED', 84 | message: 'Regular transaction signing is deprecated in this version. Use hash signing.', 85 | }, 86 | }; 87 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/helpers/nominate.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js'; 2 | 3 | export default function nominate(input: string, paramDenomination?: number) { 4 | const parts = input.toString().split('.'); 5 | const denomination = paramDenomination !== undefined ? paramDenomination : 18; 6 | 7 | if (parts[1]) { 8 | // remove trailing zeros 9 | while (parts[1].substring(parts[1].length - 1) === '0' && parts[1].length > 1) { 10 | parts[1] = parts[1].substring(0, parts[1].length - 1); 11 | } 12 | } 13 | 14 | let count = parts[1] ? denomination - parts[1].length : denomination; 15 | 16 | count = count < 0 ? 0 : count; 17 | 18 | let transformed = parts.join('') + '0'.repeat(count); 19 | 20 | // remove beginning zeros 21 | while (transformed.substring(0, 1) === '0' && transformed.length > 1) { 22 | transformed = transformed.substring(1); 23 | } 24 | 25 | return transformed; 26 | } 27 | 28 | export const nominateValToHex = (value: string) => { 29 | let val = value && value.length > 0 ? new BigNumber(nominate(value)).toString(16) : '0'; 30 | 31 | if (val.length % 2 !== 0) { 32 | val = '0' + val; 33 | } 34 | 35 | return val; 36 | }; 37 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/helpers/types.ts: -------------------------------------------------------------------------------- 1 | export interface AccountType { 2 | address: string; 3 | balance: string; 4 | nonce: number; 5 | code?: string; 6 | } 7 | 8 | export interface DelegationContractType { 9 | name: string; 10 | gasLimit: number; 11 | data: string; 12 | } 13 | 14 | export interface StatCardType { 15 | title?: string; 16 | value?: string; 17 | valueUnit?: string; 18 | svg?: string; 19 | color?: string; 20 | percentage?: string; 21 | tooltipText?: string; 22 | children?: any; 23 | } 24 | 25 | export interface ActionModalType { 26 | balance?: string; 27 | show: boolean; 28 | title: string; 29 | description: string; 30 | handleClose: () => void; 31 | handleContinue: (value: string) => void; 32 | } 33 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/helpers/useDelegation.ts: -------------------------------------------------------------------------------- 1 | import { TransactionHash } from '@elrondnetwork/erdjs'; 2 | import { useContext } from 'context'; 3 | import { Delegation } from 'contracts'; 4 | import { DelegationTransactionType } from './contractDataDefinitions'; 5 | import { ledgerErrorCodes } from './ledgerErrorCodes'; 6 | export interface UseDelegationType { 7 | handleClose: (txHash: TransactionHash) => void; 8 | setError: (error: string) => void; 9 | } 10 | export default function useDelegation({ 11 | handleClose, 12 | setError: setTransactionError, 13 | }: UseDelegationType) { 14 | const { dapp, delegationContract, account, networkConfig } = useContext(); 15 | const delegation = new Delegation(dapp.proxy, delegationContract, dapp.provider, account); 16 | 17 | const sendTransaction = (transactionArguments: DelegationTransactionType) => { 18 | transactionArguments.chainId = networkConfig.chainId; 19 | delegation 20 | .sendTransaction(transactionArguments) 21 | .then(transaction => { 22 | handleClose(transaction.getHash()); 23 | }) 24 | .catch(e => { 25 | if (e.statusCode in ledgerErrorCodes) { 26 | setTransactionError((ledgerErrorCodes as any)[e.statusCode].message); 27 | } 28 | if (e.message === 'HWApp not initialised, call init() first') { 29 | setTransactionError('Your session has expired. Please login again'); 30 | } 31 | if (e.message === 'Failed or Rejected Request') { 32 | setTransactionError('Failed or Rejected Request. Please try again'); 33 | } 34 | if (e.message === 'cancel') { 35 | setTransactionError('Transaction Cancelled'); 36 | } 37 | 38 | console.error(`${transactionArguments.type}`, e); 39 | }); 40 | }; 41 | 42 | return { sendTransaction }; 43 | } 44 | 45 | export function useDelegationWallet() { 46 | const { dapp, delegationContract, account } = useContext(); 47 | const delegation = new Delegation(dapp.proxy, delegationContract, dapp.provider, account); 48 | const sendTransactionWallet = (transactionArguments: DelegationTransactionType) => { 49 | delegation 50 | .sendTransaction(transactionArguments) 51 | .then() 52 | .catch(e => { 53 | console.error(`${transactionArguments.type}`, e); 54 | }); 55 | }; 56 | 57 | return { sendTransactionWallet }; 58 | } 59 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/helpers/useInterval.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | 3 | function useInterval(callback: () => void, delay: number | null) { 4 | const savedCallback = useRef<() => void | null>(); 5 | 6 | useEffect(() => { 7 | savedCallback.current = callback; 8 | }); 9 | 10 | useEffect(() => { 11 | function tick() { 12 | if (typeof savedCallback?.current !== 'undefined') { 13 | savedCallback?.current(); 14 | } 15 | } 16 | 17 | if (delay !== null) { 18 | const id = setInterval(tick, delay); 19 | return () => clearInterval(id); 20 | } 21 | }, [delay]); 22 | } 23 | 24 | export default useInterval; 25 | -------------------------------------------------------------------------------- /react-delegationdashboard/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 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { StateInspector } from 'reinspect'; 4 | import App from './App'; 5 | import './assets/styles/theme.scss'; 6 | 7 | let MountedApp = ; 8 | 9 | if (process.env.NODE_ENV === 'development') { 10 | MountedApp = ( 11 | 12 | 13 | 14 | ); 15 | } 16 | 17 | ReactDOM.render(MountedApp, document.getElementById('root')); 18 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/pages/Dashboard/Actions/ClaimRewardsAction/ClaimRewardsModal.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Modal } from 'react-bootstrap'; 3 | import { useContext } from 'context'; 4 | import BigNumber from 'bignumber.js'; 5 | import { DelegationTransactionType } from 'helpers/contractDataDefinitions'; 6 | import { useDelegationWallet } from 'helpers/useDelegation'; 7 | import ConfirmTransactionModal from 'components/ConfirmTransactionModal'; 8 | export interface ClaimRewardsModalType { 9 | show: boolean; 10 | title: string; 11 | description: string; 12 | handleClose: () => void; 13 | } 14 | const ClaimRewardsModal = ({ show, title, description, handleClose }: ClaimRewardsModalType) => { 15 | const { totalActiveStake, contractOverview, ledgerAccount, walletConnectAccount } = useContext(); 16 | const [showCheckYourLedgerModal, setShowCheckYourLedgerModal] = useState(false); 17 | const [transactionArguments, setTransactionArguments] = useState( 18 | new DelegationTransactionType('', '') 19 | ); 20 | const { sendTransactionWallet } = useDelegationWallet(); 21 | 22 | const handleClaimRewards = (): void => { 23 | let txArguments = new DelegationTransactionType('0', 'claimRewards'); 24 | if (ledgerAccount || walletConnectAccount) { 25 | handleClose(); 26 | setTransactionArguments(txArguments); 27 | setShowCheckYourLedgerModal(true); 28 | } else { 29 | sendTransactionWallet(txArguments); 30 | } 31 | }; 32 | 33 | const isRedelegateEnable = () => { 34 | const bnTotalActiveStake = new BigNumber(totalActiveStake); 35 | const bnMaxDelegationCap = new BigNumber(contractOverview.maxDelegationCap); 36 | if ( 37 | bnTotalActiveStake.comparedTo(bnMaxDelegationCap) >= 0 && 38 | contractOverview.reDelegationCap === 'true' 39 | ) { 40 | return false; 41 | } 42 | return true; 43 | }; 44 | 45 | const handleRedelegateRewards = () => { 46 | let txArguments = new DelegationTransactionType('0', 'reDelegateRewards'); 47 | if (ledgerAccount || walletConnectAccount) { 48 | handleClose(); 49 | setTransactionArguments(txArguments); 50 | setShowCheckYourLedgerModal(true); 51 | } else { 52 | sendTransactionWallet(txArguments); 53 | } 54 | }; 55 | 56 | return ( 57 | <> 58 | 65 |
66 |
67 |

68 | {title} 69 |

70 |

{description}

71 |
72 | 80 | {isRedelegateEnable() && ( 81 | 89 | )} 90 |
91 | 94 |
95 |
96 |
97 | { 101 | setShowCheckYourLedgerModal(false); 102 | }} 103 | /> 104 | 105 | ); 106 | }; 107 | 108 | export default ClaimRewardsModal; 109 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/pages/Dashboard/Actions/ClaimRewardsAction/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import ClaimRewardsModal from './ClaimRewardsModal'; 3 | 4 | const ClaimRewardsAction = () => { 5 | const [showClaimRewardsModal, setShowClaimRewardsModal] = useState(false); 6 | 7 | return ( 8 |
9 | 17 | { 22 | setShowClaimRewardsModal(false); 23 | }} 24 | /> 25 |
26 | ); 27 | }; 28 | 29 | export default ClaimRewardsAction; 30 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/pages/Dashboard/Actions/DelegateAction/index.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { useContext } from 'context'; 3 | import DelegateModal from './DelegateModal'; 4 | import { DelegationTransactionType } from 'helpers/contractDataDefinitions'; 5 | import ConfirmTransactionModal from 'components/ConfirmTransactionModal'; 6 | import { useDelegationWallet } from 'helpers/useDelegation'; 7 | 8 | const DelegateAction = () => { 9 | const { account, ledgerAccount, walletConnectAccount } = useContext(); 10 | const [showDelegateModal, setShowDelegateModal] = useState(false); 11 | const [showCheckYourLedgerModal, setShowCheckYourLedgerModal] = useState(false); 12 | const [transactionArguments, setTransactionArguments] = useState( 13 | new DelegationTransactionType('', '') 14 | ); 15 | const { sendTransactionWallet } = useDelegationWallet(); 16 | 17 | const handleDelegate = (value: string) => { 18 | const txArguments = new DelegationTransactionType(value, 'delegate'); 19 | if (ledgerAccount || walletConnectAccount) { 20 | setShowDelegateModal(false); 21 | setTransactionArguments(txArguments); 22 | setShowCheckYourLedgerModal(true); 23 | } else { 24 | sendTransactionWallet(txArguments); 25 | } 26 | }; 27 | 28 | return ( 29 |
30 | 38 | { 42 | setShowDelegateModal(false); 43 | }} 44 | handleContinue={handleDelegate} 45 | /> 46 | { 50 | setShowCheckYourLedgerModal(false); 51 | }} 52 | /> 53 |
54 | ); 55 | }; 56 | 57 | export default DelegateAction; 58 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/pages/Dashboard/Actions/UndelegateAction/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { useContext } from 'context'; 3 | import { nominateValToHex } from 'helpers/nominate'; 4 | import UndelegateModal from './UndelegateModal'; 5 | import { DelegationTransactionType } from 'helpers/contractDataDefinitions'; 6 | import { useDelegationWallet } from 'helpers/useDelegation'; 7 | import ConfirmTransactionModal from 'components/ConfirmTransactionModal'; 8 | 9 | interface UndelegateModalType { 10 | balance: string; 11 | } 12 | 13 | const UndelegateAction = ({ balance }: UndelegateModalType) => { 14 | const { egldLabel, ledgerAccount, walletConnectAccount } = useContext(); 15 | const [showModal, setShowModal] = useState(false); 16 | const [showCheckYourLedgerModal, setShowCheckYourLedgerModal] = useState(false); 17 | const [transactionArguments, setTransactionArguments] = useState( 18 | new DelegationTransactionType('', '') 19 | ); 20 | const { sendTransactionWallet } = useDelegationWallet(); 21 | 22 | const handleUndelegate = (value: string) => { 23 | let txArguments = new DelegationTransactionType('0', 'unDelegate', nominateValToHex(value)); 24 | if (ledgerAccount || walletConnectAccount) { 25 | setShowModal(false); 26 | setTransactionArguments(txArguments); 27 | setShowCheckYourLedgerModal(true); 28 | } else { 29 | sendTransactionWallet(txArguments); 30 | } 31 | }; 32 | return ( 33 |
34 | 37 | { 43 | setShowModal(false); 44 | }} 45 | handleContinue={handleUndelegate} 46 | /> 47 | { 51 | setShowCheckYourLedgerModal(false); 52 | }} 53 | /> 54 |
55 | ); 56 | }; 57 | 58 | export default UndelegateAction; 59 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/pages/Dashboard/PendingUndelegated/UndelegatedValueRow.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import moment from 'moment'; 3 | import { useContext } from 'context'; 4 | import { UndelegatedValueType } from './UndelegatedValueType'; 5 | import { DelegationTransactionType } from 'helpers/contractDataDefinitions'; 6 | import { useDelegationWallet } from 'helpers/useDelegation'; 7 | import ConfirmTransactionModal from 'components/ConfirmTransactionModal'; 8 | 9 | const UndelegatedValueRow = ({ 10 | undelegatedValue: value, 11 | }: { 12 | undelegatedValue: UndelegatedValueType; 13 | }) => { 14 | const [isDisabled, setIsDisabled] = React.useState(true); 15 | const { egldLabel, ledgerAccount, walletConnectAccount } = useContext(); 16 | const [counter, setCounter] = React.useState(value.timeLeft); 17 | const [showCheckYourLedgerModal, setShowCheckYourLedgerModal] = useState(false); 18 | const [transactionArguments, setTransactionArguments] = useState( 19 | new DelegationTransactionType('', '') 20 | ); 21 | const { sendTransactionWallet } = useDelegationWallet(); 22 | 23 | const handleWithdraw = () => { 24 | let txArguments = new DelegationTransactionType('0', 'withdraw'); 25 | if (ledgerAccount || walletConnectAccount) { 26 | setTransactionArguments(txArguments); 27 | setShowCheckYourLedgerModal(true); 28 | } else { 29 | sendTransactionWallet(txArguments); 30 | } 31 | }; 32 | 33 | useEffect(() => { 34 | counter > 0 && setTimeout(() => setCounter(counter - 1), 1000); 35 | counter === 0 && setIsDisabled(false); 36 | }, [counter]); 37 | 38 | const getTimeLeft = () => { 39 | if (counter === 0) setCounter(value.timeLeft); 40 | let waitingStartDate = moment.duration(counter, 'seconds'); 41 | if(waitingStartDate.asDays() >= 1){ 42 | return (waitingStartDate.asDays() | 0) + ' days'; 43 | } 44 | const timeLeftInMiliseconds = waitingStartDate.asMilliseconds(); 45 | return moment.utc(timeLeftInMiliseconds).format('HH:mm:ss'); 46 | }; 47 | return ( 48 | <> 49 | 50 | 51 |
52 | {value.value} {egldLabel} 53 |
54 | 55 | 56 |
57 | {value.timeLeft > 0 ? ( 58 | 59 | {getTimeLeft()} left 60 | 61 | ) : ( 62 | Completed 63 | )} 64 |
65 | 66 | 67 | 74 | 75 | 76 | { 80 | setShowCheckYourLedgerModal(false); 81 | }} 82 | /> 83 | 84 | ); 85 | }; 86 | export default UndelegatedValueRow; 87 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/pages/Dashboard/PendingUndelegated/UndelegatedValueType.ts: -------------------------------------------------------------------------------- 1 | export class UndelegatedValueType { 2 | value: string; 3 | timeLeft: number; 4 | public constructor(value: string, timeLeft: number) { 5 | this.value = value; 6 | this.timeLeft = timeLeft; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/pages/Dashboard/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { useContext, useDispatch } from 'context'; 3 | import Delegation from './Delegation'; 4 | import PendingUndelegated from './PendingUndelegated'; 5 | import { Redirect } from 'react-router-dom'; 6 | import Overview from 'components/Overview'; 7 | import { Address } from '@elrondnetwork/erdjs'; 8 | import { AccountType } from 'helpers/contractDataDefinitions'; 9 | import State from 'components/State'; 10 | import { faCircleNotch } from '@fortawesome/free-solid-svg-icons'; 11 | import { getItem } from 'storage/session'; 12 | 13 | const Dashboard = () => { 14 | const { 15 | loggedIn, 16 | dapp, 17 | address, 18 | networkConfig, 19 | ledgerAccount, 20 | walletConnectAccount, 21 | } = useContext(); 22 | const dispatch = useDispatch(); 23 | 24 | const fetchAccount = () => { 25 | if (loggedIn) { 26 | dapp.proxy.getAccount(new Address(address)).then(account => { 27 | dispatch({ 28 | type: 'setAccount', 29 | account: new AccountType(account.balance.toString(), account.nonce), 30 | }); 31 | }); 32 | } 33 | }; 34 | 35 | const isLedgerLogin = getItem('ledgerLogin') && !ledgerAccount; 36 | const isWalletConnect = getItem('walletConnectLogin') && !walletConnectAccount; 37 | const dispatchLoginType = () => { 38 | if (isLedgerLogin) { 39 | const ledgerLogin = getItem('ledgerLogin'); 40 | dispatch({ 41 | type: 'setLedgerAccount', 42 | ledgerAccount: { 43 | index: ledgerLogin.index, 44 | address: address, 45 | }, 46 | }); 47 | } 48 | if (isWalletConnect) { 49 | dispatch({ 50 | type: 'setWalletConnectAccount', 51 | walletConnectAccount: address, 52 | }); 53 | } 54 | }; 55 | useEffect(fetchAccount, /* eslint-disable react-hooks/exhaustive-deps */ []); 56 | 57 | useEffect(dispatchLoginType, /* eslint-disable react-hooks/exhaustive-deps */ []); 58 | if (!loggedIn) { 59 | return ; 60 | } 61 | return ( 62 |
63 |
64 | 65 | {networkConfig.roundDuration === -1 ? ( 66 | 67 | ) : ( 68 |
69 | 70 | 71 |
72 | )} 73 |
74 |
75 | ); 76 | }; 77 | 78 | export default Dashboard; 79 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/pages/Home/Login/Wallet.tsx: -------------------------------------------------------------------------------- 1 | import { Address, WalletProvider } from '@elrondnetwork/erdjs'; 2 | import React, { useEffect } from 'react'; 3 | import { useContext, useDispatch } from 'context'; 4 | import { getItem, removeItem, setItem } from 'storage/session'; 5 | import { AccountType } from 'helpers/contractDataDefinitions'; 6 | import { useLocation } from 'react-router-dom'; 7 | import { network } from 'config'; 8 | 9 | const WalletLogin = () => { 10 | const dispatch = useDispatch(); 11 | const { dapp } = useContext(); 12 | 13 | const { search } = useLocation(); 14 | const handleOnClick = () => { 15 | dispatch({ type: 'loading', loading: true }); 16 | dapp.provider 17 | .init() 18 | .then(async initialised => { 19 | if (initialised) { 20 | // Wallet provider will redirect, we can set a session information so we know when we are getting back 21 | // that we initiated a wallet provider login 22 | setItem('wallet_login', {}, 60); // Set a 60s session only 23 | await dapp.provider.login(); 24 | } else { 25 | dispatch({ type: 'loading', loading: true }); 26 | console.warn('Something went wrong trying to redirect to wallet login..'); 27 | } 28 | }) 29 | .catch(err => { 30 | dispatch({ type: 'loading', loading: false }); 31 | console.warn(err); 32 | }); 33 | }; 34 | 35 | const walletLogin = () => { 36 | if (getItem('wallet_login')) { 37 | dispatch({ type: 'loading', loading: true }); 38 | dapp.provider.init().then(initialised => { 39 | if (!initialised) { 40 | dispatch({ type: 'loading', loading: false }); 41 | return; 42 | } 43 | const urlSearchParams = new URLSearchParams(search); 44 | const params = Object.fromEntries(urlSearchParams as any); 45 | const address = params?.address; 46 | if (address !== undefined && new Address(params.address)) { 47 | removeItem('wallet_login'); 48 | dispatch({ type: 'login', address }); 49 | dapp.proxy 50 | .getAccount(new Address(address)) 51 | .then(account => 52 | dispatch({ 53 | type: 'setAccount', 54 | account: new AccountType(account.balance.toString(), account.nonce), 55 | }) 56 | ) 57 | .catch(err => { 58 | console.log({ err }); 59 | dispatch({ type: 'loading', loading: false }); 60 | }); 61 | } 62 | removeItem('wallet_login'); 63 | dispatch({ type: 'loading', loading: false }); 64 | return; 65 | }); 66 | } 67 | }; 68 | 69 | useEffect(walletLogin, /* eslint-disable react-hooks/exhaustive-deps */ [dapp.provider]); 70 | 71 | return ( 72 | 75 | ); 76 | }; 77 | 78 | export default WalletLogin; 79 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/pages/Home/Login/WalletConnect.tsx: -------------------------------------------------------------------------------- 1 | // @ts-ignore 2 | import platform from 'platform'; 3 | 4 | const WalletConnectLogin = () => { 5 | return ( 6 | 14 | Maiar 15 | 16 | ); 17 | }; 18 | 19 | export default WalletConnectLogin; 20 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/pages/Home/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Redirect } from 'react-router-dom'; 3 | import { faBan, faCircleNotch } from '@fortawesome/free-solid-svg-icons'; 4 | import { ReactComponent as Logo } from 'assets/images/logo.svg'; 5 | import State from 'components/State'; 6 | import { useContext } from 'context'; 7 | import WalletLogin from './Login/Wallet'; 8 | import WalletConnectLogin from './Login/WalletConnect'; 9 | 10 | const Home = () => { 11 | const { loading, error, loggedIn, egldLabel } = useContext(); 12 | 13 | const ref = React.useRef(null); 14 | 15 | return ( 16 |
17 | {error ? ( 18 | 24 | ) : loggedIn ? ( 25 | 26 | ) : loading ? ( 27 | 28 | ) : ( 29 |
30 |
31 |
32 | 33 |

Elrond Delegation Manager

34 |

35 | Delegate Elrond ({egldLabel}) and earn up to 25% APY! 36 |

37 |

Please select your login method:

38 |
39 | 43 | Ledger 44 | 45 | 46 | 47 |
48 |
49 |
50 |
51 | )} 52 |
53 | ); 54 | }; 55 | 56 | export default Home; 57 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/pages/Ledger/AddressRow.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | interface AddressRowType { 4 | setSelectedAddress: React.Dispatch>; 5 | setSelectedIndex: React.Dispatch>; 6 | selectedAddress: string; 7 | index: number; 8 | account: string; 9 | } 10 | 11 | const AddressRow = ({ 12 | account, 13 | index, 14 | setSelectedAddress, 15 | selectedAddress, 16 | setSelectedIndex, 17 | }: AddressRowType) => { 18 | const ref = React.useRef(null); 19 | 20 | const handleChange = (e: React.ChangeEvent) => { 21 | const checked = e.target.checked; 22 | if (checked) { 23 | setSelectedAddress(account); 24 | setSelectedIndex(index); 25 | } else if (selectedAddress === account && !checked) { 26 | setSelectedAddress(''); 27 | setSelectedIndex(undefined); 28 | } 29 | }; 30 | 31 | return ( 32 | 33 | 34 |
35 | 43 | 52 |
53 | 54 | {index} 55 | 56 | ); 57 | }; 58 | 59 | export default AddressRow; 60 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/pages/Ledger/ConfirmedAddress.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { useContext } from 'context'; 3 | 4 | const ConfirmedAddress = () => { 5 | const { ledgerAccount } = useContext(); 6 | 7 | return ( 8 |
9 |
10 |
11 |

Confirm Ledger Address

12 |

For security, please confirm that your address:

13 |

{ledgerAccount?.address}

14 |

is the one shown on your Ledger device screen now.

15 |

Select Approve on your device to confirm.

16 |

17 | Or, if it does not match, close this page and{' '} 18 | 24 | contact support 25 | 26 | . 27 |

28 |
29 |
30 |
31 | ); 32 | }; 33 | 34 | export default ConfirmedAddress; 35 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/pages/Ledger/LedgerConnect.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { ReactComponent as LedgerLogo } from 'assets/images/ledger-nano.svg'; 3 | 4 | const LedgerConnect = ({ onClick, error }: { onClick: () => void; error: string }) => { 5 | const [ledgerError] = React.useState(error); 6 | return ( 7 |
8 |
9 |
10 | 11 |

Connect Ledger

12 |

Unlock your device & open the Elrond App.

13 |
14 | {ledgerError && ( 15 |

16 | {ledgerError} 17 |

18 | )} 19 | 26 |
27 |
28 |
29 |
30 | ); 31 | }; 32 | 33 | export default LedgerConnect; 34 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/pages/Ledger/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useContext, useDispatch } from 'context'; 3 | import AddressTable from './AddressTable'; 4 | import ConfirmedAddress from './ConfirmedAddress'; 5 | import { HWProvider } from '@elrondnetwork/erdjs'; 6 | import { useHistory } from 'react-router-dom'; 7 | import LedgerConnect from './LedgerConnect'; 8 | 9 | const Ledger = () => { 10 | const { dapp } = useContext(); 11 | const dispatch = useDispatch(); 12 | const history = useHistory(); 13 | const { ledgerAccount } = useContext(); 14 | const [error, setError] = React.useState(''); 15 | const [showAddressTable, setShowAddressTable] = React.useState(false); 16 | 17 | const onClick = () => { 18 | setError(''); 19 | if (ledgerAccount !== undefined) { 20 | const hwWalletP = new HWProvider(dapp.proxy); 21 | hwWalletP 22 | .init() 23 | .then((success: any) => { 24 | if (!success) { 25 | dispatch({ type: 'loading', loading: false }); 26 | console.warn('could not initialise ledger app, make sure Elrond app is open'); 27 | return; 28 | } 29 | 30 | hwWalletP 31 | .login() 32 | .then(address => { 33 | dispatch({ type: 'setProvider', provider: hwWalletP }); 34 | dispatch({ type: 'login', address }); 35 | history.push('/dashboard'); 36 | }) 37 | .catch((err: any) => { 38 | setError('Check if Elrond app is open on Ledger'); 39 | dispatch({ type: 'loading', loading: false }); 40 | console.warn(err); 41 | }); 42 | }) 43 | .catch(error => { 44 | console.error('error ', error); 45 | }); 46 | } else { 47 | setShowAddressTable(true); 48 | } 49 | }; 50 | return ( 51 | <> 52 | {(() => { 53 | switch (true) { 54 | case ledgerAccount !== undefined && !error: 55 | return ; 56 | case showAddressTable && !error: 57 | return ( 58 | 63 | ); 64 | case error !== '': 65 | return ; 66 | default: 67 | return ; 68 | } 69 | })()} 70 | 71 | ); 72 | }; 73 | 74 | export default Ledger; 75 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/pages/Owner/Nodes/AddNodeAction.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { DropzoneFileType } from 'components/DropzonePem'; 3 | import RequestVariablesModal from 'components/DropzonePem/RequestVariablesModal'; 4 | import { BLS } from '@elrondnetwork/erdjs'; 5 | import { DelegationTransactionType } from 'helpers/contractDataDefinitions'; 6 | import { useDelegationWallet } from 'helpers/useDelegation'; 7 | import { useContext } from 'context'; 8 | import ConfirmTransactionModal from 'components/ConfirmTransactionModal'; 9 | 10 | const AddNodeAction = () => { 11 | const { ledgerAccount, walletConnectAccount } = useContext(); 12 | const [showAddNodes, setAddNodesModal] = useState(false); 13 | const [showCheckYourLedgerModal, setShowCheckYourLedgerModal] = useState(false); 14 | const [transactionArguments, setTransactionArguments] = useState( 15 | new DelegationTransactionType('', '') 16 | ); 17 | const { sendTransactionWallet } = useDelegationWallet(); 18 | 19 | const handleAddNodes = (value: string) => { 20 | let txArguments = new DelegationTransactionType('0', 'addNodes', value); 21 | if (ledgerAccount || walletConnectAccount) { 22 | setAddNodesModal(false); 23 | setTransactionArguments(txArguments); 24 | setShowCheckYourLedgerModal(true); 25 | } else { 26 | sendTransactionWallet(txArguments); 27 | } 28 | }; 29 | 30 | const getPemPubKeysWithSignature = (pemFiles: DropzoneFileType[]) => { 31 | let keysData = ''; 32 | pemFiles.forEach(({ pubKey, signature }) => { 33 | keysData += `@${pubKey}@${signature}`; 34 | }); 35 | return keysData; 36 | }; 37 | 38 | const addNodesRequest = { 39 | data: (pemFiles: DropzoneFileType[]) => { 40 | return `${getPemPubKeysWithSignature(pemFiles)}`; 41 | }, 42 | variables: [ 43 | { 44 | name: 'pemFiles', 45 | type: 'pemUpload', 46 | }, 47 | ], 48 | }; 49 | return ( 50 |
51 | 60 | { 64 | setAddNodesModal(false); 65 | }} 66 | triggerDispatchEvent={(variablesData: string) => handleAddNodes(variablesData)} 67 | variables={addNodesRequest.variables} 68 | data={addNodesRequest.data} 69 | /> 70 | { 74 | setShowCheckYourLedgerModal(false); 75 | }} 76 | /> 77 |
78 | ); 79 | }; 80 | 81 | export default AddNodeAction; 82 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/pages/Owner/Nodes/helpers/index.ts: -------------------------------------------------------------------------------- 1 | import * as keysFunctions from './keysFunctions'; 2 | import * as nodeTypes from './nodeTypes'; 3 | import * as stakeHooks from './stakeHooks'; 4 | 5 | export { keysFunctions }; 6 | export { nodeTypes }; 7 | export { stakeHooks }; 8 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/pages/Owner/Nodes/helpers/keysFunctions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Address, 3 | AddressValue, 4 | BytesValue, 5 | ContractFunction, 6 | decodeString, 7 | Query, 8 | } from '@elrondnetwork/erdjs'; 9 | import { auctionContract, stakingContract } from 'config'; 10 | import { DappState } from 'context/state'; 11 | import { NodeType } from './nodeType'; 12 | import { NodeStatus } from './nodeTypes'; 13 | 14 | export const getAllNodesStatus = (dapp: DappState, delegationContract?: string) => { 15 | const query = new Query({ 16 | address: new Address(delegationContract), 17 | func: new ContractFunction('getAllNodeStates'), 18 | }); 19 | return new Promise>(resolve => { 20 | dapp.proxy 21 | .queryContract(query) 22 | .then(value => { 23 | let nodes = new Array(); 24 | const untypedResponse = value.outputUntyped(); 25 | mapNodes(untypedResponse, nodes, []); 26 | return resolve(nodes); 27 | }) 28 | .catch(e => console.error('GetAllNodesStatus error ', e)); 29 | }); 30 | }; 31 | 32 | export const getBlsKeysStatus = (dapp: DappState, queued: any[], delegationContract?: string) => { 33 | const query = new Query({ 34 | address: new Address(auctionContract), 35 | func: new ContractFunction('getBlsKeysStatus'), 36 | args: [new AddressValue(new Address(delegationContract))], 37 | }); 38 | return new Promise>(resolve => { 39 | dapp.proxy 40 | .queryContract(query) 41 | .then(value => { 42 | let nodes = new Array(); 43 | let untypedResponse = value.outputUntyped(); 44 | mapNodes(untypedResponse.reverse(), nodes, queued); 45 | return resolve(nodes); 46 | }) 47 | .catch(e => console.error('GetBlsKeysStatus error', e)); 48 | }); 49 | }; 50 | export const getQueueSize = (dapp: DappState) => { 51 | const query = new Query({ 52 | address: new Address(stakingContract), 53 | func: new ContractFunction('getQueueSize'), 54 | }); 55 | return new Promise(resolve => { 56 | dapp.proxy 57 | .queryContract(query) 58 | .then(value => { 59 | const untypedResponse = value.outputUntyped(); 60 | return resolve(decodeString(untypedResponse[0])); 61 | }) 62 | .catch(e => console.error('getQueueSize error', e)); 63 | }); 64 | }; 65 | 66 | export const getQueueIndex = (blsKey: any, dapp: DappState) => { 67 | const query = new Query({ 68 | address: new Address(stakingContract), 69 | func: new ContractFunction('getQueueIndex'), 70 | caller: new Address(auctionContract), 71 | args: [BytesValue.fromHex(blsKey)], 72 | }); 73 | return new Promise(resolve => { 74 | dapp.proxy 75 | .queryContract(query) 76 | .then(value => { 77 | const untypedResponse = value.outputUntyped(); 78 | return resolve(decodeString(untypedResponse[0])); 79 | }) 80 | .catch(e => console.error('getQueueIndex error', e)); 81 | }); 82 | }; 83 | 84 | const isStatus = (value: string) => { 85 | if (NodeStatus[value]) { 86 | return true; 87 | } 88 | return false; 89 | }; 90 | 91 | const mapNodes = (responseValues: Buffer[], nodes: NodeType[], queued: any[]) => { 92 | let status: { [key: string]: string }; 93 | responseValues.forEach(value => { 94 | if (isStatus(decodeString(value))) { 95 | status = { key: decodeString(value), value: NodeStatus[decodeString(value)] }; 96 | } else { 97 | if (status.key === 'queued') { 98 | queued.push(value.toString('hex')); 99 | } 100 | nodes.push(new NodeType(value.toString('hex'), status)); 101 | } 102 | }); 103 | }; 104 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/pages/Owner/Nodes/helpers/nodeType.ts: -------------------------------------------------------------------------------- 1 | export class NodeType { 2 | blsKey!: string; 3 | status!: { [key: string]: string }; 4 | queueIndex?: string; 5 | queueSize?: string; 6 | public constructor( 7 | blsKey: string, 8 | status: { [key: string]: string }, 9 | queueIndex?: string, 10 | queueSize?: string 11 | ) { 12 | this.blsKey = blsKey; 13 | this.status = status; 14 | this.queueIndex = queueIndex; 15 | this.queueSize = queueSize; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/pages/Owner/Nodes/helpers/nodeTypes.ts: -------------------------------------------------------------------------------- 1 | export const nodeActions = { 2 | unJail: { label: 'Unjail', transaction: 'unJail' }, 3 | unStake: { label: 'Unstake', transaction: 'unStake' }, 4 | reStake: { label: 'ReStake', transaction: 'reSreStakeUnStakedNodestake' }, 5 | unBond: { label: 'Unbond', transaction: 'unBond' }, 6 | stake: { label: 'Stake', transaction: 'stake' }, 7 | remove: { label: 'Remove', transaction: 'remove' }, 8 | }; 9 | 10 | export const NodeStatus: { [key: string]: string } = { 11 | notStaked: 'Inactive', 12 | unStaked: 'UnStaked', 13 | staked: 'Staked', 14 | jailed: 'Jail', 15 | queued: 'Queued', 16 | }; 17 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/pages/Owner/Nodes/helpers/stakeHooks.ts: -------------------------------------------------------------------------------- 1 | import { DelegationTransactionType } from 'helpers/contractDataDefinitions'; 2 | export interface StakeActionType { 3 | blsKey: string; 4 | } 5 | 6 | export const nodeTransactions = { 7 | unStake: ({ blsKey }: StakeActionType) => { 8 | let transactionArgs = new DelegationTransactionType('0', 'unStakeNodes', blsKey); 9 | return transactionArgs; 10 | }, 11 | reStake: ({ blsKey }: StakeActionType) => { 12 | let transactionArguments = new DelegationTransactionType('0', 'reStakeUnStakedNodes', blsKey); 13 | return transactionArguments; 14 | }, 15 | unJail: ({ blsKey }: StakeActionType) => { 16 | let transactionArguments = new DelegationTransactionType('2.5', 'unJailNodes', blsKey); 17 | return transactionArguments; 18 | }, 19 | unBond: ({ blsKey }: StakeActionType) => { 20 | let transactionArguments = new DelegationTransactionType('0', 'unBondNodes', blsKey); 21 | return transactionArguments; 22 | }, 23 | stake: ({ blsKey }: StakeActionType) => { 24 | let transactionArguments = new DelegationTransactionType('0', 'stakeNodes', `${blsKey}`); 25 | return transactionArguments; 26 | }, 27 | remove: ({ blsKey }: StakeActionType) => { 28 | let transactionArguments = new DelegationTransactionType('0', 'removeNodes', `${blsKey}`); 29 | return transactionArguments; 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/pages/Owner/Nodes/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { useContext } from 'context'; 3 | import NodeRow from './NodeRow'; 4 | import { 5 | getAllNodesStatus, 6 | getBlsKeysStatus, 7 | getQueueSize, 8 | getQueueIndex, 9 | } from './helpers/keysFunctions'; 10 | import AddNodeAction from './AddNodeAction'; 11 | import { NodeType } from './helpers/nodeType'; 12 | 13 | const Nodes = () => { 14 | const { dapp, delegationContract } = useContext(); 15 | const [keys, setKeys] = useState(new Array()); 16 | const queued: any = []; 17 | 18 | const setQueuedKeys = async (queued: any, adaptedNodesStatus: NodeType[]) => { 19 | if (queued.length) { 20 | const results = await Promise.all([ 21 | getQueueSize(dapp), 22 | ...queued.map((blsKey: any) => getQueueIndex(blsKey, dapp)), 23 | ]); 24 | 25 | let queueSize: any; 26 | results.forEach((result, index) => { 27 | if (index === 0) { 28 | queueSize = result; 29 | } else { 30 | const [found] = adaptedNodesStatus.filter(({ blsKey }: any) => { 31 | return blsKey === queued[index - 1]; 32 | }); 33 | 34 | found.queueIndex = result; 35 | found.queueSize = queueSize; 36 | } 37 | }); 38 | } 39 | }; 40 | 41 | const getDiplayNodes = () => { 42 | Promise.all([ 43 | getAllNodesStatus(dapp, delegationContract), 44 | getBlsKeysStatus(dapp, queued, delegationContract), 45 | ]) 46 | .then(async ([nodesStatus, blsKeys]) => { 47 | const adaptedNodesStatus = nodesStatus.map(item => { 48 | let index = blsKeys.findIndex(i => i.blsKey === item.blsKey); 49 | return { 50 | ...item, 51 | status: index >= 0 ? blsKeys[index].status : item.status, 52 | }; 53 | }); 54 | await setQueuedKeys(queued, adaptedNodesStatus); 55 | setKeys(adaptedNodesStatus); 56 | }) 57 | .catch(error => console.error('getDiplayNodes error', error)); 58 | }; 59 | 60 | useEffect(getDiplayNodes, /* eslint-disable react-hooks/exhaustive-deps */ []); 61 | 62 | return ( 63 | <> 64 |
65 |
66 |
67 |

My Nodes

68 |
69 | 70 |
71 |
72 | {keys.length > 0 ? ( 73 |
74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | {keys.map((blsKey, i) => ( 84 | 85 | ))} 86 | 87 |
Public keyStatusActions
88 |
89 | ) : ( 90 | No keys found for this contract. 91 | )} 92 |
93 |
94 | 95 | ); 96 | }; 97 | export default Nodes; 98 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/pages/Owner/index.tsx: -------------------------------------------------------------------------------- 1 | import { Redirect } from 'react-router-dom'; 2 | import { Address } from '@elrondnetwork/erdjs'; 3 | import { useEffect } from 'react'; 4 | import { useContext, useDispatch } from 'context'; 5 | import Overview from 'components/Overview'; 6 | import Nodes from './Nodes'; 7 | import { AccountType } from 'helpers/contractDataDefinitions'; 8 | import { getItem } from 'storage/session'; 9 | 10 | const Owner = () => { 11 | const { 12 | address, 13 | contractOverview, 14 | loggedIn, 15 | dapp, 16 | ledgerAccount, 17 | walletConnectAccount, 18 | } = useContext(); 19 | const dispatch = useDispatch(); 20 | const isOwner = () => { 21 | let loginAddress = new Address(address).hex(); 22 | return loginAddress.localeCompare(contractOverview.ownerAddress) === 0; 23 | }; 24 | 25 | const fetchAccount = () => { 26 | dapp.proxy.getAccount(new Address(address)).then(account => { 27 | dispatch({ 28 | type: 'setAccount', 29 | account: new AccountType(account.balance.toString(), account.nonce), 30 | }); 31 | }); 32 | }; 33 | 34 | useEffect(fetchAccount, /* eslint-disable react-hooks/exhaustive-deps */ []); 35 | 36 | const isLedgerLogin = getItem('ledgerLogin') && !ledgerAccount; 37 | const isWalletConnect = getItem('walletConnectLogin') && !walletConnectAccount; 38 | const dispatchLoginType = () => { 39 | if (isLedgerLogin) { 40 | const ledgerLogin = getItem('ledgerLogin'); 41 | dispatch({ 42 | type: 'setLedgerAccount', 43 | ledgerAccount: { 44 | index: ledgerLogin.index, 45 | address: address, 46 | }, 47 | }); 48 | } 49 | if (isWalletConnect) { 50 | dispatch({ 51 | type: 'setWalletConnectAccount', 52 | walletConnectAccount: address, 53 | }); 54 | } 55 | }; 56 | 57 | useEffect(dispatchLoginType, /* eslint-disable react-hooks/exhaustive-deps */ []); 58 | 59 | if (!loggedIn) { 60 | return ; 61 | } 62 | 63 | return ( 64 | <> 65 | {isOwner() ? ( 66 |
67 |
68 | 69 |
70 | 71 |
72 |
73 |
74 | ) : ( 75 | 76 | )} 77 | 78 | ); 79 | }; 80 | 81 | export default Owner; 82 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/pages/WalletConnect/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { useHistory } from 'react-router-dom'; 3 | import QRCode from 'qrcode'; 4 | import { useContext, useDispatch } from 'context'; 5 | import { WalletConnectProvider } from '@elrondnetwork/erdjs'; 6 | import { walletConnectBridge, walletConnectDeepLink } from 'config'; 7 | 8 | const WalletConnect = () => { 9 | const { dapp } = useContext(); 10 | const dispatch = useDispatch(); 11 | const history = useHistory(); 12 | const [qrSvg, setQrSvg] = useState(''); 13 | const [wcUri, setWcUri] = useState(''); 14 | const [error, setError] = useState(''); 15 | 16 | const urlParams = new URLSearchParams(window.location.search); 17 | const isFromMobile = urlParams.get('mobileplatform') === 'true'; 18 | 19 | const svgQr: any = { 20 | dangerouslySetInnerHTML: { 21 | __html: qrSvg, 22 | }, 23 | style: { 24 | width: '15rem', 25 | height: '15rem', 26 | }, 27 | }; 28 | 29 | const buildQrCode = () => { 30 | (async () => { 31 | if (wcUri) { 32 | const svg = await QRCode.toString(wcUri, { type: 'svg' }); 33 | setQrSvg(svg); 34 | } 35 | })(); 36 | }; 37 | 38 | useEffect(buildQrCode, [wcUri]); 39 | const handleOnLogin = () => { 40 | dapp.provider 41 | .getAddress() 42 | .then(address => { 43 | dispatch({ 44 | type: 'setWalletConnectLogin', 45 | walletConnectLogin: { 46 | loginType: 'walletConnect', 47 | }, 48 | }); 49 | dispatch({ type: 'login', address }); 50 | history.push('/dashboard'); 51 | }) 52 | .catch(e => { 53 | setError('Invalid address'); 54 | console.log(e); 55 | }); 56 | }; 57 | 58 | const handleOnLogout = () => { 59 | dispatch({ type: 'logout', provider: dapp.provider }); 60 | }; 61 | 62 | const walletConnectInit = async () => { 63 | const walletConnect = new WalletConnectProvider(dapp.proxy, walletConnectBridge, { 64 | onClientLogin: handleOnLogin, 65 | onClientLogout: handleOnLogout, 66 | }); 67 | dapp.provider = walletConnect; 68 | 69 | const walletConectUri = await walletConnect.login(); 70 | setWcUri(walletConectUri); 71 | }; 72 | 73 | useEffect( 74 | () => { 75 | walletConnectInit(); 76 | }, 77 | /* eslint-disable react-hooks/exhaustive-deps */ [] 78 | ); 79 | 80 | return ( 81 |
82 |
83 |
84 |
85 | 86 |

Connect Maiar

87 |

88 | {isFromMobile ? ( 89 | <> 90 |

91 | Scan the QR code using Maiar or click the button below to open the App 92 |

93 | 101 | Maiar Login 102 | 103 | 104 | ) : ( 105 | 'Scan the QR code using Maiar' 106 | )} 107 |

108 |
109 | {error && ( 110 |

111 | {error} 112 |

113 | )} 114 |
115 |
116 |
117 |
118 | ); 119 | }; 120 | 121 | export default WalletConnect; 122 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/routes.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Dashboard from './pages/Dashboard'; 3 | import Home from './pages/Home'; 4 | import withPageTitle from './components/PageTitle'; 5 | import Owner from 'pages/Owner'; 6 | import Ledger from 'pages/Ledger'; 7 | import WalletConnect from 'pages/WalletConnect'; 8 | 9 | interface RouteType { 10 | path: string; 11 | page: string; 12 | title: string; 13 | component: any; 14 | } 15 | 16 | const routes: RouteType[] = [ 17 | { 18 | path: '/', 19 | page: 'home', 20 | title: '', 21 | component: Home, 22 | }, 23 | { 24 | path: '/dashboard', 25 | page: 'dashboard', 26 | title: 'Dashboard', 27 | component: Dashboard, 28 | }, 29 | { 30 | path: '/ledger', 31 | page: 'ledger', 32 | title: 'Ledger login', 33 | component: Ledger, 34 | }, 35 | { 36 | path: '/walletconnect', 37 | page: 'walletconnect', 38 | title: 'Maiar login', 39 | component: WalletConnect, 40 | }, 41 | { 42 | path: '/owner', 43 | page: 'owner', 44 | title: 'Owner', 45 | component: Owner, 46 | }, 47 | ]; 48 | 49 | const wrappedRoutes = () => { 50 | return routes.map(route => { 51 | const title = route.title ? `${route.title} • Delegation Manager` : 'Delegation Manager'; 52 | return { 53 | path: route.path, 54 | page: route.page, 55 | component: (withPageTitle(title, route.component) as any) as React.ComponentClass<{}, any>, 56 | }; 57 | }); 58 | }; 59 | 60 | export default wrappedRoutes(); 61 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/setupTests.ts: -------------------------------------------------------------------------------- 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/extend-expect'; 6 | -------------------------------------------------------------------------------- /react-delegationdashboard/src/storage/session.tsx: -------------------------------------------------------------------------------- 1 | import moment from 'moment'; 2 | 3 | export const setItem = (key: string, item: any, ttl: number = 3600) => { 4 | const expires = moment().unix() + ttl; 5 | sessionStorage.setItem( 6 | key, 7 | JSON.stringify({ 8 | expires, 9 | data: item, 10 | }) 11 | ); 12 | }; 13 | 14 | export const getItem = (key: string): any => { 15 | const item = sessionStorage.getItem(key); 16 | if (!item) { 17 | return null; 18 | } 19 | 20 | const deserializedItem = JSON.parse(item); 21 | if (!deserializedItem) { 22 | return null; 23 | } 24 | 25 | if (!deserializedItem.hasOwnProperty('expires') || !deserializedItem.hasOwnProperty('data')) { 26 | return null; 27 | } 28 | 29 | const expired = moment().unix() >= deserializedItem.expires; 30 | if (expired) { 31 | sessionStorage.removeItem(key); 32 | return null; 33 | } 34 | 35 | return deserializedItem.data; 36 | }; 37 | 38 | export const removeItem = (key: string) => sessionStorage.removeItem(key); 39 | -------------------------------------------------------------------------------- /react-delegationdashboard/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2019", 4 | "lib": ["dom", "dom.iterable", "es2019"], 5 | "allowJs": true, 6 | "baseUrl": "src", 7 | "skipLibCheck": true, 8 | "esModuleInterop": true, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx", 18 | "noFallthroughCasesInSwitch": true 19 | }, 20 | "include": ["src"] 21 | } 22 | --------------------------------------------------------------------------------